Tools

June 4, 2026 · View on GitHub

Chaz agents interact with the world through tools. The ReAct loop calls tools based on LLM decisions, subject to security policies and approval gates.

Built-in Tools

Every built-in is owned by an extension; disabling an extension hides its tools from the LLM. The owning extension is shown so you can find a tool's lifecycle (and disable it per-session/per-agent) at a glance.

ToolOwnerRiskApprovalDescription
get_timesystemLowNeverReturns the current UTC time
calculatesystemLowNeverEvaluates math expressions (via meval)
describe_toolsystemLowNeverReturns full description/schema for a tool (discovery)
compactcoreLowNeverSummarize and compact conversation context
spawn_agentcoreMediumUnlessAutoApprovedDelegates to a named peer Agent (persistent identity, own keys)
spawn_workercoreMediumUnlessAutoApprovedInvokes a Worker template declared under the calling Agent (no keys)
shellcoreHighAlwaysExecutes a shell command
read_filefsLowNeverReads file contents from disk
write_filefsMediumUnlessAutoApprovedWrites content to a file
edit_filefsMediumUnlessAutoApprovedReplace exact text in a file (single or atomic-multi-edit)
web_fetchwebMediumUnlessAutoApprovedHTTP GET or POST requests
web_searchwebLowNeverSearch the web; returns title/url/snippet per result
remembermemoryLowNeverStores a key-value fact in the agent's own memory (or a granted bank)
recallmemoryLowNeverSearches the agent's own memory (or a granted bank) by keyword
list_memory_banksmemoryLowNeverLists the memory banks this agent has been granted access to
skill_listskillsLowNeverList the skills available to this agent (progressive disclosure)
skill_searchskillsLowNeverSearch the skill catalog by keyword
skill_showskillsLowNeverFetch a skill's body — the "activation" half of progressive disclosure
schedule_addscheduleLowNeverAdd a recurring agent-owned schedule (cron)
schedule_modifyscheduleLowNeverPartial update of an existing schedule
schedule_removescheduleLowNeverDelete a schedule by id
schedule_listscheduleLowNeverList an agent's schedules
schedule_oncescheduleLowNeverAdd a one-shot schedule firing after N seconds

That's 23 tools across 7 extensions today. External tools from MCP servers plug in under the same policy layer and show up here too, namespaced as <server>__<tool> (matches the Anthropic Agent SDK / Claude Code mcp__server__tool convention so OpenAI-compatible providers, which reject dots in function names, accept them).

Risk Levels

  • Low -- safe operations with no side effects
  • Medium -- operations that modify state or access the network
  • High -- operations that execute arbitrary code

Approval Requirements

  • Never -- tool runs without asking the user
  • UnlessAutoApproved -- runs automatically if listed in security.auto_approved_tools, otherwise asks
  • Always -- always asks the user before running

In the TUI, approval is an inline prompt (y/n/a). In Matrix, approval is not yet implemented -- unapproved tools time out.

Tool Details

get_time

Returns the current UTC timestamp. No arguments.

calculate

Evaluates a mathematical expression string. Uses the meval crate.

{ "expression": "2 * pi * 6371" }

read_file / write_file

Read or write files on the host filesystem. Both go through the host's FileRead / FileWrite capabilities; once the FsGrant path enforcement is wired they'll honour allow_read / allow_write allowlists.

{"path": "/tmp/notes.txt"}
{"path": "/tmp/output.txt", "content": "Hello, world!"}

edit_file

Replace exact text in a file. The tool validates that each old_text appears exactly once before writing, which makes diff-style edits safe. Use the edits array to apply several replacements atomically (all or nothing).

{"path": "/tmp/notes.txt", "old_text": "TODO: implement", "new_text": "Implemented in PR #42"}

{"path": "/tmp/config.toml", "edits": [
  {"old_text": "version = \"0.3.0\"", "new_text": "version = \"0.3.1\""},
  {"old_text": "verbose = false",     "new_text": "verbose = true"}
]}

web_fetch

Performs HTTP requests. Subject to network policy (endpoint allowlisting, SSRF protection).

{"url": "https://api.example.com/data", "method": "GET"}
{"url": "https://api.example.com/submit", "method": "POST", "body": "{\"key\": \"value\"}"}

Runs a search query and returns up to 10 normalized results ({title, url, snippet}). Typically pairs with web_fetch — search for a topic, then fetch the most relevant result.

{ "query": "CRDT synchronization algorithms", "max_results": 5 }

Backends are an ordered preference list configured under web_search.backends — the tool tries each in turn and falls through to the next on any error, returning the first success. Configuration lives in Configuration. Supported backends:

  • kagi — Kagi Search API (requires api_key; invite-only beta)
  • tavily — LLM-oriented search API (requires api_key)
  • brave — Brave Search API (requires api_key)
  • serper — Google SERP via serper.dev (requires api_key)
  • searxng — any SearxNG instance (requires url:, no key by default). Self-host or point at a public instance.
  • duckduckgo — keyless fallback that scrapes DuckDuckGo's HTML page

Empty results are a legitimate answer and do not trigger failover — otherwise a bad query would mask itself by running through every backend. Only errors (network, HTTP non-2xx, bad JSON) advance to the next entry.

The tool is Low-risk and approval-free because the agent never supplies the destination URL — only a query string — and every HTTP destination is fixed by operator config.

shell

Executes a shell command. Subject to command allowlist/denylist filtering.

{ "command": "ls -la /tmp" }

remember / recall

Persistent key-value memory. By default writes to the agent's own memory (its own Living Agent DB's memory subtree), which travels with the agent through eidetica sync and is naturally isolated from other agents.

{"key": "user_timezone", "value": "America/New_York"}
{"query": "timezone"}

Shared memory banks. Pass an optional bank argument to read or write a shared bank the agent has been granted access to. Banks are separate eidetica DBs (or other Agent DBs) configured by an operator via /memory grant; the agent's own grants are listed by list_memory_banks. Write access requires write permission on the bank.

{"key": "deadline", "value": "Friday", "bank": "project-alpha"}
{"query": "deadline", "bank": "project-alpha"}

There is no "global" scope: cross-agent sharing is always a bank with an explicit grant. Access is authoritatively enforced by the bank DB's eidetica AuthSettings — the agent's key must be authorized on the bank DB itself.

list_memory_banks

Lists the memory banks this agent has been granted access to, with the permission level (Read or Write) for each. self is always listed — that's the agent's own memory. Use the names it returns with remember / recall's bank argument.

describe_tool

Returns the full description and JSON Schema for any registered tool. Useful when tool profiles hide details (Brief or Summary mode) and the agent needs the full specification.

{ "tool": "filesystem__read_file" }

compact

Summarizes the conversation history via an LLM call and writes a Summary entry. The context builder treats the most recent Summary as the conversation start boundary, effectively compacting older messages.

spawn_agent

Delegates a task to another agent in a child session. The named agent's persistent identity and memory are used — pick this when the work needs continuity. See Agents.

{
  "agent": "researcher",
  "task": "Find the latest papers on CRDT synchronization",
  "async": false
}

spawn_worker

Invokes a Worker template declared under the calling Agent. Workers are configured one-shot LLM calls — they have no identity, no keys, and no persistent state of their own; entries written to the child session are signed by the parent Agent's key. Use a Worker for focused, delegated work that doesn't need its own continuity; use spawn_agent when you want a named peer Agent (Ava, Chaz) with its own keys and persistent memory.

Worker templates are per-Agent (Ava's researcher is distinct from Chaz's researcher); resolution is scoped to the calling Agent only.

{
  "name": "researcher",
  "task": "Read /tmp/log.json and summarise the error counts by minute",
  "tools": ["read_file", "calculate"],
  "async": false
}

name is required and selects the Worker template. tools narrows the resolved scope for this invocation (must be a subset of what the Worker template + parent Agent allow). model and preset are optional overrides. Unset fields on a Worker template fall back to the parent Agent's defaults. max_iterations is accepted for compatibility but does not carve out a separate ReAct budget — every Worker invocation shares the calling Agent's iteration pool, so a nested Worker draws from whatever budget remains rather than starting fresh. See Spawn Permissions for the budget semantics.

skill_list / skill_search / skill_show

Progressive-disclosure access to the skills catalog. skill_list returns one line per available skill (name — description); skill_search filters that list by keyword. Neither loads the body. skill_show is what actually pulls a skill into the agent's view — call it with the name from the catalog.

{ "query": "git" }
{ "skill": "release-checklist" }

Skills come from three sources merged at session start: disk skills shipped on this peer, skills written into the responding agent's own DB, and skills in any granted skill banks attached to the session. The merged catalog is exposed via these tools only when the skills extension is active for the agent.

schedule_add / schedule_modify / schedule_remove / schedule_list

Agent-facing CRUD over agent-owned schedules, mirroring the /schedule slash commands described in Agents — Schedules. Schedules live in the owning agent's DB (not the session) and are fired by chaz's RoutineEngine, which sleeps until the next due fire instead of polling. schedule_add targets the current session (Pinned) by default, or a fresh session per fire. Cron uses 6 fields: sec min hour day_of_month month day_of_week.

{
  "id": "morning-brief",
  "cron": "0 0 9 * * Mon-Fri",
  "task": "Summarize overnight activity"
}

The agent field is optional — omit it to target yourself, or pass a display name / DB id to target another agent on this peer.

Lifecycle bounds. A recurring schedule can be retired automatically:

  • max_fires — retire after this many fires. cron hourly + max_fires: 8 expresses "wake hourly for 8 hours".
  • expires_at — RFC 3339 timestamp after which it stops firing.

Whichever bound is hit first wins; both are optional (omit = unbounded). fire_count is tracked authoritatively in the agent DB so max_fires survives restarts. When a bound is reached the schedule is persisted as disabled (it shows in schedule_list as (disabled) with its [fired N×] count rather than being deleted, so the history stays auditable). schedule_modify can set/replace max_fires/expires_at; re-enabling a schedule that already passed a bound will simply retire again on its next fire.

schedule_once

One-shot wakeup that fires a directive into this session after a delay, then deletes itself. The directive is routed back to the calling agent — cross-agent scheduling stays in schedule_add. Use it for "come back to this in N seconds" cases; for recurring work, use schedule_add instead.

{
  "after_seconds": 1800,
  "task": "Check whether the build finished and report status."
}
  • after_seconds is bounded to [30, 2_592_000] (30 seconds to 30 days).
  • The wakeup time has up to 30 s of jitter past the requested delay (poll interval).
  • Generated rule id has the form wakeup-<epoch_ms>; it surfaces in /schedule list and schedule_list with an @YYYY-MM-DD HH:MM:SSZ marker in place of a cron expression.

External Tools (MCP)

Chaz supports external tools via the Model Context Protocol. MCP servers run as subprocesses (or are reached over Streamable HTTP) and their tools are registered alongside built-ins, subject to the same policy layer. See MCP External Tools for configuration and a worked example.

Tool Profiles

Tool profiles control how each tool's definition is presented to the LLM — useful when the full set is large and you want to keep token cost down or hide everything but the names from cheap models. A profile maps tool name (exact or glob) to one of:

  • Full (default) — name, description, full JSON Schema.
  • Brief — name + first sentence of the description, parameter names only (no per-param descriptions).
  • Summary — name only, parameters trimmed to an empty schema. The LLM has to call describe_tool to learn more.
  • Hidden — not sent to the LLM at all. The tool stays callable if the agent already knows about it.

Profiles live under top-level tool_profiles: in config and are referenced by name from an agent or preset. The resolution order for a tool is exact name → glob prefix (namespace.*) → profile default.

tool_profiles:
  compact:
    default: full
    tools:
      "filesystem.*": brief
      "github.*": summary
      shell: hidden

agents:
  - name: skim
    tool_profile: compact

The compact profile here shows full schemas for built-ins, brief schemas for the filesystem MCP namespace, name-only for github, and drops shell entirely.

Security Controls

Capability boundary

Tools access system resources through a ToolHost — a sandboxed capability boundary. The tool declares what it wants to do (e.g. Capability::Shell { command: "ls -la", … }), and the host decides whether to allow it based on configured grants. The tool itself never reads file paths or hits the network directly; only the host does. The default NativeToolHost enforces grants in-process; future hosts will add OS-level (bwrap) or VM-level (WASM) sandboxing.

Today there are three grant kinds, declared per-tool under security.tool_policies.<tool>.grants:

GrantFieldWhat it does
shellallow / denyCommand-prefix allowlist + denylist. Empty allow = allow-all. deny always wins.
networkendpointsList of { host, path_prefix?, methods? } patterns. Empty list = allow any public host.
networkallow_privateIf false (default), private IPs and internal hostnames are blocked even when allowed.
fsallow_read / allow_writePath allowlists. Schema stub — accepted in config today, enforcement still pending.

Resolution: the tool's default_policy() ships baseline grants; security.tool_policies overrides per-tool at config load; per-agent grants on agents: overlay last, kind-by-kind (Grants::merge_over — the agent layer replaces a kind only when it explicitly sets one).

Worked example: lock shell down to git and ls, deny everything else

security:
  tool_policies:
    shell:
      approval: unless_auto_approved # let agents run allowed commands without prompting
      grants:
        shell:
          allow: ["git", "ls", "cat"]
          deny: ["rm", "sudo"]
    web_fetch:
      grants:
        network:
          endpoints:
            - host: "api.github.com"
              methods: ["GET"]
            - host: "*.example.com"
              path_prefix: "/v1"
          allow_private: false

What this gets you:

  • shell accepts git status, ls -la, cat /etc/hosts, but the host denies git push && rm foo because the denylist matches rm in the second segment of the pipeline. Empty grant or missing grants.shell = permissive (the host enforces nothing).
  • web_fetch can GET https://api.github.com/... and any path under https://*.example.com/v1/..., but is denied other hosts entirely; private IPs (127.0.0.1, 10.0.0.0/8, internal hostnames) are blocked regardless.
  • Want a specific agent to be tighter still? Re-declare the same grants: block under agents: - name: <agent> — that overlay replaces the resolved policy's grants per-kind for that agent only.

The legacy security.shell_allowlist / security.shell_denylist / security.allowed_endpoints fields still work but are deprecated; they're synthesised into the new grant shape at startup with a one-time warn!.

Leak detection

All tool outputs are scanned for secret patterns (API keys, tokens, etc.) before entering the LLM context. The leak detector supports 12 patterns and can either redact or block the output.

Output safety

Tool results fed back to the LLM are wrapped in XML delimiters (<tool_output tool="name">...</tool_output>) with angle-bracket escaping, preventing prompt injection through tool output.

Timeouts and rate limits

Tool execution is wrapped in a configurable timeout (default varies by tool). Tools can also have a rate_limit (max calls per minute) configured in their policy.

See Security for details on network policies, shell sandboxing, rate limiting, and approval configuration.