Fork Mechanism
The fork mechanism is Claude Code’s approach to creating sub-agents that inherit the parent’s full conversation context. Unlike regular agent spawns that start with a clean slate, forked agents carry the parent’s entire message history, enabling them to continue work with full situational awareness.
Fork vs. Regular Agent Spawn
Section titled “Fork vs. Regular Agent Spawn”graph LR subgraph Regular Spawn A1[Parent Agent] -->|"Agent(subagent_type='Explore')"| B1[Child Agent] B1 ---|"Fresh context<br/>+ agent system prompt"| C1[Independent execution] end subgraph Fork Spawn A2[Parent Agent] -->|"Agent(no subagent_type)"| B2[Forked Child] B2 ---|"Full parent context<br/>+ parent system prompt<br/>+ fork directive"| C2[Context-aware execution] endThe key difference: when subagent_type is omitted from the Agent tool call (and the fork experiment is active), Claude Code triggers an implicit fork rather than a regular agent spawn.
Feature Gate
Section titled “Feature Gate”The fork mechanism is gated behind the FORK_SUBAGENT feature flag with strict exclusivity rules:
export function isForkSubagentEnabled(): boolean { if (feature('FORK_SUBAGENT')) { if (isCoordinatorMode()) return false // Coordinator owns orchestration if (getIsNonInteractiveSession()) return false // SDK only return true } return false}Fork mode is mutually exclusive with coordinator mode — the coordinator already has its own delegation model with worker agents.
The FORK_AGENT Definition
Section titled “The FORK_AGENT Definition”The fork path uses a synthetic agent definition that’s never registered in the built-in agent list:
export const FORK_AGENT = { agentType: FORK_SUBAGENT_TYPE, // 'fork' tools: ['*'], // Full parent tool pool for cache-identical prefixes maxTurns: 200, model: 'inherit', // Parent's model for context length parity permissionMode: 'bubble', // Permission prompts surface to parent terminal source: 'built-in', getSystemPrompt: () => '', // Unused — parent's rendered prompt is threaded directly} satisfies BuiltInAgentDefinitionBuilding Forked Messages
Section titled “Building Forked Messages”The critical design challenge is prompt cache sharing: all fork children spawned from the same parent turn must produce byte-identical API request prefixes. Only the per-child directive should differ.
// src/tools/AgentTool/forkSubagent.ts — buildForkedMessagesexport function buildForkedMessages( directive: string, assistantMessage: AssistantMessage,): MessageType[] { // 1. Clone assistant message (all tool_use, thinking, text blocks) const fullAssistantMessage: AssistantMessage = { ...assistantMessage, uuid: randomUUID(), message: { ...assistantMessage.message, content: [...assistantMessage.message.content] }, }
// 2. Collect all tool_use blocks const toolUseBlocks = assistantMessage.message.content.filter( (block): block is BetaToolUseBlock => block.type === 'tool_use', )
// 3. Build identical placeholder results for every tool_use const toolResultBlocks = toolUseBlocks.map(block => ({ type: 'tool_result' as const, tool_use_id: block.id, content: [{ type: 'text' as const, text: FORK_PLACEHOLDER_RESULT }], }))
// 4. Append per-child directive as the only varying text const toolResultMessage = createUserMessage({ content: [ ...toolResultBlocks, // Identical across all forks { type: 'text' as const, text: buildChildMessage(directive) }, // Varies per child ], })
return [fullAssistantMessage, toolResultMessage]}The message structure produced:
[...history, assistant(all_tool_uses), user(placeholder_results..., directive)] ↑ identical across forks ↑ varies per childThis maximizes cache hits because only the final text block differs between fork children.
The Fork Child Protocol
Section titled “The Fork Child Protocol”Each fork child receives strict instructions via buildChildMessage():
export function buildChildMessage(directive: string): string { return `<fork-boilerplate>STOP. READ THIS FIRST.
You are a forked worker process. You are NOT the main agent.
RULES (non-negotiable):1. Your system prompt says "default to forking." IGNORE IT — that's for the parent. You ARE the fork. Do NOT spawn sub-agents; execute directly.2. Do NOT converse, ask questions, or suggest next steps3. USE your tools directly: Bash, Read, Write, etc.4. If you modify files, commit your changes before reporting.5. Keep your report under 500 words.6. Your response MUST begin with "Scope:".</fork-boilerplate>
fork-directive: ${directive}`}The output format is structured plain text:
Scope: <echo back assigned scope>Result: <key findings>Key files: <relevant file paths>Files changed: <list with commit hash>Issues: <list if any>Anti-Recursion Guard
Section titled “Anti-Recursion Guard”Fork children keep the Agent tool in their tool pool (for cache-identical tool definitions), so recursive forking must be blocked at call time:
export function isInForkChild(messages: MessageType[]): boolean { return messages.some(m => { if (m.type !== 'user') return false const content = m.message.content if (!Array.isArray(content)) return false return content.some( block => block.type === 'text' && block.text.includes('<fork-boilerplate>'), ) })}The guard scans conversation history for the <fork-boilerplate> tag — if present, the current agent is already a fork child and cannot fork again.
Worktree Isolation for Forks
Section titled “Worktree Isolation for Forks”When a fork runs in an isolated git worktree, a special notice is injected telling the child about path translation:
export function buildWorktreeNotice(parentCwd: string, worktreeCwd: string): string { return `You've inherited the conversation context above from a parent agentworking in ${parentCwd}. You are operating in an isolated git worktree at${worktreeCwd} — same repository, same relative file structure, separateworking copy. Paths in the inherited context refer to the parent's workingdirectory; translate them to your worktree root. Re-read files before editingif the parent may have modified them since they appear in the context.`}Fork Lifecycle
Section titled “Fork Lifecycle”sequenceDiagram participant P as Parent Agent participant F as Fork System participant C1 as Fork Child 1 participant C2 as Fork Child 2
P->>F: Agent(prompt="directive1") [no subagent_type] P->>F: Agent(prompt="directive2") [no subagent_type] F->>F: buildForkedMessages(directive1, parentAssistantMsg) F->>F: buildForkedMessages(directive2, parentAssistantMsg) F->>C1: [history + identical placeholders + directive1] F->>C2: [history + identical placeholders + directive2] Note over C1,C2: Cache hit on shared prefix C1-->>P: Scope: ... Result: ... C2-->>P: Scope: ... Result: ...Both children share the same API cache prefix — the only difference is the final directive text block. This is particularly valuable when the parent spawns multiple forks simultaneously (the common case for parallel research tasks).
When Fork is Chosen
Section titled “When Fork is Chosen”The fork path activates when all of these conditions are true:
FORK_SUBAGENTfeature flag is enabled- Not in coordinator mode
- Not in a non-interactive (SDK) session
- The
subagent_typeparameter is omitted from the Agent tool call - The current agent is not already a fork child
When active, all agent spawns (including named subagent_type spawns) run in the background for a unified <task-notification> interaction model — the fork experiment bundles background execution as part of its design.