> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ziro-agent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools

> Progressive tool loading keeps the context small: core tools are always bound, everything else is discovered and activated on demand.

Binding every tool to every LLM call wastes context and degrades tool selection.
Ziro instead splits tools into three tiers and lets the model discover and
activate what it needs, when it needs it.

## The three tiers

| Tier         | Bound to the LLM               | Where it is defined               |
| ------------ | ------------------------------ | --------------------------------- |
| **Core**     | Every turn, always             | `tool_policy.yaml` → `core_tools` |
| **Meta**     | Every turn, always             | Built in: the discovery surface   |
| **Deferred** | Only after `load_tools([...])` | Registered in the `ToolRegistry`  |

Deferred tools are registered in the registry but **not bound** until the model
activates them. They are searchable the whole time; they just do not occupy a
slot in the tool schema block sent with each request.

## The discovery loop

<Steps>
  <Step title="Search">
    The model calls `search_tools("fetch a url")`. This is a semantic search over
    indexed tool descriptions, with a keyword fallback.
  </Step>

  <Step title="Load">
    It picks names from the results and calls
    `load_tools(["webfetch:web_fetch"])`.
  </Step>

  <Step title="Call">
    The tool is now bound and can be called normally.
  </Step>
</Steps>

Activated tools persist for the rest of the thread, up to `max_active`. Past
that, the least-recently-used activated tool is evicted. The active set is
tracked in `AgentState.active_tools` and rendered in the TUI side pane.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
python -m ziro.tools.indexer      # re-index tool descriptions
```

<Note>
  `unload_tools([...])` deactivates tools explicitly, to free context before the
  LRU would.
</Note>

## Namespaces and qualified names

Every tool is registered under a namespace, and its **qualified name** is
`<namespace>:<tool>`:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
fs:read_file
fs_write:write_file
shell:run_shell
webfetch:web_fetch
websearch:web_search
subagent:spawn_subagent
tasks:write_todos
```

Qualified names are what you write in the config files that talk about tools:

* `tool_policy.yaml` → `core_tools`
* `permissions.yaml` → `allow` / `ask` / `deny` / `dangerous_default_deny` globs
* a subagent's `*.agent.md` → `tools:` and `namespaces:`
* `hooks.yaml` → the `matcher` glob for `pre_tool` and `post_tool`

Namespace tokens expand to concrete tools. `rag`, `rag:*`, and the bare namespace
name all resolve to every tool registered in that namespace, preserving order and
de-duplicating. That is how a subagent can declare its rights as whole namespaces
rather than listing individual tools.

<Info>
  The model usually calls a tool by its unqualified name (`run_shell`), which is
  why hook matchers for it are written `*run_shell`.
</Info>

Tools from external MCP servers are registered under the **server name** as the
namespace, and enter the same deferred-discovery surface. See
[MCP](/guides/mcp).

## A disabled tool is not registered at all

This is the important consequence of the design. When a feature's policy file
says `enabled: false`, the tool is **never registered** in the registry. It is
therefore:

* invisible to `search_tools` and `list_tools`
* impossible to activate with `load_tools`
* absent from the permission view a subagent inherits

There is no separate runtime gate: the absence *is* the disable switch.

| Policy file             | Controls                                                        |
| ----------------------- | --------------------------------------------------------------- |
| `shell_policy.yaml`     | `run_shell` (default **off**)                                   |
| `webfetch_policy.yaml`  | `web_fetch`                                                     |
| `websearch_policy.yaml` | `web_search`                                                    |
| `fs_policy.yaml`        | `enabled` for reads; `allow_write` for the `fs_write` namespace |
| `handoff_policy.yaml`   | `request_handoff`                                               |
| `clarify_policy.yaml`   | `ask_user_question` (default on)                                |
| `subagent_policy.yaml`  | the `subagent` namespace                                        |

<Warning>
  Registration is a *visibility* control, not a permission boundary. A registered
  tool still passes through the F06 permission gate on every call, which can
  `allow`, `ask` (raising a human-in-the-loop interrupt), or `deny`. See
  [Permissions](/guides/permissions).
</Warning>

## The full tool surface

| Tool                                                          | Tier                   | Purpose                                                                                                                         |
| ------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `search_rag(query)`                                           | Core                   | Semantic search over indexed documents (the "docs" collection).                                                                 |
| `search_skills(query)`                                        | Core                   | Semantic search over `SKILL.md` files; returns available references.                                                            |
| `load_skill_ref(skill_name, filename)`                        | Core                   | Load a specific reference or script file from a skill directory on demand.                                                      |
| `save_memory(content, scope?, name?, description?)`           | Core                   | Persist a fact. `scope="user"` (default) hits the personal store; `scope="project"` writes a file to this project's memory dir. |
| `load_project_memory(name)`                                   | Core                   | Load the full body of a project-memory fact by name.                                                                            |
| `write_todos(todos)`                                          | Core (`tasks`)         | Rewrite the turn's todo list; renders in the TUI side pane.                                                                     |
| `update_todo(id, …)`                                          | Core (`tasks`)         | Update one todo's `status`, `content`, or `result`.                                                                             |
| `request_handoff(reason)`                                     | Core (`handoff`)       | Pause the turn for a human operator and inject their reply as the next message.                                                 |
| `ask_user_question(questions)`                                | Core (`clarify`)       | Ask 1–4 structured clarifying questions (2–4 options each, optional multi-select) mid-turn.                                     |
| `search_tools(query)`                                         | Meta                   | Find available deferred tools by keyword.                                                                                       |
| `load_tools(names)`                                           | Meta                   | Activate deferred tools for this thread.                                                                                        |
| `list_tools(namespace)`                                       | Meta                   | Browse all tools by namespace.                                                                                                  |
| `unload_tools(names)`                                         | Meta                   | Deactivate tools to free context.                                                                                               |
| `spawn_subagent(agent_id, task)`                              | Deferred (`subagent`)  | Delegate a self-contained subtask to a child agent in isolated context; returns one concise result.                             |
| `dispatch_subagents(tasks)`                                   | Deferred (`subagent`)  | Parallel fan-out; one result block per child.                                                                                   |
| `get_subagent_transcript(run_id)`                             | Deferred (`subagent`)  | Pull a past child run's full transcript on demand (capped).                                                                     |
| `web_fetch(url)`                                              | Deferred (`webfetch`)  | Fetch an http/https page and return cleaned text. SSRF-guarded.                                                                 |
| `web_search(query, max_results?)`                             | Deferred (`websearch`) | Search the web; returns ranked title/url/snippet results.                                                                       |
| `run_shell(command, cwd?)`                                    | Deferred (`shell`)     | Run a shell command on the host or in a sandbox container.                                                                      |
| `read_file(path, offset?, limit?)`                            | Core (`fs`)            | Read a project text file as numbered lines. Byte- and line-capped, binary-safe, path-confined.                                  |
| `grep(pattern, path, glob?, output_mode?, case_insensitive?)` | Core (`fs`)            | Regex search over file contents. Skips ignored dirs and binaries.                                                               |
| `glob_files(pattern, path?)`                                  | Core (`fs`)            | Find files by glob, mtime-sorted, repo-relative, path-confined.                                                                 |
| `write_file(path, content)` / `edit_file(path, …)`            | Deferred (`fs_write`)  | Mutating writes, byte-capped and path-confined.                                                                                 |

### Why `fs` and `fs_write` are separate namespaces

Reads and writes are split so a permission policy can `allow` `fs:*` while
setting `ask` on `fs_write:*`. The read-only trio stays frictionless; every
mutation goes through the gate. `fs_write` also refuses the control plane by
default: secrets (`.env`, `*.key`, `*.pem`, ssh keys), `*_policy.yaml`,
`permissions.yaml`, `hooks.yaml`, `.git`, `.github`, and `.ziro`.

## Configuring the tiers

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
# packages/ziro-core/src/ziro/agents/<id>/tool_policy.yaml
core_tools:
  - search_rag
  - search_skills
  - save_memory
  - fs:read_file
  - fs:grep
  - fs:glob_files
  - tasks:write_todos
  - tasks:update_todo
max_active: 12
```

Namespace tokens work here too: `fs` or `fs:*` pulls in the whole namespace.

<Tip>
  Keep `core_tools` small. Every core tool's schema is sent on every request and
  counts against the compaction budget. The whole point of the deferred tier is
  that a tool costs nothing until it is needed.
</Tip>

## Subagents inherit, never widen

A spawned subagent's tool rights are the **intersection** of the child's own tool
policy and the parent's current rights: core tools, active tools, and permission
grants. This is enforced by a filtered facade over the shared registry plus a
predicate threaded into the meta-tools, so even progressive discovery cannot
surface or activate an out-of-rights tool for a child. See
[Subagents](/concepts/subagents).

## Next steps

<Columns cols={3}>
  <Card title="Permissions" icon="shield" href="/guides/permissions">
    The gate every tool call passes through.
  </Card>

  <Card title="Shell and files" icon="terminal" href="/guides/shell-and-files">
    Running commands and editing files safely.
  </Card>

  <Card title="MCP" icon="plug" href="/guides/mcp">
    Bringing external tool servers into the same surface.
  </Card>
</Columns>
