Skip to content

Sub-agents

Delegate work to child agents with the spawn tool.

spawn hands a task to a child agent. By default the child inherits the tools, system prompt, aliases, MCP servers, skills and behavior of the parent. The events of the child bubble up to the hooks of the parent.

Spawn a child agent#

TypeScript
import { basicTools, createSpawnTool, definePreset } from 'zidane'

definePreset({
  name: 'orchestrator',
  tools: {
    ...basicTools,
    spawn: createSpawnTool({ maxConcurrent: 5, model: 'claude-haiku-4-5-20251001', thinking: 'low' }),
  },
})

createSpawnTool({ preset: { tools: {} } })  // preset replaces inherited fields: children get no tools
OptionDefaultEffect
maxConcurrent3Concurrent children, Infinity for no cap (queuing)
maxDepth3Nesting limit. 0 turns spawning off, 1 forbids grandchildren.
modelparent'sChild model
system, thinkingunsetChild system prompt and thinking level. A per-call input.system wins.
presetnoneFields that replace the inherited ones
timeoutMsnonePer-child timeout. When it expires, zidane destroys the child and fires spawn:error.
forwardHookstrueRe-fire child events on the parent as child:* (observing children)
onSpawn, onCompletenoneCallbacks when a child starts and when it ends
persist, shareReadState, shareHandlefalseBoundaries. The basic preset's spawn uses persist: true.
subagentsnoneType registry
modelsnonePer-spawn model choice

Concurrency and queuing#

Past maxConcurrent, the spawn calls of a top-level agent wait for a slot in arrival order and fire spawn:queued. The calls of a sub-agent past the cap are refused, because a wait on a slot that an ancestor holds can deadlock.

One turn runs at most behavior.maxConcurrentTools tool calls at once (10 by default), spawns included. If you set maxConcurrent above 10, raise maxConcurrentTools too.

In the apps, the setting Max parallel subagents (maxParallelSubagents: 1, 2, 3, 5, 10 or Unlimited, default 3) sets the cap of each session, nested subagents included. It also raises maxConcurrentTools of the session to match. A change applies from the next run (settings).

A refused call (concurrency cap, maxDepth, unknown model, bad resume) returns a tool error to the model. The run of the parent continues.

Parent and child boundaries#

Flag (default false)Shares
persistThe Session of the parent, plus read state. Child turns append to session.turns under the runId of the child. Its SessionRun carries parentRunId, so you can rebuild the tree. A child resume needs it.
shareReadStateOnly the read-state map. Child read_file calls count for the read tracking of the parent, and child turns stay in memory. Use it when the requireReadBeforeEdit guard of the parent must honor child reads without storing child turns.
shareHandleThe ExecutionHandle of the parent. The child skips execution.spawn() and never destroys the handle, so remote contexts avoid a new sandbox per spawn.

The child always gets the AgentClock and AgentBehavior of the parent.

Resume a child#

The spawn input takes resume: a child label such as child-2, or a run id. The child continues with its stored turns, and task holds the next instruction. Resume needs persist: true and a parent session. The tool refuses a child that is still running or that has no stored turns.

Subagent type registry#

subagents adds a subagent_type enum to the spawn schema. Use the Claude Code SDK names (Explore, Plan, Verification, general-purpose) so that trained models pick them. The enum always includes general-purpose. An unregistered type runs as general-purpose, with no error.

TypeScript
createSpawnTool({
  subagents: {
    Explore: { description: 'read-only research', readonly: true },
    Plan: { description: 'design changes', system: 'You are the planning subagent. Do not edit.' },
    Verification: { description: 'run tests', tools: ['shell', 'read_file'] },
  },
})
FieldEffect
descriptionShown to the model to pick a type
system, modelType system prompt and model. A per-call input.system or input.model wins.
thinkingType thinking level. Wins over createSpawnTool({ thinking }).
toolsAllow-list of canonical names, a subset of the tools of the parent. It can list mcp_<server>_<tool> names. A list without MCP names gives the child no MCP tools. Wins over readonly.
readonly: trueShorthand for ['read_file', 'grep', 'glob', 'list_files']. The child gets no MCP tools.

Subagents from Markdown files#

In the apps, each Markdown file in an agents/ folder is a subagent type. The first file for a name wins. zidane reads <project>/.agents/agents, <project>/.zidane/agents, ~/.agents/agents and ~/.zidane/agents. With the setting Import configurations from other agents on, it also reads .claude/agents.

.zidane/agents/reviewer.md
---
name: reviewer
description: Reviews diffs for bugs
tools: Read, Grep, Bash(git:*)
model: sonnet
---
You review diffs. Report bugs with file:line.
KeyEffect
nameType name, case-sensitive. Defaults to the file name.
descriptionRequired. zidane skips a file without one.
toolsAllowed tools, as Claude Code or zidane names. Without it, the type gets every tool of the parent. Bash(git:*) grants all of shell, because patterns do not restrict arguments.
disallowedToolsTools to remove. Without tools, the type keeps the native tools minus this list, and gets no MCP or extension tools.
readonlytrue limits the agent to read tools
permissionModeplan is the same as readonly: true. zidane ignores other values.
modelModel for this type

The body is the system prompt of the type. zidane skips a file with an empty body. A file with an unreadable tools, disallowedTools or readonly value is skipped, so a typo cannot widen the tool set. A file with name: Explore in your user folder replaces the built-in Explore. The agent profiles in the picker of the apps (Build, Plan) are separate. They are built in or come from extensions.

Model selection per spawn#

models adds a model field to the spawn schema, so the calling model picks the child model. Each entry routes to the provider that serves it. The tool refuses an unknown id and lists the closest matches. Up to 8 entries, the field description lists the models. Above 8, the field points to a separate model_search tool, which you register with createModelSearchTool.

Precedence: input.model, the type's model, createSpawnTool({ model }), then the parent's model.

In the apps, the setting Allow subagents to use different models (subagentModels, on by default) adds the model field and model_search when your signed-in providers have a model catalog. When it is off, children stay on the model of the parent (settings).

Observing children#

spawn:queued, spawn:before, spawn:complete and spawn:error fire on the parent. With forwardHooks on, child events re-fire as child:* with childId and depth (bubble events). A child:spawn:* event at depth 2 or more also carries parentChildId, the sub-agent that called spawn.

child:tool:gate, child:mcp:tool:gate and child:tool:transform share the context that the loop of the child awaits. A parent handler can block, substitute or rewrite a tool call of the child. Usage totals across parent and children are on agents.

In the apps, time estimates give each running sub-agent its own time left, in its header.

Reference#