# zidane/tools

> The built-in tools, the factories for shell, spawn, skills and interaction tools, and the `ToolDef` types.

Import: `import { … } from 'zidane/tools'`. Source: [src/tools/index.ts](https://github.com/Tahul/zidane/blob/main/src/tools/index.ts).

Use `zidane/tools` to pick tools one by one instead of the `basic` preset, or to write your own. It holds the file tools (`readFile`, `writeFile`, `edit`, `multiEdit`, `listFiles`, `glob`, `grep`), `shell` with its background-task companions, the `spawn` factory and the `ToolDef` and `ToolContext` types. The web tools are in [`zidane/tools/web-search`](https://zidane.si/docs/reference/tools-web-search.md) and [`zidane/tools/fetch-url`](https://zidane.si/docs/reference/tools-fetch-url.md). See [Tools](https://zidane.si/docs/tools.md).

```ts
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
import { glob, grep, listFiles, readFile } from 'zidane/tools'

const agent = createAgent({
  provider: anthropic(),
  tools: { read_file: readFile, list_files: listFiles, glob, grep },
})

await agent.run({ prompt: 'Find where retries are configured and explain how.' })
```

## Functions

### `createGenerateImageTool`

Function.

Build the `generate_image` tool.

```ts
function createGenerateImageTool(options?: GenerateImageToolOptions): ToolDef
```

**Parameters**

- `options?` (`GenerateImageToolOptions`, default `{}`): Providers to generate with and the default model.

**Returns** `ToolDef`

Source: [src/tools/generate-image.ts:213](https://github.com/Tahul/zidane/blob/main/src/tools/generate-image.ts#L213)

### `createInteractionTool`

Function.

Create an interaction tool that lets the agent request structured input.

The model calls this tool with a payload matching the schema.
`onRequest` is called with the payload and should return the response
(string or object) that gets sent back to the model as the tool result.

```ts
function createInteractionTool(options: InteractionToolOptions): ToolDef
```

**Parameters**

- `options` (`InteractionToolOptions`): Payload schema, optional name and description, and the `onRequest` handler.

**Returns** `ToolDef`

**Example**

```ts
import { createInteractionTool } from 'zidane/tools'

const askUser = createInteractionTool({
  name: 'ask_user',
  schema: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] },
  onRequest: async payload => `Answer to: ${String(payload.question)}`,
})
```

Source: [src/tools/interaction.ts:58](https://github.com/Tahul/zidane/blob/main/src/tools/interaction.ts#L58)

### `createModelSearchTool`

Function.

Build the `model_search` tool over a spawn model catalog. Register it
alongside `createSpawnTool({ models })` — typically only when the
catalog is too large to embed in the spawn schema, though it is
harmless (and occasionally useful) alongside an embedded catalog too.

```ts
function createModelSearchTool(models: readonly SpawnModelEntry[]): ToolDef
```

**Parameters**

- `models` (`readonly SpawnModelEntry[]`): Spawn model catalog to search.

**Returns** `ToolDef`

Source: [src/tools/model-search.ts:83](https://github.com/Tahul/zidane/blob/main/src/tools/model-search.ts#L83)

### `createShellTool`

Function.

Creates the `shell` tool, which runs a command in the execution context, in the foreground or as a background task.

The default exported `shell` is equivalent to `createShellTool({ allowBackground: true })`.
Hosts that use `createAgent` typically do not call this directly: when
`behavior.tasksDir` is unset or `behavior.disableBackgroundTasks: true` is
set, the agent rewrites the registered built-in `shell` using this factory.

Truncation is **tail-priority**: when stdout+stderr combined exceeds
`maxOutputBytes`, the head is dropped and a marker `…(N bytes truncated
from head)…` is inserted before the tail. Errors and exit summaries
usually live at the end of output, so keeping the tail preserves the
model's most useful signal. Commands that only explore (`cat`, `sed -n`,
`grep`, `ls`, `git log`…) keep their start and end instead: what a read
shows first is what it was run for.

The combined cap defaults to 32 KiB and the per-call timeout follows the
execution context's own default (30 s for in-process).

Read-only commands run in parallel with other safe tool calls. Anything else runs alone.

```ts
function createShellTool(opts?: CreateShellToolOptions): ToolDef
```

**Parameters**

- `opts?` (`CreateShellToolOptions`, default `{}`): Background support and sibling tool names. See [`CreateShellToolOptions`](https://zidane.si/docs/reference/tools.md#CreateShellToolOptions).

**Returns** `ToolDef`

**Example**

```ts
import { createShellTool } from 'zidane/tools'

const shell = createShellTool({ allowBackground: false })
```

Source: [src/tools/shell.ts:206](https://github.com/Tahul/zidane/blob/main/src/tools/shell.ts#L206)

### `createSkillsReadTool`

Function.

Creates the `skills_read` tool, which reads a bundled resource file of an active skill. Unknown skill names are rejected at runtime.

```ts
function createSkillsReadTool(options: SkillsReadToolOptions): ToolDef
```

**Parameters**

- `options` (`SkillsReadToolOptions`): Skill catalog and activation state.

**Returns** `ToolDef`

Source: [src/tools/skills-read.ts:31](https://github.com/Tahul/zidane/blob/main/src/tools/skills-read.ts#L31)

### `createSkillsRunScriptTool`

Function.

Creates the `skills_run_script` tool, which runs a script from an active skill's `scripts/` directory in the execution context.

```ts
function createSkillsRunScriptTool(options: SkillsRunScriptToolOptions): ToolDef
```

**Parameters**

- `options` (`SkillsRunScriptToolOptions`): Skill catalog, activation state and script timeout.

**Returns** `ToolDef`

Source: [src/tools/skills-run-script.ts:36](https://github.com/Tahul/zidane/blob/main/src/tools/skills-run-script.ts#L36)

### `createSkillsUseTool`

Function.

Factory for `skills_use`. Auto-injected into the agent's tool set by the
agent runtime when a non-empty skills catalog is available (unless
`SkillsConfig.tool === false`).

The `name` property carries no `enum` so the tool list stays byte-stable
across catalogs; unknown names are rejected at runtime with the list of
available skills.

```ts
function createSkillsUseTool(options: SkillsUseToolOptions): ToolDef
```

**Parameters**

- `options` (`SkillsUseToolOptions`): Skill catalog, activation state, hooks and shell interpolation settings.

**Returns** `ToolDef`

Source: [src/tools/skills-use.ts:112](https://github.com/Tahul/zidane/blob/main/src/tools/skills-use.ts#L112)

### `createSpawnTool`

Function.

Create a configured spawn tool.

State (`children`, `totalChildStats`, counters, active count) is scoped to
the returned instance. Multiple parent agents using the same instance will
share counters + stats + concurrency slots — call `createSpawnTool()` per
agent (or use the stateless default `spawn`) to keep them isolated.

```ts
function createSpawnTool(options?: SpawnToolOptions): SpawnTool
```

**Parameters**

- `options?` (`SpawnToolOptions`, default `{}`): Limits, model catalog, subagent registry and child defaults. See [`SpawnToolOptions`](https://zidane.si/docs/reference/tools.md#SpawnToolOptions).

**Returns** `SpawnTool`

**Example**

```ts
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers'
import { createSpawnTool } from 'zidane/tools'

const spawn = createSpawnTool({ maxConcurrent: 2, maxDepth: 1 })
const agent = createAgent({
  provider: anthropic({ apiKey: 'sk-ant-...' }),
  tools: { spawn },
})
```

Source: [src/tools/spawn.ts:480](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L480)

### `createToolSearchTool`

Function.

Factory for `tool_search`. Auto-injected by the agent when
`behavior.toolDisclosure === 'lazy'` and at least one MCP tool is in the
registry. Opt out via `behavior.toolSearch.tool === false`.

```ts
function createToolSearchTool(options: ToolSearchToolOptions): ToolDef
```

**Parameters**

- `options` (`ToolSearchToolOptions`): Lazy tool catalog, unlock set and callbacks. See [`ToolSearchToolOptions`](https://zidane.si/docs/reference/tools.md#ToolSearchToolOptions).

**Returns** `ToolDef`

Source: [src/tools/tool-search.ts:281](https://github.com/Tahul/zidane/blob/main/src/tools/tool-search.ts#L281)

### `formatModelLine`

Function.

One display line per model: id, provider, real pricing, context
window, reasoning flag. The single source of truth for how models
render in BOTH the spawn schema's embedded catalog and `model_search`
output, so the model never sees two spellings of the same entry.

```ts
function formatModelLine(entry: SpawnModelEntry): string
```

**Parameters**

- `entry` (`SpawnModelEntry`): Model and its provider.

**Returns** `string`

Source: [src/tools/model-search.ts:40](https://github.com/Tahul/zidane/blob/main/src/tools/model-search.ts#L40)

### `getReadState`

Function.

Get or lazily create the per-session read-state map. Returns `undefined`
when no session is provided.

Most tool callers should prefer [`resolveReadStateMap`](https://zidane.si/docs/reference/tools.md#resolveReadStateMap), which
additionally honors an explicit `ctx.readState` (the path
`spawn`'s `shareReadState: true` uses to forward the parent's map
into a sessionless child). Use this helper directly only when the
Session-keyed map is exactly what you want and you don't need to
accept an override.

```ts
function getReadState(session: Session | undefined): ReadStateMap | undefined
```

**Parameters**

- `session` (`Session | undefined`): Session that owns the map.

**Returns** `ReadStateMap | undefined`

Source: [src/tools/read-state.ts:128](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L128)

### `hashContent`

Function.

64-bit content hash: two independent 32-bit FNV-1a-style lanes (different
offset bases + multipliers) concatenated as 16 hex chars. Fast,
non-cryptographic — but wide enough that the `requireReadBeforeEdit`
drift gate, which silently corrupts a file on a false "unchanged"
verdict, isn't betting on a 32-bit birthday bound (\~1 in 4 billion per
pair; uncomfortably reachable over many files × many sessions). Entries
live only in per-session in-memory maps, so widening needs no
stored-format migration. Still cheaper than allocating a Buffer and
pulling in `crypto`.

```ts
function hashContent(text: string): string
```

**Parameters**

- `text` (`string`): File content to hash.

**Returns** `string`

Source: [src/tools/read-state.ts:242](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L242)

### `hydrateReadStateFromSession`

Function.

Rebuild the per-session read-state map from durable session turns.

Durable replay runtimes may return recorded tool results without re-running
the tool body, so in-memory side effects like `readState.set(...)` can be
missing even though the transcript contains the successful `read_file`.
This helper is intentionally opt-in: live hosts keep the normal side-effect
behavior unless they call it themselves.

Hydration only fills missing keys. Live in-memory state may already reflect a
newer read or edit from the current run, and durable history cannot prove it
is fresher. Relative paths are resolved against the current execution cwd,
matching replay hosts that run sessions from a stable workspace.

```ts
function hydrateReadStateFromSession(
  session: Session | undefined,
  cwd: string,
  options?: HydrateReadStateOptions,
): number
```

**Parameters**

- `session` (`Session | undefined`): Session whose turns are replayed. Returns 0 when `undefined`.
- `cwd` (`string`): Working directory used to resolve relative paths.
- `options?` (`HydrateReadStateOptions`, default `{}`): Tool names, aliases and line-number default. See [`HydrateReadStateOptions`](https://zidane.si/docs/reference/tools.md#HydrateReadStateOptions).

**Returns** `number`: The number of entries added.

Source: [src/tools/read-state.ts:326](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L326)

### `hydrateReadStateFromSessionAsync`

Function.

Async replay hydrator for durable hosts that can cheaply read the current
full file through their execution context. Transcript-only reconstruction is
still preferred for full reads; partial/truncated read_file entries fall back
to `readFileForHash` and hash those full current bytes.

```ts
function hydrateReadStateFromSessionAsync(
  session: Session | undefined,
  cwd: string,
  options?: HydrateReadStateOptions,
): Promise<number>
```

**Parameters**

- `session` (`Session | undefined`): Session whose turns are replayed. Resolves to 0 when `undefined`.
- `cwd` (`string`): Working directory used to resolve relative paths.
- `options?` (`HydrateReadStateOptions`, default `{}`): Tool names, aliases and the `readFileForHash` fallback. See [`HydrateReadStateOptions`](https://zidane.si/docs/reference/tools.md#HydrateReadStateOptions).

**Returns** `Promise<number>`: The number of entries added.

Source: [src/tools/read-state.ts:398](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L398)

### `isSpawnTool`

Function.

Narrow a [`ToolDef`](https://zidane.si/docs/reference/tools.md#ToolDef) to a [`SpawnTool`](https://zidane.si/docs/reference/tools.md#SpawnTool) (i.e. it exposes `withSubagents`).
In its own module so callers can test a tool without loading the spawn tool.

```ts
function isSpawnTool(tool: ToolDef): tool is SpawnTool
```

**Parameters**

- `tool` (`ToolDef`): Tool to test.

**Returns** `tool is SpawnTool`

Source: [src/tools/is-spawn-tool.ts:9](https://github.com/Tahul/zidane/blob/main/src/tools/is-spawn-tool.ts#L9)

### `matchModelEntries`

Function.

Case-insensitive multi-term filter across id, display name, and
provider. Every whitespace-separated term must match somewhere
(mirrors the TUI picker's `filterModelCatalog` semantics). Empty /
whitespace queries pass everything through.

```ts
function matchModelEntries(
  entries: readonly SpawnModelEntry[],
  query: string,
): SpawnModelEntry[]
```

**Parameters**

- `entries` (`readonly SpawnModelEntry[]`): Catalog entries to filter.
- `query` (`string`): Space-separated terms. All must match.

**Returns** `SpawnModelEntry[]`

Source: [src/tools/model-search.ts:62](https://github.com/Tahul/zidane/blob/main/src/tools/model-search.ts#L62)

### `readStateKey`

Function.

Canonical read-state key for a `(cwd, path)` pair.

`ctx.execution.readFile(handle, path)` resolves the model-provided
path against `handle.cwd` before touching disk — so `src/App.tsx`,
`./src/App.tsx`, and `/abs/cwd/src/App.tsx` all read the **same**
bytes. The read-state map MUST mirror that resolution: without it,
a model that reads `src/App.tsx` and then edits `./src/App.tsx`
trips the `requireReadBeforeEdit` gate for a file it demonstrably
already saw (the gate's "has not been read in this session" message
fires on a stale key).

`node:path`'s `resolve(cwd, path)` short-circuits when `path` is
already absolute, so absolute and relative shapes converge on the
same canonical form. We don't `realpath()` symlinks — the file IS
the path the model addressed, not the realpath behind it; the host's
execution context decides how to dereference.

```ts
function readStateKey(cwd: string, path: string): string
```

**Parameters**

- `cwd` (`string`): Working directory to resolve against.
- `path` (`string`): File path, absolute or relative to `cwd`.

**Returns** `string`

Source: [src/tools/read-state.ts:226](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L226)

### `resolveReadStateMap`

Function.

Resolve the active read-state map from a tool context. An explicit
`ctx.readState` wins (used by `spawn`'s `shareReadState` opt-in to
forward the parent's map into a sessionless child); otherwise the
usual `Session`-keyed lookup applies.

Tools should call this helper instead of `getReadState(ctx.session)`
directly so the `shareReadState` plumbing is honored uniformly.

```ts
function resolveReadStateMap(
  ctx: { readState?: ReadStateMap; session?: Session },
): ReadStateMap | undefined
```

**Parameters**

- `ctx` (`{ readState?: ReadStateMap; session?: Session }`): Tool context. `readState` wins over the session map.

**Returns** `ReadStateMap | undefined`

Source: [src/tools/read-state.ts:149](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L149)

### `tailTruncate`

Function.

Keep the last `maxBytes` UTF-8 bytes of `text`, dropping the head and
prefixing a marker. A non-positive `maxBytes` disables truncation and
returns `text` unchanged.

`maxBytes` budgets the kept tail only; the marker is added on top and may
push the returned string slightly past `maxBytes`. That tradeoff is
intentional — folding the marker into the budget would shrink the content
actually shown. A leading run of U+FFFD (left by an upstream raw-byte slice
that landed mid-codepoint) is stripped from the kept tail.

```ts
function tailTruncate(
  text: string,
  maxBytes: number,
  options?: TailTruncateOptions,
): string
```

**Parameters**

- `text` (`string`): Text to truncate.
- `maxBytes` (`number`): UTF-8 bytes of tail to keep. Zero or less disables truncation.
- `options?` (`TailTruncateOptions`, default `{}`): Marker override. See [`TailTruncateOptions`](https://zidane.si/docs/reference/tools.md#TailTruncateOptions).

**Returns** `string`

Source: [src/tools/truncate.ts:110](https://github.com/Tahul/zidane/blob/main/src/tools/truncate.ts#L110)

### `validateToolArgs`

Function.

Validates a tool input against its JSON Schema and coerces values the model typed loosely, such as numeric strings. Items of arrays that cannot be coerced are dropped.

```ts
function validateToolArgs(
  input: Record<string, unknown>,
  schema: Record<string, unknown>,
): ValidationResult
```

**Parameters**

- `input` (`Record<string, unknown>`): Tool call input.
- `schema` (`Record<string, unknown>`): JSON Schema of the tool, with `properties` and `required`.

**Returns** `ValidationResult`

Source: [src/tools/validation.ts:131](https://github.com/Tahul/zidane/blob/main/src/tools/validation.ts#L131)

### `withIsolatedSpawnTool`

Function.

Give a preset its own spawn tool state (see `SpawnTool.isolated`).
Agent profiles are module constants holding ONE spawn instance, so a host
that builds agents from them repeatedly (one per session in the chat host;
one per activation / session switch in the TUI) must isolate at each
build, or every agent shares one slot pool and child-label counter.
Apply AFTER extension composition: its `withSubagents` rebuild keeps the
shared state.

```ts
function withIsolatedSpawnTool<P extends { tools?: Record<string, ToolDef> }>(
  preset: P,
  options?: Pick<SpawnToolOptions, 'maxConcurrent'>,
): P
```

**Type parameters**

- `P` (`extends { tools?: Record<string, ToolDef> }`)

**Parameters**

- `preset` (`P`): Preset that may hold a spawn tool under `tools.spawn`. Returned unchanged when it has none.
- `options?` (`Pick<SpawnToolOptions, 'maxConcurrent'>`): The isolated copy's cap (`maxConcurrent`, `Infinity` for none). Omitted, it keeps the preset's.

**Returns** `P`

Source: [src/tools/spawn.ts:444](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L444)

## Constants

### `DEFAULT_IMAGE_DIR`

Constant.

Folder for images saved without a `path`.

```ts
const DEFAULT_IMAGE_DIR = 'generated-images'
```

Source: [src/tools/generate-image.ts:38](https://github.com/Tahul/zidane/blob/main/src/tools/generate-image.ts#L38)

### `edit`

Constant.

Surgical edit — replace `old_string` with `new_string` in a single file.

Mirrors Claude Code's `Edit` semantics so models post-trained on Anthropic's
tool surface need no relearning. Fails clearly when `old_string` isn't unique
(unless `replace_all: true`) and when not found, with a nearest-match preview
so the model can recover without a separate `read_file` round-trip.

```ts
const edit: ToolDef
```

Source: [src/tools/edit.ts:22](https://github.com/Tahul/zidane/blob/main/src/tools/edit.ts#L22)

### `glob`

Constant.

The `glob` tool. Lists files in the execution context that match a glob pattern.

Delegates matching to `ExecutionContext.listFiles` with `{ glob:
true }`. This keeps filesystem semantics inside the execution context
(process / docker / sandbox) instead of reaching around it from the tool.

Results are capped at 1000 entries by default to keep model input bounded.
The cap applies after the directory filter, so dirs don't consume the
file budget.

Patterns are deliberately NOT confined to the workspace: like
`read_file`, `../`-prefixed patterns may match outside the cwd — the
execution context is the sandbox boundary, not this tool.

By default, contexts that can cheaply provide metadata return
`<path>\t<size>\t<mtime>` rows. Contexts without cheap metadata support return
paths only. Pass `metadata: false` to request plain newline-separated paths.

```ts
const glob: ToolDef
```

Source: [src/tools/glob.ts:48](https://github.com/Tahul/zidane/blob/main/src/tools/glob.ts#L48)

### `grep`

Constant.

The `grep` tool. Searches file contents for regular expressions, using ripgrep when it is available and a JavaScript fallback otherwise.

Wraps ripgrep (`rg`) when available; when it's not, falls back to a
regex search that enumerates files via the execution context's shell
(`find` over `exec`) and reads them via `ctx.execution.readFile`. Both
legs go through the `ToolContext.execution` seam, so the search
runs wherever the context's files actually live — in-process, a
container, or a remote sandbox — without the tool ever touching the host
filesystem directly.

The tool surface mirrors Claude Code's `Grep` so models authored against the
Anthropic tool surface need no relearning. Output modes:

- `files_with_matches` (default) — newline-separated paths.
- `content` — `path:line:match` (line numbers on by default).
- `count` — `path:N` per matching file.

Results are capped via `head_limit` (default 250) to keep model input
bounded; `offset` lets the caller page through. Pagination operates on
OUTPUT LINES, not match groups — in content mode, context lines and `--`
separators consume the budget too (documented in the schema).

A line cap is not enough on its own: ripgrep is line-oriented, so a minified
/ single-line file collapses to ONE match line that is the entire file, and
`head_limit` would happily return megabytes — enough to blow the model's
context window. Output is therefore ALSO byte-bounded: each line is clipped
to `MAX_LINE_BYTES` and the total to `OUTPUT_BYTE_CAP`, mirroring the byte
discipline `read_file` already enforces.

`path` is deliberately NOT confined to the workspace: like `read_file`,
absolute paths and `../` traversal are allowed — the execution context is
the sandbox boundary, not this tool.

```ts
const grep: ToolDef
```

Source: [src/tools/grep.ts:96](https://github.com/Tahul/zidane/blob/main/src/tools/grep.ts#L96)

### `listFiles`

Constant.

The `list_files` tool. Lists the entries of one directory, capped at 1000 entries.

```ts
const listFiles: ToolDef
```

Source: [src/tools/list-files.ts:17](https://github.com/Tahul/zidane/blob/main/src/tools/list-files.ts#L17)

### `multiEdit`

Constant.

The `multi_edit` tool. Applies a list of string replacements to one file in order, each on the output of the previous one.
Best-effort, per-hunk semantics.

Each step replaces `old_string` with `new_string` against the file
contents *as left by the previous applied edit*. A per-step failure
(`old_string` not found, ambiguous match without `replace_all`,
identical strings, malformed input) is recorded against THAT step
only — the remaining steps still run. The file is written iff at
least one step applied. The result text reports per-hunk outcomes
inline and (when any step failed) carries an
`<edit-outcomes>…</edit-outcomes>` annotation block so the chat layer

- replay can paint per-hunk badges via `parseEditOutcomesFromResult`.

Per-hunk approval is a TUI/host concern. When the user partially
denies a `multi_edit` from the file-edit modal, the gate handler
rebinds `ctx.input.edits` to the approved subset before this tool
runs — so the body sees a smaller all-approved batch and reports
outcomes keyed against THAT subset. The host's `tool:transform`
hook merges this subset-keyed result with the approval-side denied
entries (1:1 with the model's ORIGINAL `edits` list) before the
canonical `<edit-outcomes>` block lands on the wire — see
`mergeApprovalAndBodyOutcomes` in `chat/edit-approval.ts`.

Wire / replay format (see `parseEditOutcomesFromResult`):

```
Edited <path>: applied N of M edits (R replacements).
edit #2 failed: old_string not found in <path>.

<edit-outcomes>
#1 applied
#2 failed: old_string not found in <path>
#3 applied
</edit-outcomes>
```

The annotation is omitted entirely on the all-applied path — the
renderer's `isEditErrorResult` check then suppresses the paired
`tool-result` so the diff stands alone.

```ts
const multiEdit: ToolDef
```

Source: [src/tools/multi-edit.ts:85](https://github.com/Tahul/zidane/blob/main/src/tools/multi-edit.ts#L85)

### `readFile`

Constant.

The `read_file` tool. Reads one file, or several files and ranges in one call, from the execution context,
with line-based offset/limit and a hard byte cap.

Defaults are tuned for source code: 2000 lines / 256 KiB. A typical source
file, lockfile, or large config fits in one read; logs and very large
fixtures get truncated with a footer that documents how to fetch the
remainder.

Binary files are detected on the leading characters of the decoded text — if
the sample contains a NUL or a high proportion of U+FFFD replacement
characters, we return a marker so the model doesn't drown in mojibake.

```ts
const readFile: ToolDef
```

Source: [src/tools/read-file.ts:221](https://github.com/Tahul/zidane/blob/main/src/tools/read-file.ts#L221)

### `RECORD_ARTIFACT_TOOL`

Constant.

Name of the [`recordArtifact`](https://zidane.si/docs/reference/tools.md#recordArtifact) tool.

```ts
const RECORD_ARTIFACT_TOOL = 'record_artifact'
```

Source: [src/tools/record-artifact.ts:27](https://github.com/Tahul/zidane/blob/main/src/tools/record-artifact.ts#L27)

### `recordArtifact`

Constant.

The `record_artifact` tool. Records a pull request, branch, commit, file or custom artifact on the session.

```ts
const recordArtifact: ToolDef
```

Source: [src/tools/record-artifact.ts:32](https://github.com/Tahul/zidane/blob/main/src/tools/record-artifact.ts#L32)

### `shell`

Constant.

Default `shell` tool with background mode enabled.

Most hosts use this directly via `basicTools`. When the agent's
`behavior.tasksDir` is unset OR `behavior.disableBackgroundTasks:
true` is set, `createAgent` auto-rewrites this identity to a
`createShellTool({ allowBackground: false })` variant so the model
never sees a flag it can't use. Hosts who want to bypass that
auto-rewrite can register a `createShellTool({ allowBackground })`
directly — the rewrite only fires on identity-equal references to
this constant.

```ts
const shell: ToolDef
```

Source: [src/tools/shell.ts:392](https://github.com/Tahul/zidane/blob/main/src/tools/shell.ts#L392)

### `shellKill`

Constant.

The `shell_kill` tool. Sends SIGTERM to a background shell task by `task_id`.

```ts
const shellKill: ToolDef
```

Source: [src/tools/shell-kill.ts:23](https://github.com/Tahul/zidane/blob/main/src/tools/shell-kill.ts#L23)

### `WAIT_TASK_TIMED_OUT_PREFIX`

Constant.

Stable prefix on the timed-out result. The agent's notification
suppression keys on it: results carrying this prefix mean the task is
still running, so the pending `<task-notification>` must survive.

```ts
const WAIT_TASK_TIMED_OUT_PREFIX = 'wait_task: timed out'
```

Source: [src/tools/wait-task.ts:32](https://github.com/Tahul/zidane/blob/main/src/tools/wait-task.ts#L32)

### `waitTask`

Constant.

The `wait_task` tool. Waits for a background shell task to exit and returns its final status. On timeout the task keeps running.

```ts
const waitTask: ToolDef
```

Source: [src/tools/wait-task.ts:113](https://github.com/Tahul/zidane/blob/main/src/tools/wait-task.ts#L113)

### `writeFile`

Constant.

Write a file, with an idempotency signal when the content is unchanged.

Three return shapes — chosen so the model can recognize a no-op without a
separate read:

- `Created path (N bytes)` — file did not exist
- `Updated path (N bytes)` — content differed from on-disk
- `No change needed: path already at target state (N bytes)` — equal

Race window: in non-process execution contexts (docker, sandbox) shared by
multiple agents, another writer can mutate the file between our read and
our write. Local process context is single-writer per agent so the race is
a non-issue there. Documented rather than locked because the cost of
cross-context locking outweighs the cost of a stale "No change" message.

```ts
const writeFile: ToolDef
```

Source: [src/tools/write-file.ts:24](https://github.com/Tahul/zidane/blob/main/src/tools/write-file.ts#L24)

## Interfaces

### `ChildAgent`

Interface.

A running child agent started by the spawn tool.

```ts
interface ChildAgent
```

**Properties**

- `depth` (`number`): Subagent depth — 1 for a direct child of a top-level agent.
- `id` (`string`): Child label, such as `child-1`.
- `startedAt` (`number`): Start time as epoch milliseconds.
- `task` (`string`): Task the child was given.

Source: [src/tools/spawn.ts:64](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L64)

### `CreateShellToolOptions`

Interface.

Options for [`createShellTool`](https://zidane.si/docs/reference/tools.md#createShellTool).

```ts
interface CreateShellToolOptions
```

**Properties**

- `allowBackground?` (`boolean`): Whether to expose the `run_in_background` flag in the input schema +
  the background-mode paragraphs in the description. When `false`, the
  model never sees the flag and won't try to use it. The execute path
  still has a defensive fallback: an explicit `run_in_background: true`
  call (e.g. from a hand-crafted message) returns a clean error rather
  than silently running foreground.

  Default: `true`.
- `registeredCanonicals?` (`ReadonlySet<string>`): Canonical names of tools registered alongside `shell` on the same
  agent. When non-empty, the description gains a "prefer the dedicated
  tool" block for each known sibling (`read_file`, `glob`, `grep`,
  `list_files`, `edit`, `write_file`) — useful against the
  `ls`/`cat`-to-re-verify loop some models fall into when both a
  dedicated tool AND `shell` are visible. Unknown / unrecognized names
  are ignored.

  Set by `createAgent` per-run from the tool registry; hosts that
  construct a `shell` directly can pass it explicitly. Omit to suppress
  the block entirely (no nudge for shell-only agents, no nudge for
  hosts that prefer to author their own anti-loop prose).
- `toolAliases?` (`Record<string, string>`): The agent's `toolAliases` map, used to render the wire-level name of
  each sibling in the swap block. Without this, the block always prints
  canonical names — fine for the default preset, wrong for hosts that
  alias-rename (the model would be told to call a name it doesn't see
  in the tool spec).

Source: [src/tools/shell.ts:86](https://github.com/Tahul/zidane/blob/main/src/tools/shell.ts#L86)

### `ExtensionSubagentDef`

Interface.

A subagent def carried by an extension's `registerSubagent` — same as
[`SubagentDef`](https://zidane.si/docs/reference/tools.md#SubagentDef) but carrying its own dispatch `name` (the registry
key). Self-describing so the def survives the trip from extension
`setup` through the merged [`ExtensionRegistry`](https://zidane.si/docs/reference/extensions.md#ExtensionRegistry) to the host's
`SpawnTool.withSubagents` merge.

```ts
interface ExtensionSubagentDef extends SubagentDef
```

**Properties**

- `description?` (`string`, from `SubagentDef`): Short description rendered into the spawn tool's schema (the
  `subagent_type` field's description) so the model can pick a type
  that matches the task without round-tripping through docs.
- `model?` (`string`, from `SubagentDef`): Default model for this subagent type (e.g. an Explore preset pinned
  to a fast, cheap model). Precedence: per-call `input.model` >
  this > `SpawnToolOptions.model` > inherit the parent run's model.
  When a catalog is configured and lists this id, the child also
  routes to the provider that serves it.
- `name` (`string`): Registry key the model emits as `subagent_type`.
- `readonly?` (`boolean`, from `SubagentDef`): Mark this subagent as read-only — equivalent to listing only
  obviously-non-mutating tools (`read_file`, `grep`, `glob`,
  `list_files`) in `SubagentDef.tools`. Convenience for the
  common "Plan" / "Explore" subagent shape Claude Code ships.
  Read-only children inherit no MCP tools (unknown mutation risk).

  When both `readonly: true` and `tools` are set, `tools` wins
  (explicit beats implicit).
- `system?` (`string`, from `SubagentDef`): System prompt override for this subagent type. Per-call
  `input.system` still wins — model-supplied specialization beats
  preset defaults.
- `thinking?` (`'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`, from `SubagentDef`): Default thinking level for this subagent type. Precedence: this >
  `SpawnToolOptions.thinking` > provider default.
- `tools?` (`readonly string[]`, from `SubagentDef`): Restrict the child agent's tool registry to this list of canonical
  tool names. Operates as a filter over the parent's tools (the
  parent's selection is the upper bound — a subagent can never gain
  tools the parent doesn't have). When unset, the child inherits the
  parent's full tool list.

  Tool names are canonical (registry-key) not wire/alias names — the
  filter runs before aliases are applied. Names that don't match a
  parent tool are silently dropped (matches the lenient behaviour of
  `enabledTools` on MCP configs).

  MCP tools (namespaced `mcp_<server>_<tool>` names) can be listed too:
  they inherit through the parent's shared live connection, filtered to
  exactly the listed names — see `inheritedMcpForSubagent`. An explicit
  list (or `readonly`) with no MCP names gives the child no MCP tools.

Source: [src/tools/spawn.ts:322](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L322)

### `GenerateImageToolOptions`

Interface.

Options for [`createGenerateImageTool`](https://zidane.si/docs/reference/tools.md#createGenerateImageTool).

```ts
interface GenerateImageToolOptions
```

**Properties**

- `defaultModel?` (`string`): Model used when a call names none, as `<provider>/<model id>`. Default: the first source's default model.
- `sources?` (`readonly ImageModelSource[]`): Providers to generate with. Omitted: the run's providers that have an
  available image endpoint (`ToolContext.providers`, else `provider`).

Source: [src/tools/generate-image.ts:57](https://github.com/Tahul/zidane/blob/main/src/tools/generate-image.ts#L57)

### `HydrateReadStateOptions`

Interface.

Options for [`hydrateReadStateFromSession`](https://zidane.si/docs/reference/tools.md#hydrateReadStateFromSession) and [`hydrateReadStateFromSessionAsync`](https://zidane.si/docs/reference/tools.md#hydrateReadStateFromSessionAsync).

```ts
interface HydrateReadStateOptions
```

**Properties**

- `aliasMaps?` (`AliasMaps`): Tool-name aliases in effect for the transcript being replayed. zidane's
  loop canonicalizes tool-call names before persisting, so durable turns
  normally already carry canonical names — but a transcript captured from a
  host that persists WIRE names (the aliased form the model saw, e.g.
  `Read` / `Edit`), or one imported from another stack, would otherwise be
  unrecognized and hydrate nothing. When provided, each tool-call name is
  resolved to canonical (`toCanonicalName`) before matching, so both the
  canonical and the aliased shape are honored. No-op for already-canonical
  names.
- `defaultLineNumbers?` (`boolean`): Agent-level default for omitted `read_file.lineNumbers`.
- `editToolName?` (`string`): Canonical edit tool name. Defaults to `edit`.
- `multiEditToolName?` (`string`): Canonical multi-edit tool name. Defaults to `multi_edit`.
- `readFileForHash?` (`(path: string) => Promise<string | null | undefined>`): Optional replay-only fallback for read_file results whose transcript output
  is partial/truncated. Receives the model-provided path and should return
  the current full text bytes to hash exactly like live read_file.
- `readFileToolName?` (`string`): Canonical read-file tool name. Defaults to `read_file`.
- `turns?` (`readonly SessionTurn[]`): Turns to replay instead of `session.turns`: what the model sees of the session (after compaction), when only those reads count.
- `writeFileToolName?` (`string`): Canonical write-file tool name. Defaults to `write_file`.

Source: [src/tools/read-state.ts:254](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L254)

### `ImageModelSource`

Interface.

A provider the tool can generate with.

```ts
interface ImageModelSource
```

**Properties**

- `images` (`ProviderImages`): The provider's image endpoint.
- `label?` (`string`): Display name of the provider, for the model list.
- `provider` (`string`): Provider name as the `model` argument spells it: `<provider>/<model id>`.

Source: [src/tools/generate-image.ts:47](https://github.com/Tahul/zidane/blob/main/src/tools/generate-image.ts#L47)

### `InteractionToolOptions`

Interface.

Options for [`createInteractionTool`](https://zidane.si/docs/reference/tools.md#createInteractionTool).

```ts
interface InteractionToolOptions
```

**Properties**

- `description?` (`string`): Tool description shown to the model
- `name?` (`string`): Tool name (default: 'interaction')
- `onRequest` (`(payload: Record<string, unknown>, ctx: ToolContext) => Promise<string | Record<string, unknown>>`): Called when the model invokes this tool. Receives the validated payload and tool context, returns data for the model.
- `schema` (`Record<string, unknown>`): JSON Schema for the request payload the model sends

Source: [src/tools/interaction.ts:29](https://github.com/Tahul/zidane/blob/main/src/tools/interaction.ts#L29)

### `LazyToolEntry`

Interface.

A tool the model can discover through `tool_search` and unlock on demand.

```ts
interface LazyToolEntry
```

**Properties**

- `canonicalName` (`string`): Canonical (registry-key) name used for unlock-set membership and for the
  loop's `ctx.tools[name]` dispatch lookup. Equal to `name` when no alias
  is configured for this tool.
- `description` (`string`): Tool description matched by the search.
- `inputSchema` (`Record<string, unknown>`): JSON Schema of the tool input, returned inline when the tool is unlocked.
- `name` (`string`): Wire name (after `toolAliases` rewrite). What the model sees in the
  catalog, what `tool_search` matches against, and what the provider's
  tool list will carry once the entry is unlocked.
- `server?` (`string`): Source MCP server, when applicable. Used for `server`-bulk unlock.

Source: [src/tools/tool-search.ts:27](https://github.com/Tahul/zidane/blob/main/src/tools/tool-search.ts#L27)

### `ReadStateEntry`

Interface.

What the last `read_file` of a file returned, kept to deduplicate re-reads and to detect changes before an edit.

```ts
interface ReadStateEntry
```

**Properties**

- `contentHash` (`string`): Hash of the file content, from [`hashContent`](https://zidane.si/docs/reference/tools.md#hashContent).
- `elided?` (`true`): Set when the on-wire read output this entry tracks can no longer be
  trusted as-is, so `read_file`'s dedup must MISS on the next identical
  re-read — the "unchanged since the previous read" stub would otherwise
  point at content the model can no longer see, or that is no longer true.

  Two writers:

  - the wire transforms (stale-read elision, tail compaction), which REMOVE
    the output from the on-wire history;
  - the mutating tools (`edit` / `multi_edit` / `write_file`), which refresh
    `contentHash` to the post-mutation bytes — without the flag that refresh
    would make the very next re-read hash-match and dedup would claim the
    PRE-mutation body is "still current". Marking at the mutation keeps
    dedup honest in every `elideStaleReads` mode, including off.

  The entry stays registered either way — `requireReadBeforeEdit` must keep
  passing (the contentHash still guards against disk drift; un-registering
  here is what degraded models into shell-edit one-liner workarounds).
  Cleared by the next fresh read (`rememberRead` writes a flag-less entry).
- `limit` (`number`): Line limit of the last read.
- `lineNumbers?` (`boolean`): Whether the prior read emitted line-number prefixes. Tracked so a
  read with `lineNumbers: true` doesn't dedup against one with
  `lineNumbers: false` (or vice versa) — the dedup stub would mislead
  the model about the prior output's shape.
- `maxBytes` (`number`): Byte cap of the last read.
- `mtimeMs` (`number`): Wall-clock at last read — diagnostic only, not used for invalidation.
- `offset` (`number`): Slice parameters for the last read.
- `staleRejections?` (`number`): Consecutive `requireReadBeforeEdit` drift refusals on this file since
  the last fresh read. Fuel for `staleRefusalHint`: one refusal is
  routine (the model edited after an out-of-band mutation and just needs
  to re-read), but repeated refusals mean re-reading isn't fixing it —
  something OUTSIDE the session (editor auto-save, formatter, codegen
  watcher) keeps rewriting the file, and the error message must say so
  instead of repeating advice the model already followed. That repetition
  is exactly what teaches models to abandon `edit` for raw shell writes
  that bypass every guard.

  Cleared by any fresh read (`rememberRead` writes a flag-less entry) and
  by any successful edit (the mutation-site refresh drops it).

Source: [src/tools/read-state.ts:55](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L55)

### `SkillsReadToolOptions`

Interface.

Options for [`createSkillsReadTool`](https://zidane.si/docs/reference/tools.md#createSkillsReadTool).

```ts
interface SkillsReadToolOptions
```

**Properties**

- `catalog` (`readonly SkillConfig[]`): Resolved skills the model may read from.
- `state` (`SkillActivationState`): Activation state. Only active skills can be read.

Source: [src/tools/skills-read.ts:19](https://github.com/Tahul/zidane/blob/main/src/tools/skills-read.ts#L19)

### `SkillsRunScriptToolOptions`

Interface.

Options for [`createSkillsRunScriptTool`](https://zidane.si/docs/reference/tools.md#createSkillsRunScriptTool).

```ts
interface SkillsRunScriptToolOptions
```

**Properties**

- `catalog` (`readonly SkillConfig[]`): Resolved skills whose scripts the model may run.
- `scriptTimeoutMs?` (`number`): Script timeout in milliseconds. Default 60000.
- `state` (`SkillActivationState`): Activation state. Only active skills can run scripts.

Source: [src/tools/skills-run-script.ts:19](https://github.com/Tahul/zidane/blob/main/src/tools/skills-run-script.ts#L19)

### `SkillsUseToolOptions`

Interface.

Options for [`createSkillsUseTool`](https://zidane.si/docs/reference/tools.md#createSkillsUseTool).

```ts
interface SkillsUseToolOptions
```

**Properties**

- `allowShellInterpolation?` (`boolean`): Execute `!\`cmd\``shell interpolation on activation. Mirrors`SkillsConfig.allowShellInterpolation`. Default `true`. Interpolation
  runs arbitrary shell from (possibly third-party) skill content, outside
  the allowed-tools gate — set `false`for locked-down hosts. When off,
  patterns are replaced with a`\[shell interpolation disabled]\`
  placeholder.
- `approveShellInterpolation?` (`(request: ShellInterpolationApproval) => boolean | Promise<boolean>`): Optional approval gate for `!\`cmd\``shell interpolation. Invoked once
  per (skill, command) the first time a skill with embedded shell is
  activated, BEFORE the command runs. Return`false`to skip that command
  (it is replaced with a`\[shell interpolation not approved]\` placeholder).
  When omitted, interpolation runs without prompting. Hosts
  (TUI/GUI) can wire this to an interactive confirmation.
- `catalog` (`readonly SkillConfig[]`): Resolved skills catalog for this run.
- `hooks` (`Hookable<AgentHooks>`): Agent hooks — used to fire `skills:activate` on first activation.
- `state` (`SkillActivationState`): Per-agent activation state the tool mutates.

Source: [src/tools/skills-use.ts:34](https://github.com/Tahul/zidane/blob/main/src/tools/skills-use.ts#L34)

### `SpawnModelEntry`

Interface.

A [`ModelInfo`](https://zidane.si/docs/reference/chat.md#ModelInfo) paired with the runtime `Provider.name` that serves
it. `provider` must match a `Provider.name` available to the run (the
primary or a fallback-chain provider — see `ToolContext.providers`);
the spawn tool routes the child agent to that instance.

```ts
interface SpawnModelEntry
```

**Properties**

- `model` (`ModelInfo`): Catalog entry for the model.
- `provider` (`string`): Name of the runtime provider that serves the model.

Source: [src/tools/model-search.ts:26](https://github.com/Tahul/zidane/blob/main/src/tools/model-search.ts#L26)

### `SpawnTaskInput`

Interface.

Input accepted by `SpawnTool.runTask` — the typed mirror of the
`execute` schema. Fields beyond `task` only take effect when the tool
was built with the matching capability (a subagent registry for
`subagent_type`, a model catalog for `model`).

```ts
interface SpawnTaskInput
```

**Properties**

- `model?` (`string`): Catalog model id to pin the child to. Unknown ids reject the task.
- `resume?` (`string`): Child label (`child-2`) or raw run id of a prior stopped/failed child
  to resume. The new child is seeded with that run's persisted turns and
  `task` becomes the continuation instruction. Requires the tool to be
  built with `persist: true` and the parent to have a session.
- `subagent_type?` (`string`): Registered subagent type. Unknown types degrade to `general-purpose`.
- `system?` (`string`): System prompt override for the child. Wins over a subagent preset's system.
- `task` (`string`): Task prompt for the child agent.

Source: [src/tools/spawn.ts:392](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L392)

### `SpawnTaskOutcome`

Interface.

Structured outcome of one spawned child run — the host-facing shape
behind `SpawnTool.runTask`. `message` is the exact model-facing
string `execute` returns (header + stats + response), while `text` is
the child's bare final response so hosts don't have to parse the
formatted message.

```ts
interface SpawnTaskOutcome
```

**Properties**

- `id?` (`string`): Child label (`child-1`, …). Absent when no child slot was reserved (rejections, pre-aborted parent).
- `message` (`string`): Model-facing formatted result — exactly what `execute` returns.
- `stats?` (`AgentStats`): Cumulative child stats (subtree-wide). Absent on rejection.
- `status` (`'error' | 'completed' | 'aborted' | 'timeout' | 'rejected'`): Terminal status. `'rejected'` = refused before any child ran
  (unknown model, depth/concurrency cap, unavailable provider).
- `text` (`string`): The child's final assistant text. Empty unless `status === 'completed'`.

Source: [src/tools/spawn.ts:417](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L417)

### `SpawnTool`

Interface.

The object [`createSpawnTool`](https://zidane.si/docs/reference/tools.md#createSpawnTool) returns: a [`ToolDef`](https://zidane.si/docs/reference/tools.md#ToolDef) that also
exposes live [`SpawnToolState`](https://zidane.si/docs/reference/tools.md#SpawnToolState) plus `SpawnTool.withSubagents`
for rebuilding the tool against a merged subagent registry (the seam the
extension system uses to add / edit spawnable subagents).

```ts
interface SpawnTool extends ToolDef, SpawnToolState
```

**Properties**

- `callsNestedTools?` (`boolean`, deprecated, from `ToolDef`): Whether this tool calls other tools through `ToolContext.callTool`.

  **Deprecated.** Shipped in v8.2.5; use `DurableToolDef.callsNestedTools` (`zidane/durable`). Leaves `ToolDef` in the next release.
- `children` (`ReadonlyMap<string, ChildAgent>`, readonly, from `SpawnToolState`): Currently running children.
- `execute` (`(input: Record<string, unknown>, ctx: ToolContext) => Promise<string | ToolResultContent[]>`, from `ToolDef`): Execute the tool and return its output.

  Return a plain string for text-only tools (the common case). Return a
  `ToolResultContent[]` when the tool produces non-text content (images, mixed
  text+image) that the provider can route through natively (Anthropic
  `tool_result.content` arrays, OpenAI Codex pi-ai) or through the
  companion-user-message fallback (OpenAI Chat Completions).
- `isConcurrencySafe?` (`boolean | ((input: Record<string, unknown>) => boolean)`, from `ToolDef`): Whether this tool is safe to dispatch in parallel with siblings inside
  one assistant turn. The scheduler treats safe tools as
  fan-out-eligible: a fleet of concurrent-safe tools runs in parallel
  (capped by `behavior.maxConcurrentTools`). An unsafe tool acts as a
  **barrier** — it waits for any in-flight fleet to drain, then runs
  alone, then unblocks the queue. Results are yielded back to the model
  in submission order regardless of completion order.

  Forms:

  - `true` / `false` — static decision.
  - `(input) => boolean` — input-aware (e.g. `shell` inspects the
    command for write side-effects so read-only commands fan out and
    mutating commands barrier).

  **Defaults to `false`.** Fail-closed: any tool that doesn't
  explicitly opt in serializes. Third-party MCP tools and any custom
  tool authored without an annotation will not parallelize.

  Side-effect-free read-only tools (read_file, glob, grep, list_files,
  todoread, tool_search, …) should set `true`. Tools that touch shared
  state (edit, multi_edit, write_file, todowrite, interaction, …)
  should leave it unset.
- `isolated` (`(options?: Pick<SpawnToolOptions, 'maxConcurrent'>) => SpawnTool`): Return a FRESH spawn tool with the same options but its OWN state:
  concurrency slots, cumulative stats, child-label counter. Hosts that
  build many agents from one shared preset (the daemon / GUI chat host,
  one agent per session) call it per agent build — otherwise every
  session shares one slot pool and one session's fan-out queues behind
  (or, for a subagent's spawns, is refused by) another session's children.
  `options.maxConcurrent` replaces the copy's cap, for a host that takes
  it from a setting.
- `maxConcurrent` (`number`, readonly): The tool's simultaneous-children cap (`createSpawnTool({ maxConcurrent })`,
  default 3; `Infinity` when uncapped). Hosts driving `runTask`
  directly size their worker pools to this, so an admitted task starts at
  once instead of queuing.
- `replay?` (`'safe' | 'unsafe'`, from `ToolDef`): What a durable run does with a call of this tool that a process death
  left unfinished. `'safe'`: run it again (reads with no side effect).
  `'unsafe'` (default): report it to the model as interrupted, since its
  effect may or may not have happened.
- `runsNestedAgent?` (`boolean`, deprecated, from `ToolDef`): Whether this tool runs a full nested agent loop inside its `execute`.

  **Deprecated.** Shipped in v8.2.5; use `DurableToolDef.runsNestedAgent` (`zidane/durable`). Leaves `ToolDef` in the next release.
- `runTask` (`(input: SpawnTaskInput, ctx: ToolContext) => Promise<SpawnTaskOutcome>`): Host-facing structured variant of `execute` — same child lifecycle
  (model routing, hook bubbling, session persistence, concurrency
  slots), but returns a [`SpawnTaskOutcome`](https://zidane.si/docs/reference/tools.md#SpawnTaskOutcome) instead of the
  model-facing string, so hosts (e.g. command `ctx.spawnAgents`) can
  branch on `status` and read the bare `text` without string parsing.
  `execute` is a thin formatter over this.
- `spec` (`ToolSpec`, from `ToolDef`): Name, description and input schema sent to the provider.
- `totalChildStats` (`Readonly<AgentStats>`, readonly, from `SpawnToolState`): Cumulative stats across every completed direct child of this spawn-tool
  instance (returns a copy). Each child's contribution is the cumulative
  `AgentStats` returned by its `agent.run()` — so
  `totalIn`/`totalOut`/`totalCacheRead`/`totalCacheCreation` cover the
  entire subtree (children + grandchildren + …), while `turns` and
  `elapsed` stay parent-loop-only per child and are summed across direct
  children. `elapsed` over-counts when children ran in parallel.

  Lives across multiple parent runs that share this instance.
- `withModels` (`(models: readonly SpawnModelEntry[]) => SpawnTool`): Return a FRESH spawn tool with the given model catalog (the `model`
  schema field + validation — see `SpawnToolOptions.models`),
  sharing this instance's counters, stats, and concurrency slots.
  Hosts call it at agent-build time when the available models are only
  known then (e.g. the TUI's provider chain).
- `withSubagents` (`(extra: SubagentRegistry, overrides?: Record<string, SubagentOverride>) => SpawnTool`): Return a FRESH spawn tool whose `subagent_type` enum is recomputed
  from the base registry merged with `extra` (adds), then `overrides`
  (edits, applied last — an override returning `undefined` deletes the
  entry). Precedence: host base < adds < overrides. The rebuilt
  instance shares this one's concurrency / stats / child-label state.

Source: [src/tools/spawn.ts:340](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L340)

### `SpawnToolOptions`

Interface.

Options for [`createSpawnTool`](https://zidane.si/docs/reference/tools.md#createSpawnTool).

```ts
interface SpawnToolOptions
```

**Properties**

- `forwardHooks?` (`boolean`): Forward a curated subset of child hook events (`stream:*`, `tool:*`,
  `turn:after`) onto the parent's hook bus as `child:*` events. Default:
  `true`. Grandchildren bubble through their child transparently.
- `maxConcurrent?` (`number`): Maximum concurrent sub-agents (default: 3); `Infinity` removes the cap.
  Past it, the top-level agent's spawn calls wait for a slot; a subagent's
  are refused. One turn still runs at most `behavior.maxConcurrentTools`
  tool calls at once (default 10), spawns included.
- `maxDepth?` (`number`): Maximum subagent depth. 0 disables spawning entirely; 1 allows top-level
  spawns but forbids grandchildren; 3 (default) allows three levels of
  recursion — enough for most orchestration patterns, a sharp ceiling
  against runaway loops.
- `model?` (`string`): Child model override. When unset, the child inherits the parent run's
  resolved model (`ToolContext.model`), falling back to
  `provider.meta.defaultModel` only when the parent didn't expose one.
- `models?` (`readonly SpawnModelEntry[]`): Catalog of models children may run on. When set, the spawn input
  schema gains an optional `model` field so the CALLING MODEL can
  cost-tier its delegation (mechanical / high-volume work on cheaper
  models, synthesis on the primary). Reuses the chat layer's
  `ModelInfo` objects verbatim — real `cost` pricing and context
  windows, no separate catalog to maintain — paired with the runtime
  `Provider.name` that serves each model (see [`SpawnModelEntry`](https://zidane.si/docs/reference/tools.md#SpawnModelEntry)).

  - ≤ `EMBEDDED_CATALOG_MAX` entries → the list is embedded in
    the field description (id · price · context) so no extra round-trip
    and no system-prompt bloat.
  - Larger → the description points at the `model_search` tool
    (register [`createModelSearchTool`](https://zidane.si/docs/reference/tools.md#createModelSearchTool) with the same entries).
  - Model-supplied ids are validated: unknown ids are rejected with
    the closest matches so the model can self-correct; ids served by
    a provider not present in the run are rejected with the reason.

  Default: unset — no `model` field, children always inherit.
- `onComplete?` (`(child: ChildAgent, stats: AgentStats, status: NonNullable<'error' | 'completed' | 'aborted' | 'timeout' | undefined>) => void`): Called when a child agent completes (success, abort, timeout, or error).
- `onSpawn?` (`(child: ChildAgent) => void`): Called when a child agent starts.
- `persist?` (`boolean`): When `true` and the parent has a session, the child reuses the parent's
  session — child turns are appended with the child's own `runId`, and the
  resulting `SessionRun` carries `parentRunId` so the tree is
  reconstructible. Default: `false` (child is in-memory only).

  **Read-state isolation.** Sharing the session also shares the
  `read_file` / `requireReadBeforeEdit` tracking map (it's keyed
  by `Session`). With `persist: false` the child gets no session,
  so reads inside the subagent populate nothing the parent can see —
  a follow-up `edit` / `multi_edit` in the parent will trip the
  gate with `"has not been read"` even though the model just
  read the file in the child. Use `shareReadState` when
  you want the parent's gate to honor the child's reads WITHOUT
  also persisting child turns to the parent's session.
- `preset?` (`Preset`): Preset override for children. Shallow-merged over the parent's preset (parent fields still win for anything left unset).
- `shareHandle?` (`boolean`): Run children on the parent's execution handle instead of letting each
  child mint (and destroy) its own. Default: `false` — an isolated handle
  per child.

  Why you'd turn it on: for a remote context (sandbox / container), a child
  calling `execution.spawn()` is a real allocation. Under a durable runtime
  that allocation is NOT a recorded side effect, so a replayed attempt
  mints a FRESH sandbox and orphans the first — one leak per child per
  attempt. It also removes a per-child spawn/destroy round-trip on the hot
  path.

  The trade: children see the parent's filesystem and background-task
  namespace directly, so an isolated scratch space per child is gone. The
  child never destroys a shared handle (that's `AgentOptions.handle`
  semantics — host owns the lifecycle), and teardown skips background-task
  reassignment entirely since there is no child handle to promote from.

  No effect when the parent has no handle (`ctx.handle` absent).
- `shareReadState?` (`boolean`): Forward the parent's read-state map to the child agent so the
  `requireReadBeforeEdit` gate and `dedupReads` cache see reads
  across the parent/child boundary. Orthogonal to `persist` —
  use this when you want shared read tracking without sharing the
  session's turn history. Default: `false`.

  Has no effect when the parent has no read-state to share (no
  session and no explicit `readState` on the parent agent's
  options). Implementation: passes the parent's resolved
  `ReadStateMap` to the child via `AgentOptions.readState`, which
  tools resolve via `ctx.readState ?? getReadState(ctx.session)`.
- `subagents?` (`SubagentRegistry`): Named subagent presets the model can select via the `subagent_type`
  input field. Mirrors the Claude Code SDK's surface — models trained
  on it routinely emit `subagent_type: 'Explore' | 'Plan' |
  'Verification' | 'general-purpose'`, and without a registry the
  field is silently dropped, so hosts wanting type-specialized
  subagents have to invent their own dispatch layer.

  Each entry overlays the base spawn config for that particular
  dispatch. Per-call `input.system` still wins over `subagents[type].system`
  so the model can always specialize further.

  When the registry is non-empty:

  - `subagent_type` appears in the spawn input schema as a `string`
    enum of the registered keys (plus the always-available
    `'general-purpose'` fallback).
  - Models that pass an unregistered type are routed to
    `'general-purpose'` (no error — degrade gracefully so trained
    models keep working even on hosts that haven't wired every
    type Claude Code uses).

  When the registry is empty / unset, the field is omitted entirely.

  Default: `undefined` (no subagent types; `subagent_type` schema
  field hidden).
- `system?` (`string`): Child system prompt override. Per-spawn `input.system` takes precedence.
- `thinking?` (`'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`): Child thinking level.
- `timeoutMs?` (`number`): Per-child timeout, in milliseconds. When the child exceeds it the spawn
  tool returns a timeout marker, fires `spawn:error`, and destroys the
  child agent. Default: none.

Source: [src/tools/spawn.ts:98](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L98)

### `SpawnToolState`

Interface.

Read-only view of the children a spawn tool instance has run.

```ts
interface SpawnToolState
```

**Properties**

- `children` (`ReadonlyMap<string, ChildAgent>`, readonly): Currently running children.
- `totalChildStats` (`Readonly<AgentStats>`, readonly): Cumulative stats across every completed direct child of this spawn-tool
  instance (returns a copy). Each child's contribution is the cumulative
  `AgentStats` returned by its `agent.run()` — so
  `totalIn`/`totalOut`/`totalCacheRead`/`totalCacheCreation` cover the
  entire subtree (children + grandchildren + …), while `turns` and
  `elapsed` stay parent-loop-only per child and are summed across direct
  children. `elapsed` over-counts when children ran in parallel.

  Lives across multiple parent runs that share this instance.

Source: [src/tools/spawn.ts:76](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L76)

### `SubagentDef`

Interface.

Per-type subagent override applied when the model calls
`spawn({ subagent_type: '…' })`. All fields are optional; absent
fields fall back to the parent's resolved configuration (see
[`SpawnToolOptions`](https://zidane.si/docs/reference/tools.md#SpawnToolOptions) comments for the merge order).

```ts
interface SubagentDef
```

**Properties**

- `description?` (`string`): Short description rendered into the spawn tool's schema (the
  `subagent_type` field's description) so the model can pick a type
  that matches the task without round-tripping through docs.
- `model?` (`string`): Default model for this subagent type (e.g. an Explore preset pinned
  to a fast, cheap model). Precedence: per-call `input.model` >
  this > `SpawnToolOptions.model` > inherit the parent run's model.
  When a catalog is configured and lists this id, the child also
  routes to the provider that serves it.
- `readonly?` (`boolean`): Mark this subagent as read-only — equivalent to listing only
  obviously-non-mutating tools (`read_file`, `grep`, `glob`,
  `list_files`) in `SubagentDef.tools`. Convenience for the
  common "Plan" / "Explore" subagent shape Claude Code ships.
  Read-only children inherit no MCP tools (unknown mutation risk).

  When both `readonly: true` and `tools` are set, `tools` wins
  (explicit beats implicit).
- `system?` (`string`): System prompt override for this subagent type. Per-call
  `input.system` still wins — model-supplied specialization beats
  preset defaults.
- `thinking?` (`'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`): Default thinking level for this subagent type. Precedence: this >
  `SpawnToolOptions.thinking` > provider default.
- `tools?` (`readonly string[]`): Restrict the child agent's tool registry to this list of canonical
  tool names. Operates as a filter over the parent's tools (the
  parent's selection is the upper bound — a subagent can never gain
  tools the parent doesn't have). When unset, the child inherits the
  parent's full tool list.

  Tool names are canonical (registry-key) not wire/alias names — the
  filter runs before aliases are applied. Names that don't match a
  parent tool are silently dropped (matches the lenient behaviour of
  `enabledTools` on MCP configs).

  MCP tools (namespaced `mcp_<server>_<tool>` names) can be listed too:
  they inherit through the parent's shared live connection, filtered to
  exactly the listed names — see `inheritedMcpForSubagent`. An explicit
  list (or `readonly`) with no MCP names gives the child no MCP tools.

Source: [src/tools/spawn.ts:249](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L249)

### `TailTruncateOptions`

Interface.

Options for [`tailTruncate`](https://zidane.si/docs/reference/tools.md#tailTruncate).

```ts
interface TailTruncateOptions
```

**Properties**

- `marker?` (`(droppedBytes: number) => string`): Build the marker inserted before the kept tail, given the number of
  UTF-8 bytes dropped from the head. Defaults to
  `…(<n> bytes truncated from head)…\n`. Return an empty string to keep
  the tail with no marker at all.

Source: [src/tools/truncate.ts:17](https://github.com/Tahul/zidane/blob/main/src/tools/truncate.ts#L17)

### `ToolContext`

Interface.

Runtime context passed to every tool execution.
Provides access to the agent's provider, abort signal, execution environment, and hooks.

The preset-y fields (`name`, `system`, `tools`, `toolAliases`, `toolDescriptions`,
`mcpServers`, `skills`, `behavior`) mirror the agent's own options so child-spawning
tools can inherit them.

```ts
interface ToolContext
```

**Properties**

- `behavior?` (`AgentBehavior`): Behavior defaults (for child inheritance)
- `callId` (`string`): Tool call ID from the model
- `callTool?` (`(name: string, input: Record<string, unknown>, options?: NestedToolCallOptions) => Promise<NestedToolCallResult>`): Call another tool of this run from inside this tool, through the same
  gate / validation / hooks pipeline as a model-issued call (see
  `src/loop-nested-calls.ts`). Only present on calls the model issued
  directly: nesting is one level deep. The nested result does not enter
  the session; this tool decides what the model sees.
- `clock?` (`AgentClock`): Time + UUID source resolved for the owning run. Tools that mint ids
  or stamp timestamps that may land in persisted state should prefer
  this over `Date.now()` / `crypto.randomUUID()` so durable-execution
  adapters can provide replay-stable values. Optional on
  the type so hand-built contexts (tests, direct invocations) don't
  have to synthesize one — callers may fall back to the native APIs.
- `depth?` (`number`): Subagent depth for the agent owning this tool call. 0 = top-level,
  1 = first-level child, … Used by spawn to enforce a `maxDepth` cap.
  Undefined is treated as 0 by spawn.
- `durable?` (`Pick<RunScope, 'runId' | 'awakeable'> & { readonly checkpoint?: boolean; readonly mode: DurableMode; readonly runtime: DurableRuntime }`): The owning run's durable scope: tools that park on a human or external
  answer (`ask_user`) mint an awakeable here, keyed by a step key of their
  call, so a resumed run finds it again. `mode` is the runtime's: replay-only
  logic of a tool (no wall-clock branches) runs only in `replay` mode.
  `runtime` is the run's runtime: a nested agent (`spawn`) of a replaying
  run records its steps on it too. Set by the agent loop. Absent outside a
  loop, and inside the body of a tool that runs as a step, in checkpoint and
  replay mode alike (an awakeable or step opened there would nest in the
  step's record). So a tool that parks here sets `durableBody: 'composite'`
  (`DurableToolDef`). `checkpoint`: the run keeps step checkpoints
  (`Agent.resume` can continue it); a nested agent of such a run
  checkpoints its own run on `runtime` too.
- `execution` (`ExecutionContext`): The execution context (shell, filesystem, etc.)
- `handle` (`ExecutionHandle`): The active execution handle for the current agent run
- `hooks` (`Hookable<AgentHooks>`): Agent hooks for emitting events (e.g. spawn:complete)
- `mcpConnection?` (`McpConnection`): The agent's LIVE MCP connection, when servers are connected. Child-spawning
  tools hand it to subagents through a non-owning connector so children reuse
  the parent's transports (and their auth — e.g. OAuth tokens negotiated by a
  custom `mcpConnector`) instead of re-bootstrapping every server per spawn.

  Shared tools keep the hook bus they were built with, so `mcp:tool:gate` /
  `mcp:tool:transform` for child calls fire on the PARENT's hooks — the
  child's MCP access is governed by the same gate policy as the session that
  spawned it. This is a non-owning view: `close()` is a no-op, the owning
  agent's destroy path tears down the real transports.
- `mcpServers?` (`McpServerConfig[]`): MCP servers configured on the agent (for child inheritance)
- `model?` (`string`): The resolved model id for the current run (per-run override or the
  provider's default). Child-spawning tools use it so subagents inherit
  the parent run's model instead of falling back to
  `provider.meta.defaultModel`. Optional on the type so host code
  constructing contexts by hand doesn't have to supply it.
- `name?` (`string`): Agent display name (preset or user-supplied)
- `nestedSleep?` (`(ms: number) => Promise<void>`): Wait `ms` in order with this call's `callTool` calls: a durable
  timer on a replay runtime, and never alongside a recorded call. Present
  exactly when `callTool` is.
- `nestedTools?` (`Readonly<Record<string, ToolDef>>`): The run's tool registry `callTool` dispatches against, by canonical
  name: native tools, MCP tools (eager and lazy) and framework-injected
  ones. Present exactly when `callTool` is. Unlike `tools` (the
  agent's source map, for child inheritance) it includes MCP tools.
- `parentCallId?` (`string`): Tool-call id of the `spawn` call that created the run owning this tool
  call. Set for every child the built-in `spawn` tool starts (it forwards
  its own `callId`), absent on top-level runs and on host-rolled subagents
  that don't pass `AgentRunOptions.parentCallId`. Unlike `parentRunId` it
  does not depend on a shared session — see `ToolHookContext.parentCallId`.
- `parentRunId?` (`string`): Parent run id when the agent owning this tool call is a subagent.
  Mirrors `SessionRun.parentRunId`; absent on top-level runs. Tools
  that need to walk up to the spawning run (telemetry, scoped
  caches, todowrite-style state keyed by the parent thread) read it
  directly off the context instead of resolving via the session.
- `parentToolCallId?` (`string`): Set on a nested call: the id of the tool call that issued it through
  `callTool`. Tools that tailor output to what the model has seen
  (read dedup stubs) must not do so here — the caller, not the model,
  reads the result.
- `provider` (`Provider`): The LLM provider for this agent run
- `providers?` (`readonly Provider[]`): Every provider instance available to this run — the primary plus the
  fallback-chain providers when the agent was built with a provider
  array (see `AgentOptions.provider`). Child-spawning tools use it to
  route a requested model to the provider that serves it (matched by
  `Provider.name`). Optional: absent on single-provider hosts and
  hand-built contexts; consumers fall back to `provider`.
- `readState?` (`ReadStateMap`): Explicit read-state map forwarded from the agent's options. When set,
  tools should prefer it over the `Session`-keyed lookup so the
  `requireReadBeforeEdit` gate and `dedupReads` cache honor cross-context
  sharing — `spawn`'s `shareReadState` opt-in is the canonical caller.
  Resolve uniformly via `resolveReadStateMap(ctx)` (see
  `tools/read-state.ts`); custom tools that need read tracking should do
  the same.
- `reportOutcome?` (`(outcome: ToolOutcome) => void`): Report a structured side-effect outcome for this call. Mutating tools
  (write_file, edit, multi_edit) call this with the byte-level result so the
  loop can stamp it onto `tool:after.outcome` — letting hosts build
  no-op-resistant loop guards without parsing the result prose. Last call
  wins. No-op when absent (hand-built contexts, direct invocations); a tool
  that wants the signal should call it defensively (`ctx.reportOutcome?.(…)`).
- `reportProgress?` (`(progress: ToolProgress) => void`): Report live output of this call (a command's output chunks, a script's
  printed items) as the `tool:progress` hook, for a UI to show while the
  call runs. It never reaches the model or the session. Present only when
  someone shows it: on a tool that calls others (codemode), and on a nested
  call whose caller asked for it (`NestedToolCallOptions.streamProgress`).
- `reportStructured?` (`(value: unknown) => void`): Report a JSON value that describes this call's result for other tools
  (`ToolSpec.outputSchema` documents its shape). It never reaches the model
  or the session: the model still sees the returned text / content. A tool
  calling this one through `callTool` gets the value as
  `NestedToolCallResult.structured`. Last call wins; call it defensively
  (`ctx.reportStructured?.(…)`).
- `runId?` (`string`): The run id this tool call is part of. Populated by the agent loop when
  invoking tools. Optional on the type so host code constructing contexts
  by hand (tests, direct tool invocations) doesn't have to synthesize one.

  Spawn-style tools rely on this to tag child runs with `parentRunId` so
  the subagent tree can be reconstructed from a persisted session.
- `session?` (`Session`): The agent's session, when one was provided to `createAgent`. Tools that
  want to persist their own state (or, in the case of `spawn`, inherit the
  parent's session for child persistence) can read from here.
- `signal` (`AbortSignal`): Abort signal — tools should check this for early termination
- `skills?` (`SkillsConfig`): Skills configuration (for child inheritance)
- `state?` (`ToolStateView`): Durable key/value state for this call — see `src/tool-state.ts`.

  Reads are synchronous against a snapshot the agent hydrated before
  dispatch; writes are STAGED and committed by the loop once the call
  settles, so a replay-mode run can record them with the tool's step and
  re-issue an identical write on replay. That's what a plain closure
  variable (or a module-level `Map`) can't do: a recorded result skips the
  tool body, so the mutation never happens on replay.

  Durability comes from `AgentOptions.toolState`; with none configured
  this is an in-memory map scoped to the agent instance. Values cross a
  journal, so keep them small and JSON-serializable.

  Optional on the type so hand-built contexts (tests, direct
  invocations) don't have to synthesize one — read defensively
  (`ctx.state?.get(…)`).
- `system?` (`string`): Agent default system prompt
- `toolAliases?` (`Record<string, string>`): Map canonical tool names to LLM-facing (aliased) names.

  Aliasing is **LLM-boundary-only**:

  - The alias is what the provider's tool spec carries, what the model calls it, and
    what appears in `ToolHookContext.displayName` / `McpToolHookContext.displayName`.
  - The canonical name is what lives in `session.turns`, `ToolHookContext.name`, and
    what the agent uses to look up the tool implementation. Alias changes never
    desync persisted history.
- `toolDescriptions?` (`ToolDescriptions`): Agent-level description overrides (for child inheritance) — see `AgentOptions.toolDescriptions`.
- `tools` (`Record<string, ToolDef>`): Source tool map the agent was created with (pre-MCP-merge, pre-skills-injection)
- `turnId` (`string`): Turn ID that requested this tool call

Source: [src/tools/types.ts:21](https://github.com/Tahul/zidane/blob/main/src/tools/types.ts#L21)

### `ToolDef`

Interface.

A tool the agent can call: its spec and its `execute` function, plus scheduling hints.

```ts
interface ToolDef
```

**Properties**

- `callsNestedTools?` (`boolean`, deprecated): Whether this tool calls other tools through `ToolContext.callTool`.

  **Deprecated.** Shipped in v8.2.5; use `DurableToolDef.callsNestedTools` (`zidane/durable`). Leaves `ToolDef` in the next release.
- `execute` (`(input: Record<string, unknown>, ctx: ToolContext) => Promise<string | ToolResultContent[]>`): Execute the tool and return its output.

  Return a plain string for text-only tools (the common case). Return a
  `ToolResultContent[]` when the tool produces non-text content (images, mixed
  text+image) that the provider can route through natively (Anthropic
  `tool_result.content` arrays, OpenAI Codex pi-ai) or through the
  companion-user-message fallback (OpenAI Chat Completions).
- `isConcurrencySafe?` (`boolean | ((input: Record<string, unknown>) => boolean)`): Whether this tool is safe to dispatch in parallel with siblings inside
  one assistant turn. The scheduler treats safe tools as
  fan-out-eligible: a fleet of concurrent-safe tools runs in parallel
  (capped by `behavior.maxConcurrentTools`). An unsafe tool acts as a
  **barrier** — it waits for any in-flight fleet to drain, then runs
  alone, then unblocks the queue. Results are yielded back to the model
  in submission order regardless of completion order.

  Forms:

  - `true` / `false` — static decision.
  - `(input) => boolean` — input-aware (e.g. `shell` inspects the
    command for write side-effects so read-only commands fan out and
    mutating commands barrier).

  **Defaults to `false`.** Fail-closed: any tool that doesn't
  explicitly opt in serializes. Third-party MCP tools and any custom
  tool authored without an annotation will not parallelize.

  Side-effect-free read-only tools (read_file, glob, grep, list_files,
  todoread, tool_search, …) should set `true`. Tools that touch shared
  state (edit, multi_edit, write_file, todowrite, interaction, …)
  should leave it unset.
- `replay?` (`'safe' | 'unsafe'`): What a durable run does with a call of this tool that a process death
  left unfinished. `'safe'`: run it again (reads with no side effect).
  `'unsafe'` (default): report it to the model as interrupted, since its
  effect may or may not have happened.
- `runsNestedAgent?` (`boolean`, deprecated): Whether this tool runs a full nested agent loop inside its `execute`.

  **Deprecated.** Shipped in v8.2.5; use `DurableToolDef.runsNestedAgent` (`zidane/durable`). Leaves `ToolDef` in the next release.
- `spec` (`ToolSpec`): Name, description and input schema sent to the provider.

Source: [src/tools/types.ts:263](https://github.com/Tahul/zidane/blob/main/src/tools/types.ts#L263)

### `ToolDescriptionTarget`

Interface.

What a [`ToolDescriptionOverride`](https://zidane.si/docs/reference/tools.md#ToolDescriptionOverride) function receives alongside the
current description: the canonical name, the spec as the framework built
it (after every built-in rewrite), and the owning MCP server when the tool
was discovered from one.

```ts
interface ToolDescriptionTarget
```

**Properties**

- `mcpServer?` (`string`): Set for MCP-discovered tools: the configured server name.
- `name` (`string`): Canonical tool name, e.g. `shell` or `mcp_github_list_issues`.
- `spec` (`ToolSpec`): Tool spec as the framework built it, before the override.

Source: [src/tools/types.ts:429](https://github.com/Tahul/zidane/blob/main/src/tools/types.ts#L429)

### `ToolSearchToolOptions`

Interface.

Options for [`createToolSearchTool`](https://zidane.si/docs/reference/tools.md#createToolSearchTool).

```ts
interface ToolSearchToolOptions
```

**Properties**

- `addUnlock?` (`(canonical: string) => void`): Optional callback fired for every canonical name the tool unlocks. When
  set, the agent uses this to maintain an append-only `dynamicUnlockOrder`
  so the wire-level tool list emits new unlocks at the tail and keeps the
  provider prefix cache warm. Idempotent on repeat unlocks of the same
  name — callers may dedupe internally.

  Invoked **in addition to** the `unlocked.add` (which still happens, for
  callers that only watch the Set).
- `catalog` (`readonly LazyToolEntry[]`): Snapshot of every lazy tool the model can discover. Built once per run by
  the agent — the tool closes over this array and never mutates it.
- `defaultLimit?` (`number`): Default cap on returned matches when the model omits `limit`.
- `dispatcherName?` (`string`): Dispatch mode (`toolSearch.mode: 'dispatch'`): surfaced tools are NOT
  appended to the wire tools array; the model invokes them through this
  envelope tool. The description and result footer say so — without it the
  model would emit a direct `tool_use` the provider has never been told
  about.
- `unlocked` (`Set<string>`): Mutable per-run set of unlocked **canonical** tool names. The tool adds
  matches in place; the loop reads the set when rebuilding the wire-level
  tool list. Keyed by canonical (not wire) so dispatch lookups stay
  alias-stable.

  Prefer `addUnlock` for cache-stable wire-tool ordering: writes through a
  Set lose unlock order, so the wire-level rebuild that filters by `unlocked`
  has to fall back to registry iteration order — which moves entries every
  time a lazy tool earlier in the registry is unlocked, breaking provider
  prompt-cache breakpoints. The agent passes both when it owns the unlock
  tracker, with `addUnlock` mirroring writes into an ordered log.

Source: [src/tools/tool-search.ts:49](https://github.com/Tahul/zidane/blob/main/src/tools/tool-search.ts#L49)

### `ValidationResult`

Interface.

Result of [`validateToolArgs`](https://zidane.si/docs/reference/tools.md#validateToolArgs).

```ts
interface ValidationResult
```

**Properties**

- `coercedInput?` (`Record<string, unknown>`): Possibly-coerced input. Present iff `valid: true`. Tools should call
  `execute(coercedInput, ctx)` so auto-healed values reach the tool body.
  When no coercion was applied, this is reference-equal to the input.
- `coercions?` (`readonly string[]`): Names of fields whose values were coerced. Empty when nothing changed.
  Useful for telemetry (`validation:reject` on failure already carries the
  reason; this is the success-path equivalent).
- `droppedItems?` (`Readonly<Record<string, readonly number[]>>`): Indexes of array items dropped during recursive validation, keyed by
  the property name. Empty / absent when nothing was dropped. Tools that
  care about the discrepancy (e.g. `todowrite` wanting to surface
  "ignored 2 malformed items") can inspect this.
- `error?` (`string`): Human-readable reason. Present on failure only.
- `valid` (`boolean`): True when the input passes validation. `coercedInput` is set then.

Source: [src/tools/validation.ts:23](https://github.com/Tahul/zidane/blob/main/src/tools/validation.ts#L23)

## Types

### `ReadStateMap`

Type.

Read-state entries keyed by [`readStateKey`](https://zidane.si/docs/reference/tools.md#readStateKey).

```ts
type ReadStateMap = Map<string, ReadStateEntry>
```

Source: [src/tools/read-state.ts:112](https://github.com/Tahul/zidane/blob/main/src/tools/read-state.ts#L112)

### `SubagentOverride`

Type.

Editing function for an existing subagent. Receives the current def
(host- or extension-provided; `undefined` when the name is unknown)
and returns the replacement — or `undefined` to delete the entry.

```ts
type SubagentOverride = (def: SubagentDef | undefined) => SubagentDef | undefined
```

Source: [src/tools/spawn.ts:332](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L332)

### `SubagentRegistry`

Type.

Map of subagent-type key → preset. Keys are case-sensitive and
appear verbatim in the spawn input schema's enum so the model emits
them with the same casing. Common conventions: `'Explore'`,
`'Plan'`, `'Verification'`, `'general-purpose'` (Claude Code SDK's
built-in set).

```ts
type SubagentRegistry = Record<string, SubagentDef>
```

Source: [src/tools/spawn.ts:313](https://github.com/Tahul/zidane/blob/main/src/tools/spawn.ts#L313)

### `ToolDescriptionOverride`

Type.

One entry of `AgentOptions.toolDescriptions`. A string replaces the
description wholesale; a function receives the current text and returns
the new one — the form to reach for when amending, since it keeps any
framework-generated prose (e.g. `shell`'s background-mode gate and
"prefer the dedicated tool" block) intact.

```ts
type ToolDescriptionOverride =
  | string
  | ((current: string, tool: ToolDescriptionTarget) => string)
```

Source: [src/tools/types.ts:445](https://github.com/Tahul/zidane/blob/main/src/tools/types.ts#L445)

### `ToolDescriptions`

Type.

Canonical-name-keyed record of [`ToolDescriptionOverride`](https://zidane.si/docs/reference/tools.md#ToolDescriptionOverride)s.

```ts
type ToolDescriptions = Record<string, ToolDescriptionOverride>
```

Source: [src/tools/types.ts:450](https://github.com/Tahul/zidane/blob/main/src/tools/types.ts#L450)

### `ToolMap`

Type.

Tools keyed by name.

```ts
type ToolMap = Map<string, ToolDef>
```

Source: [src/tools/types.ts:421](https://github.com/Tahul/zidane/blob/main/src/tools/types.ts#L421)

## Re-exports

These names are documented on the entry point that owns them.

- [zidane/tools/prompts](https://zidane.si/docs/reference/tools-prompts.md): [`toolPrompts`](https://zidane.si/docs/reference/tools-prompts.md#toolPrompts), [`ToolPrompts`](https://zidane.si/docs/reference/tools-prompts.md#ToolPrompts)
