The main agent can spawn sub-agents to handle tasks in parallel, in isolation, or with a different specialisation. This is what makes "find every TODO and summarise them" feasible — the main agent delegates to a researcher sub-agent that does the searching, then comes back with a summary instead of polluting the main thread with hundreds of tool calls.
The system lives in a-mini/multi_agent/:
subagent.py—AgentDefinition,SubAgentManager, worktree helpers.tools.py—Agent,SendMessage,CheckAgentResult,ListAgentTasks,ListAgentTypes.
Built-in agent types
@dataclass
class AgentDefinition:
name: str
description: str = ""
system_prompt: str = ""
model: str = ""
tools: list = field(default_factory=list)
source: str = "user" # "built-in" | "user" | "project"
| Type | Optimised for | Tools |
|---|---|---|
general-purpose | Research, exploration, multi-step tasks | All |
coder | Writing, reading, modifying code | All |
reviewer | Security, correctness, code quality | Read, Glob, Grep |
researcher | Web search and documentation lookup | Read, Glob, Grep, WebFetch, WebSearch |
tester | Writing and running tests | All |
The reviewer and researcher have restricted tool sets so they can't accidentally modify files. That's intentional — a code review that mutates the code is no longer a review.
Spawning a sub-agent
The main agent calls Agent to spawn:
{
"tool": "Agent",
"input": {
"prompt": "Search this codebase for all TODO comments and summarize them.",
"subagent_type": "researcher",
"isolation": "worktree",
"name": "todo-finder",
"wait": true
}
}
Parameters:
| Field | Type | Description |
|---|---|---|
| prompt* | string | The task description for the sub-agent. |
| subagent_type | string | The agent definition to use. Defaults to |
| isolation | 'none' | 'worktree' |
|
| name | string | A label for background tracking. Used with |
| model | string | Override the model. The sub-agent inherits the parent's model otherwise. |
| wait | boolean |
|
Background mode
For long-running tasks, set wait: false:
{ "tool": "Agent", "input": { "prompt": "Run all tests and report failures.",
"name": "test-runner", "wait": false } }
The main agent gets a task id and continues. Later:
{ "tool": "CheckAgentResult", "input": { "task_id": "<id>" } }
Or if the sub-agent's interactive (asks for clarification mid-run):
{ "tool": "SendMessage", "input": { "name": "test-runner", "message": "Skip the integration suite." } }
ListAgentTasks lists all active and finished tasks. ListAgentTypes lists available agent definitions (so the model can pick one before spawning).
Worktree isolation
isolation: "worktree" creates a git worktree on a new branch under .git/worktrees/<name> and spawns the sub-agent there. The sub-agent's cwd is the worktree, so its file edits don't touch the main checkout.
Useful for:
- Refactors that should be reviewable as a separate branch.
- Experiments that might fail and need a clean rollback.
- Parallel sub-agents that would otherwise step on each other's edits.
When the sub-agent finishes:
- If no files changed, the worktree is auto-cleaned.
- If files changed, the branch name is reported back so you (or the main agent) can
git diff <branch>and decide whether to merge.
The worktree feature requires the workspace to be a git repo. If it isn't, isolation: "worktree" falls back to isolation: "none".
Nesting limit
Sub-agents can themselves spawn sub-agents, but only up to 3 levels deep. The depth is tracked in the _depth config the runtime passes through. Beyond 3 levels, Agent returns an error — preventing the infinite-recursion failure mode where an agent keeps delegating without ever doing the work.
Custom agent types
Drop a markdown file in ~/.nano_claude/agents/<name>.md:
---
name: api-designer
description: Specialised in designing REST APIs for AppMint.
model: claude-opus-4-6
tools: [Read, Grep, WebFetch]
---
You are an API designer. Focus on:
- Resource-oriented design
- Consistent naming
- Discovery-first AppMint integration
Always check /discover/* before recommending an endpoint.
Cite specific endpoints when reviewing existing APIs.
The frontmatter fields mirror AgentDefinition. Project-level agents go in .nano_claude/agents/ and override user-level definitions of the same name.
When to delegate vs handle inline
Delegate when:
- The task is well-defined and self-contained ("find all callers of
X"). - The task would generate noisy intermediate output you don't want in the main thread.
- Different tools / models suit the task than the main conversation.
- You want isolation (worktree branch).
Handle inline when:
- The task is a single edit or read.
- You want to interleave model reasoning with tool calls in real time.
- The task needs context from the running conversation that's hard to forward.
Communication between agents
SendMessage(name, message) queues a message for a named background sub-agent. The sub-agent reads its inbox at the start of each turn — useful for "stop", "skip this step", or "use this hint".
There's no broadcast — communication is point-to-point.
Inspecting tasks
/agents in the REPL shows the task list:
[running] test-runner started 30s ago, 12 turns, 4 tool calls
[finished] todo-finder completed 2m ago, summary: 14 TODOs across 5 files
[failed] refactor-auth errored at turn 7: file not found
ListAgentTasks returns the same data as a JSON-shaped tool result.
Performance considerations
- Each sub-agent costs an LLM context window — splitting one big task into many sub-agents multiplies token usage.
- Worktree creation is O(file count) — large workspaces take a few seconds per spawn.
- The main thread blocks on
wait: true. For UX, preferwait: false+CheckAgentResultpolling when the sub-agent task is long.
In Vibe Studio
When the agent in Vibe Studio spawns a sub-agent, the chat panel renders it as a nested message tree. You can expand the sub-agent's tool calls and reasoning, or collapse them to keep focus on the main thread.
The studio shows running sub-agents in a sidebar with their status — useful when you've spawned several in background mode.