Skip to content

Coordinator Pattern

Coordinator mode fundamentally changes how Claude Code operates. Instead of a single agent doing everything, the main Claude instance becomes a coordinator that delegates work to parallel worker agents. It is enabled via the CLAUDE_CODE_COORDINATOR_MODE environment variable and gated behind the COORDINATOR_MODE feature flag.

src/coordinator/coordinatorMode.ts
export function isCoordinatorMode(): boolean {
if (feature('COORDINATOR_MODE')) {
return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
}
return false
}

When coordinator mode is active, the entire built-in agent set is replaced:

src/tools/AgentTool/builtInAgents.ts
if (feature('COORDINATOR_MODE')) {
if (isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)) {
const { getCoordinatorAgents } = require('../../coordinator/workerAgent.js')
return getCoordinatorAgents() // Completely different agent roster
}
}
graph TD
U[User] <-->|conversation| C[Coordinator]
C -->|Agent tool| W1[Worker 1: Research]
C -->|Agent tool| W2[Worker 2: Implement]
C -->|Agent tool| W3[Worker 3: Verify]
C -->|SendMessage| W1
C -->|TaskStop| W2
W1 -.->|task-notification| C
W2 -.->|task-notification| C
W3 -.->|task-notification| C
style C fill:#f9f,stroke:#333,stroke-width:2px
style W1 fill:#bbf,stroke:#333
style W2 fill:#bbf,stroke:#333
style W3 fill:#bbf,stroke:#333

The coordinator has exactly three tools for worker management:

ToolPurpose
AgentSpawn a new worker
SendMessageContinue an existing worker (send follow-up to its agent ID)
TaskStopStop a running worker

The coordinator system prompt (getCoordinatorSystemPrompt()) is a detailed ~370-line document that defines the coordinator’s behavior. Key sections:

You are Claude Code, an AI assistant that orchestrates software engineering
tasks across multiple workers.
You are a coordinator. Your job is to:
- Help the user achieve their goal
- Direct workers to research, implement and verify code changes
- Synthesize results and communicate with the user
- Answer questions directly when possible — don't delegate work
that you can handle without tools

The coordinator operates in structured phases:

flowchart LR
R[Research<br/>Workers parallel] --> S[Synthesis<br/>Coordinator]
S --> I[Implementation<br/>Workers] --> V[Verification<br/>Workers]
V -->|failures| S
PhaseWhoPurpose
ResearchWorkers (parallel)Investigate codebase, find files, understand problem
SynthesisCoordinatorRead findings, craft implementation specs
ImplementationWorkersMake targeted changes per spec, commit
VerificationWorkersTest changes work

Workers report results via <task-notification> XML messages that arrive as user-role messages:

<task-notification>
<task-id>{agentId}</task-id>
<status>completed|failed|killed</status>
<summary>{human-readable status summary}</summary>
<result>{agent's final text response}</result>
<usage>
<total_tokens>N</total_tokens>
<tool_uses>N</tool_uses>
<duration_ms>N</duration_ms>
</usage>
</task-notification>

The coordinator prompt explicitly encodes concurrency constraints:

Parallelism is your superpower. Workers are async. Launch independent
workers concurrently whenever possible.
Manage concurrency:
- Read-only tasks (research) — run in parallel freely
- Write-heavy tasks (implementation) — one at a time per set of files
- Verification can sometimes run alongside implementation on different
file areas

The coordinator injects worker capability information into its own context via getCoordinatorUserContext():

src/coordinator/coordinatorMode.ts
export function getCoordinatorUserContext(
mcpClients: ReadonlyArray<{ name: string }>,
scratchpadDir?: string,
): { [k: string]: string } {
if (!isCoordinatorMode()) return {}
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)
? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME].sort().join(', ')
: Array.from(ASYNC_AGENT_ALLOWED_TOOLS)
.filter(name => !INTERNAL_WORKER_TOOLS.has(name))
.sort()
.join(', ')
let content = `Workers spawned via the Agent tool have access to these tools: ${workerTools}`
if (mcpClients.length > 0) {
const serverNames = mcpClients.map(c => c.name).join(', ')
content += `\n\nWorkers also have access to MCP tools from connected MCP servers: ${serverNames}`
}
if (scratchpadDir && isScratchpadGateEnabled()) {
content += `\n\nScratchpad directory: ${scratchpadDir}
Workers can read and write here without permission prompts.
Use this for durable cross-worker knowledge.`
}
return { workerToolsContext: content }
}

Internal tools (TeamCreate, TeamDelete, SendMessage, SyntheticOutput) are filtered from the worker capability list — workers shouldn’t know about coordinator internals.

The most critical design principle in the coordinator prompt is synthesis ownership. The coordinator must understand research findings before directing follow-up work:

Never write "based on your findings" or "based on the research."
These phrases delegate understanding to the worker instead of doing
it yourself. You never hand off understanding to another worker.
// Anti-pattern — lazy delegation (bad)
Agent({ prompt: "Based on your findings, fix the auth bug", ... })
// Good — synthesized spec
Agent({ prompt: "Fix the null pointer in src/auth/validate.ts:42.
The user field on Session is undefined when sessions expire but
the token remains cached. Add a null check before user.id access —
if null, return 401 with 'Session expired'. Commit and report
the hash.", ... })

The coordinator must decide whether to continue an existing worker (preserving its context) or spawn a fresh one:

SituationMechanismReason
Research explored exactly the files neededContinueWorker already has files in context
Research was broad, implementation is narrowSpawn freshAvoid context noise
Correcting a failureContinueWorker has error context
Verifying another worker’s codeSpawn freshFresh eyes, no implementation assumptions
Wrong approach entirelySpawn freshClean slate avoids anchoring on failed path

Coordinator mode is persisted per session. When resuming a session, the mode is matched:

src/coordinator/coordinatorMode.ts
export function matchSessionMode(
sessionMode: 'coordinator' | 'normal' | undefined,
): string | undefined {
const currentIsCoordinator = isCoordinatorMode()
const sessionIsCoordinator = sessionMode === 'coordinator'
if (currentIsCoordinator === sessionIsCoordinator) return undefined
// Flip the env var to match the session
if (sessionIsCoordinator) {
process.env.CLAUDE_CODE_COORDINATOR_MODE = '1'
} else {
delete process.env.CLAUDE_CODE_COORDINATOR_MODE
}
return sessionIsCoordinator
? 'Entered coordinator mode to match resumed session.'
: 'Exited coordinator mode to match resumed session.'
}

This ensures that resuming a coordinator session re-enters coordinator mode, even if the env var isn’t set.