Multi-Agent System
Claude Code doesn’t just run one agent. It can spawn specialized agents, coordinate teams, and isolate parallel work. Understanding these patterns helps you choose the right approach for complex tasks.
3 Coordination Patterns
| Pattern A — Subagent | Pattern B — Coordinator | Pattern C — Fork | |
|---|---|---|---|
| Execution | Sequential (blocking) | Parallel | Parallel |
| Git isolation | No | No | Yes (per-agent worktree) |
| Coordinator tools | Full | 4 only | Full (each fork) |
| Context | Isolated | Isolated per worker | Cloned from parent |
| Best for | Focused subtasks | Large parallel projects | Conflicting file changes |
Deep Dive: AgentTool Input Schema
When the main agent decides to spawn a subagent, it calls AgentTool with this schema:
AgentTool input: task: string // Description of what the subagent should do subagent_type: string // One of: "explore", "verification", "general-purpose", // "plan", "claude-code-guide", or a custom agent nameThe system resolves the agent type to a full definition containing: system prompt, allowed tools, model, memory scope, permission mode, and max turns. Custom agents defined in .claude/agents/ are discovered by matching the subagent_type against the frontmatter name field.
Why Coordinators Can’t Code
The coordinator in Pattern B is restricted to exactly 4 tools: TeamCreate, TeamDelete, SendMessage, and SyntheticOutput. No Bash. No FileEdit. No FileRead. No Grep.
This is a code-level constraint, not an instruction. The tools simply aren’t registered in the coordinator’s tool manifest.
Why? Because LLMs take the shortest path. If a coordinator has FileEdit, it will edit files directly instead of delegating. Removing execution tools forces the coordinator to divide work, assign it, and wait for results. The constraint creates the behavior:
# Without constraint (bad)Coordinator thinks: "I could just edit this file myself... done."Result: sequential, no parallelism
# With constraint (good)Coordinator thinks: "I must delegate this to a worker."Result: natural parallelism, natural specializationDeep Dive: Recursive Architecture — runAgent()
The most important architectural insight: subagents run the exact same query() function as the main agent. There is no separate “subagent loop” — it’s recursive.
FUNCTION runAgent(agentDefinition, task, forkContext, worktreePath): // 1. Resolve model (agent-specific or inherit parent) model = getAgentModel(agentDefinition, parentModel)
// 2. Build context messages IF forkContext: // Fork: clone parent context, replace tool_results with placeholder messages = filterIncompleteToolCalls(forkContext) FOR EACH toolResult IN messages: toolResult.content = "Fork started — processing in background" ELSE: // Fresh subagent: empty context messages = []
// 3. Resolve permission mode IF agentDefinition.permissionMode == "bubble": // Permission requests float up to parent terminal permissionHandler = parentTerminalHandler ELSE: permissionHandler = defaultHandler
// 4. Build system prompt from agent definition systemPrompt = agentDefinition.markdownBody
// 5. Run the SAME query loop as the main agent FOR AWAIT message OF query({model, messages, systemPrompt, tools: agentDefinition.tools}): YIELD message // Stream results back to parentThis means every agent — main, subagent, fork — gets the same capabilities: 4-phase loop, escalating recovery, context defense, permission pipeline. The difference is only in configuration (which tools, which prompt, which permission mode).
6 Built-in Agents
| Agent | Tool Access | Role | Key Detail |
|---|---|---|---|
| Explore | FileRead, Grep, Glob, Bash (read-only) | Codebase discovery | System prompt opens with CRITICAL: READ-ONLY MODE — defense at both prompt AND tool level |
| Verification | Bash, FileRead, WebFetch, Agent (recursive) | Break things | ”Your job is not to confirm — it’s to try to break it.” Detects two failure patterns: verification avoidance (describing tests without running them) and being seduced by the first 80% |
| General-purpose | All tools | Versatile worker | ”Complete the task fully — don’t gold-plate, but don’t leave it half-done” |
| Plan | FileRead, Grep, Glob, Bash (read-only), Agent | Architect | Read-only. Analyzes codebase and designs implementation plans |
| Claude Code Guide | FileRead, Grep, Glob, Bash, WebFetch, WebSearch | Help desk | Fetches docs from code.claude.com |
| Statusline Setup | FileRead, Bash, Config | Shell specialist | Reads shell config, converts PS1 prompts for statusline integration |
Custom Agents
Define custom agents in .claude/agents/ as markdown files. Each file is a complete agent spec:
---name: migration-specialistdescription: Handles database migrations safelymodel: claude-opus-4-5tools: [FileRead, Bash, Grep]whenToUse: "When working with files in migrations/ directory"memory: project # user | project | localisolation: worktree # run in separate git worktreepermissionMode: default # cannot be elevated by plugin agentsmaxTurns: 20hooks: PostToolUse: "npm run test:migrations"---
You are a database migration specialist. Before making any change,read the existing migration files to understand naming conventionsand the current schema state.Plugin agents are restricted. They cannot set permissionMode, hooks, or mcpServers per-agent. These fields would escalate privileges beyond what the user approved at install time. Plugin-level hooks (set at the plugin manifest) are trusted — per-agent escalation is not.
Deep Dive: Fork Agent Internals
Fork agents optimize for parallel execution with shared prompt cache:
FORK_PLACEHOLDER_RESULT: When forking, all tool_result blocks in the shared context are replaced with an identical placeholder string: "Fork started — processing in background". This ensures all fork children send byte-identical API request prefixes (system prompt + shared history + placeholder results). The API server caches this prefix — 3 fork children sharing cache = 3x token savings on input.
isInForkChild() anti-recursive guard:
FUNCTION isInForkChild(messages): // Check if conversation contains the fork boilerplate tag FOR message IN messages: IF message.type == "user" AND message.content.includes("<fork-context>"): RETURN true RETURN false
// Usage: AgentTool checks before allowing forkIF isInForkChild(currentMessages): REJECT "Cannot fork from inside a fork child"Without this guard: fork A spawns fork B spawns fork C → exponential explosion. The guard detects the boilerplate tag injected when the fork was created and prevents recursion.
buildWorktreeNotice(): Injected into the fork agent’s context:
"You are working in an isolated git worktree at ${worktreePath}. Your changes are on branch ${branchName}. Changes will be merged back to the parent branch when you complete your task."The agent knows it’s on a temporary branch and can commit freely without affecting the main branch.
Session mode matching: When spawning a subagent, the child’s permission mode must be equal to or more restrictive than the parent’s. A default parent can spawn a plan child (more restrictive) but a plan parent cannot spawn a bypassPermissions child (less restrictive). This prevents privilege escalation through the agent chain.
Agent Memory
Agents have 3 memory scopes:
| Scope | Storage Location | Shared Across |
|---|---|---|
| user | ~/.claude/memory/ | All projects, all sessions |
| project | .claude/memory/ | All agents in same project |
| local | In-session only | Current session only |
Agents can save state snapshots and restore them — enabling “sleep and wake” across sessions. A long-running analysis agent can checkpoint its progress, shut down, and resume exactly where it stopped.
Communication (Coordinator ↔ Workers)
Workers and coordinators communicate via SendMessage. Messages are queued in a per-agent mailbox and read at the start of each agent loop iteration. This is async and non-blocking — the coordinator sends a task and continues planning while workers execute.
Coordinator mailbox: [from: worker-1, "Found 3 auth bugs"] [from: worker-2, "API spec complete"]
Worker-1 mailbox: [from: coordinator, "Review src/auth/**"]Worker-2 mailbox: [from: coordinator, "Write API_SPEC.md"]The coordinator reads all messages at turn start, aggregates results, and decides next steps — never waiting on a blocking call.
Why This Matters to You
- When to use subagents → focused tasks that benefit from isolated context (exploration, verification, one-off generation)
- When to use teams → large parallel refactors across multiple modules where workers can truly run independently
- Why the coordinator can’t read your files → it forces delegation rather than sequential self-execution; not a bug
- How worktrees prevent merge conflicts → each Fork agent operates on its own Git branch; changes are merged only when complete
- How to define custom agents → create
.claude/agents/your-agent.mdwith YAML frontmatter; the agent is available in the current session immediately
See also: The Agent Loop — Tool Orchestration — Skill Engine