跳转到内容

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.

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]
end

The 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.

The fork mechanism is gated behind the FORK_SUBAGENT feature flag with strict exclusivity rules:

src/tools/AgentTool/forkSubagent.ts
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 path uses a synthetic agent definition that’s never registered in the built-in agent list:

src/tools/AgentTool/forkSubagent.ts
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 BuiltInAgentDefinition

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 — buildForkedMessages
export 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 child

This maximizes cache hits because only the final text block differs between fork children.

Each fork child receives strict instructions via buildChildMessage():

src/tools/AgentTool/forkSubagent.ts
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 steps
3. 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>

Fork children keep the Agent tool in their tool pool (for cache-identical tool definitions), so recursive forking must be blocked at call time:

src/tools/AgentTool/forkSubagent.ts
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.

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 agent
working in ${parentCwd}. You are operating in an isolated git worktree at
${worktreeCwd} — same repository, same relative file structure, separate
working copy. Paths in the inherited context refer to the parent's working
directory; translate them to your worktree root. Re-read files before editing
if the parent may have modified them since they appear in the context.`
}
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).

The fork path activates when all of these conditions are true:

  1. FORK_SUBAGENT feature flag is enabled
  2. Not in coordinator mode
  3. Not in a non-interactive (SDK) session
  4. The subagent_type parameter is omitted from the Agent tool call
  5. 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.