“任何足够复杂的 AI 系统,最终都会重新发明操作系统。”
当一个 Agent 面对庞大的代码库需要同时研究架构、修改代码、运行测试时,单一上下文窗口的局限性就会暴露无遗。Claude Code 的回答不是让单个 Agent 变得更强,而是让多个 Agent 协作——如同一个工程团队,各司其职,并行推进。
本章深入分析 Claude Code 的多 Agent 协调架构,从最基础的父子 Agent 派生,到 Coordinator 中心调度模式,再到 Swarm 去中心化协作,揭示这些算法背后的核心设计思想。
8.1 问题引入:为什么需要多 Agent?
考虑一个典型场景:用户要求"修复认证模块中的空指针异常"。一个优秀的 Agent 需要:
- 研究阶段:搜索代码库找到相关文件,理解类型定义,分析调用链
- 实现阶段:修改代码,添加空值检查
- 验证阶段:运行测试,检查类型,确保不引入新问题
单个 Agent 执行这些步骤意味着上下文窗口中充斥着大量搜索结果和中间产物,导致两个核心问题:
- 上下文污染:研究阶段产生的探索性信息会干扰实现阶段的决策精度
- 串行瓶颈:研究代码结构和研究测试覆盖本可以并行进行,却只能排队执行
Claude Code 的解决方案是任务分解与并行执行——将复杂任务拆分为独立的子任务,由专门的子 Agent 并行完成,最终汇总结果。这种设计需要回答三个核心问题:
- 如何派生子 Agent 并传递恰当的上下文?
- 如何在多个 Agent 之间协调任务、同步状态?
- 如何保证资源隔离,避免 Agent 之间互相干扰?
8.2 Agent 派生算法
8.2.1 AgentTool:派生的入口
在 Claude Code 中,一切 Agent 派生都始于 AgentTool。当主 Agent(父 Agent)决定需要子 Agent 协助时,它会调用 AgentTool 工具,指定子 Agent 的类型、任务描述和所需的 prompt。
源码中 runAgent 函数是 Agent 派生的核心引擎。它的签名揭示了设计意图:
// 简化的 runAgent 签名(源自 AgentTool/runAgent.ts)
async function* runAgent({
agentDefinition, // Agent 定义:类型、工具集、系统提示
promptMessages, // 任务指令
toolUseContext, // 父 Agent 的工具使用上下文
isAsync, // 同步 or 异步执行
forkContextMessages, // 可选:从父 Agent 继承的对话历史
allowedTools, // 工具权限白名单
worktreePath, // 可选:隔离的 git worktree 路径
...
}): AsyncGenerator<Message, void>
这是一个 AsyncGenerator——它逐条 yield 子 Agent 产生的消息,让父 Agent 可以实时消费结果。这个设计比返回最终结果更灵活:同步 Agent 可以流式展示进度,异步 Agent 可以在后台运行并最终通过通知报告结果。
8.2.2 上下文传递的三种模式
Agent 派生中最微妙的决策是:子 Agent 应该继承多少父 Agent 的上下文?Claude Code 提供了三种模式:
模式一:全新上下文(Clean Slate)
子 Agent 从空白状态开始,仅接收任务 prompt。适用于独立任务,如代码搜索。
// 默认模式:不传递 forkContextMessages
const contextMessages: Message[] = forkContextMessages
? filterIncompleteToolCalls(forkContextMessages)
: [] // 空数组 → 干净的上下文
模式二:继承上下文(Context Forking)
子 Agent 继承父 Agent 的完整对话历史。适用于需要理解前因后果的跟进任务。
// Fork 模式:传递过滤后的父对话历史
const contextMessages = filterIncompleteToolCalls(forkContextMessages)
const initialMessages = [...contextMessages, ...promptMessages]
注意 filterIncompleteToolCalls 的存在——它过滤掉没有对应 tool_result 的 tool_use 消息。这是一个关键的防御性设计:如果父 Agent 在中途派生子 Agent,可能存在尚未完成的工具调用,将这些不完整的消息传递给子 Agent 会导致 API 错误。
模式三:Fork 子 Agent(Prompt Cache 共享)
这是最精巧的模式。Fork 子 Agent 继承父 Agent 的全部上下文,并且刻意保持 API 请求前缀字节一致,以共享 prompt cache:
// forkSubagent.ts - 构建 Fork 消息
function buildForkedMessages(
directive: string,
assistantMessage: AssistantMessage,
): Message[] {
// 1. 保留完整的 assistant 消息(包含所有 tool_use 块)
const fullAssistantMessage = { ...assistantMessage, uuid: randomUUID() }
// 2. 为每个 tool_use 生成统一的占位符 tool_result
const toolResultBlocks = toolUseBlocks.map(block => ({
type: 'tool_result',
tool_use_id: block.id,
content: [{ type: 'text', text: 'Fork started — processing in background' }],
}))
// 3. 拼接:统一占位符 + 本 fork 独有的 directive
return [fullAssistantMessage, createUserMessage({
content: [...toolResultBlocks, { type: 'text', text: buildChildMessage(directive) }],
})]
}
这里的算法巧思在于:所有 Fork 子 Agent 共享同一个占位符 tool_result,差异仅在最后一个 text block(各自的 directive)。这确保了 API 请求的前缀部分完全一致,最大化 prompt cache 命中率。考虑到 prompt cache 可以节省大量 token 成本,这是一个极具实际价值的优化。
8.2.3 子 Agent 上下文隔离
createSubagentContext 函数是资源隔离的核心。它为每个子 Agent 创建独立的 ToolUseContext,精确控制哪些状态共享、哪些隔离:
// forkedAgent.ts - 简化的子 Agent 上下文创建
function createSubagentContext(
parentContext: ToolUseContext,
overrides?: SubagentContextOverrides,
): ToolUseContext {
return {
// ---- 隔离的状态 ----
readFileState: cloneFileStateCache(parentContext.readFileState), // 克隆文件缓存
nestedMemoryAttachmentTriggers: new Set<string>(), // 全新的触发器集合
toolDecisions: undefined, // 独立的决策记录
contentReplacementState: cloneContentReplacementState(...), // 克隆内容替换状态
// ---- 独立的 ID 和控制 ----
agentId: overrides?.agentId ?? createAgentId(), // 每个 Agent 有唯一 ID
abortController: createChildAbortController(parentContext.abortController),
// ---- 可选共享的回调 ----
setAppState: overrides?.shareSetAppState
? parentContext.setAppState
: () => {}, // 默认是 no-op
// ---- 任务注册始终共享 ----
setAppStateForTasks: parentContext.setAppStateForTasks ?? parentContext.setAppState,
// ---- UI 回调全部隔离 ----
addNotification: undefined,
setToolJSX: undefined,
}
}
这里体现了几个重要的设计决策:
文件状态缓存采用克隆:子 Agent 读取的文件状态不应影响父 Agent 的缓存,反之亦然。这避免了并行 Agent 之间的缓存混淆。
AbortController 采用父子链接:
createChildAbortController创建一个链接到父控制器的子控制器——父 Agent 被中断时,子 Agent 自动中断;但子 Agent 被中断不影响父 Agent。这是经典的级联取消模式。setAppState 默认为 no-op:异步子 Agent 默认不能修改全局 AppState,防止并行 Agent 产生竞态条件。但
setAppStateForTasks始终共享——任务注册和后台 shell 管理必须触达全局状态,否则会留下僵尸进程。UI 回调全部为 undefined:子 Agent 无法直接操控父 Agent 的 UI,避免多个 Agent 同时争夺 UI 控制权。
8.2.4 内置 Agent 类型体系
Claude Code 预定义了一组内置 Agent 类型,形成了一个"角色分工"系统:
// builtInAgents.ts - 内置 Agent 类型
function getBuiltInAgents(): AgentDefinition[] {
if (isCoordinatorMode()) {
return getCoordinatorAgents() // Coordinator 模式有专门的 Agent 集
}
const agents = [
GENERAL_PURPOSE_AGENT, // 通用 Agent:可读可写,执行多步骤任务
STATUSLINE_SETUP_AGENT, // 状态栏配置 Agent
]
if (areExplorePlanAgentsEnabled()) {
agents.push(EXPLORE_AGENT, PLAN_AGENT) // 只读搜索 Agent 和规划 Agent
}
return agents
}
其中 EXPLORE_AGENT 的定义最能体现"角色隔离"思想:
const EXPLORE_AGENT: BuiltInAgentDefinition = {
agentType: 'Explore',
disallowedTools: [AGENT_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME, ...],
model: 'haiku', // 用更快更便宜的模型
omitClaudeMd: true, // 省略 CLAUDE.md 项目配置
getSystemPrompt: () => '... READ-ONLY MODE - NO FILE MODIFICATIONS ...',
}
Explore Agent 被严格限制为只读模式:禁止所有写入工具,使用更小更快的模型(Haiku),甚至省略 CLAUDE.md 配置文件(因为只读搜索不需要了解提交规范和代码风格)。这种"最小权限"设计节省了 token 开销(仅 CLAUDE.md 的省略就在全量用户中节省了每周数十亿 token),同时确保搜索 Agent 不会意外修改文件。
8.3 Coordinator 调度算法
8.3.1 Coordinator 模式概述
当任务复杂度超过单个 Agent 的能力范围时,Claude Code 提供了一种更高级的协调模式——Coordinator 模式。在这种模式下,主 Agent 不再直接执行任务,而是作为"调度员"(Coordinator),将任务分解并分配给多个 Worker Agent 并行执行。
// coordinatorMode.ts
function isCoordinatorMode(): boolean {
return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
}
Coordinator 模式通过环境变量激活,一旦进入该模式,整个系统的行为发生根本性变化。Coordinator 的系统提示明确定义了其角色:
你是一个协调者(coordinator)。你的工作是:
- 帮助用户实现目标
- 指挥 Worker 进行研究、实现和验证代码变更
- 综合结果并与用户沟通
- 在可以直接回答时不要委派工作
8.3.2 Coordinator 的工具集
Coordinator 的工具集被严格限制,它不能直接读写文件或执行命令:
// coordinatorMode.ts - Coordinator 可用的工具
const coordinatorTools = [
AGENT_TOOL_NAME, // 派生新 Worker
SEND_MESSAGE_TOOL_NAME, // 向已有 Worker 发送消息
TASK_STOP_TOOL_NAME, // 停止运行中的 Worker
]
这种"只协调、不执行"的设计迫使 Coordinator 将所有实际工作委派给 Worker,保持了架构的清晰分层。Coordinator 甚至被告知:“Workers 无法看到你的对话。每个 prompt 必须是自包含的,包含 Worker 所需的一切信息。”
8.3.3 任务分解的四阶段模型
Coordinator 的系统提示中定义了一套严谨的任务分解工作流:
| 阶段 | 执行者 | 目的 |
|---|---|---|
| 研究(Research) | Worker(并行) | 调查代码库,理解问题 |
| 综合(Synthesis) | Coordinator | 阅读研究结果,制定实现方案 |
| 实现(Implementation) | Worker | 按方案修改代码,提交 |
| 验证(Verification) | Worker | 测试变更的正确性 |
最关键的是 综合阶段——Coordinator 必须亲自理解 Worker 的研究结果,然后写出具体的实现规格,而不是简单地"转发"。这是系统提示中明确反对的反模式:
// 反模式 — 惰性委派
Agent({ prompt: "Based on your findings, fix the auth bug" })
// 正确 — 综合后的具体规格
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'." })
8.3.4 并发控制策略
Coordinator 遵循精确的并发控制规则:
- 只读任务(研究):自由并行,多个 Worker 可以同时搜索代码
- 写入任务(实现):同一组文件上同一时间只有一个 Worker
- 验证任务:可以与不同文件区域的实现任务并行
这种分级并发模型避免了多个 Worker 同时修改相同文件导致的冲突,同时最大化了并行度。
8.3.5 Worker 续命与重用
Coordinator 不是每次都创建新 Worker,而是通过 SendMessageTool 复用已有 Worker:
// SendMessage 的核心路由逻辑
if (typeof input.message === 'string' && input.to !== '*') {
const task = appState.tasks[agentId]
if (isLocalAgentTask(task) && task.status === 'running') {
// Worker 仍在运行 → 排队消息
queuePendingMessage(agentId, input.message, setAppState)
return { success: true, message: 'Message queued for delivery' }
}
// Worker 已停止 → 从磁盘 transcript 恢复
const result = await resumeAgentBackground({
agentId,
prompt: input.message,
toolUseContext: context,
})
return { success: true, message: 'Agent resumed with your message' }
}
这里有两个重要机制:
消息排队:如果 Worker 正在执行中,新消息被排队,等 Worker 当前轮次结束后在下一轮注入。这避免了中断 Worker 的当前工作。
磁盘恢复:如果 Worker 已停止(完成或失败),系统可以从磁盘上的 sidechain transcript 恢复它的完整对话历史,然后以新消息继续执行。这意味着 Worker 的上下文不会因为停止而丢失。
8.4 Swarm 并行架构
8.4.1 从 Agent 到 Team
当需要更持久、更自主的多 Agent 协作时,Claude Code 提供了 Swarm 架构。与 Coordinator 模式中 Worker 是短暂的不同,Swarm 中的 Teammate 是独立的长期运行进程,拥有自己的终端窗口和完整的 Claude Code 实例。
创建 Team 是 Swarm 的第一步:
// TeamCreateTool.ts - 创建 Team
async call(input, context) {
const teamFile: TeamFile = {
name: finalTeamName,
description: _description,
createdAt: Date.now(),
leadAgentId, // Team Leader 的 ID
leadSessionId: getSessionId(),
members: [{
agentId: leadAgentId,
name: TEAM_LEAD_NAME, // "team-lead"
joinedAt: Date.now(),
cwd: getCwd(),
subscriptions: [],
}],
}
await writeTeamFileAsync(finalTeamName, teamFile)
registerTeamForSessionCleanup(finalTeamName) // 会话结束时清理
await resetTaskList(taskListId) // 任务编号从 1 开始
}
Team 的元数据持久化到磁盘文件中(~/.claude/teams/{team_name}/team.json),使得 Team 的状态可以跨进程存活。
8.4.2 Teammate 派生的三种后端
spawnMultiAgent.ts 中的 handleSpawn 函数揭示了 Teammate 派生的三种后端策略:
// spawnMultiAgent.ts
async function handleSpawn(input, context) {
// 策略 1:In-Process(同进程)
if (isInProcessEnabled()) {
return handleSpawnInProcess(input, context)
}
// 策略 2:尝试面板后端(tmux 或 iTerm2)
try {
await detectAndGetBackend()
} catch (error) {
// 策略 3:面板不可用时回退到 In-Process
markInProcessFallback()
return handleSpawnInProcess(input, context)
}
// 使用分屏或独立窗口
return input.use_splitpane !== false
? handleSpawnSplitPane(input, context)
: handleSpawnSeparateWindow(input, context)
}
In-Process 后端:Teammate 在同一个 Node.js 进程内运行,通过 AsyncLocalStorage 实现上下文隔离。这是最轻量的方式,无需外部依赖。
Tmux 后端:每个 Teammate 在 tmux 的一个独立 pane 中运行,拥有完整的 Claude Code 进程。Leader 在左侧面板,Teammate 在右侧面板,用户可以直观地看到每个 Agent 的工作状态。
iTerm2 后端:与 Tmux 类似,但使用 iTerm2 的原生分屏功能,提供更好的 macOS 集成体验。
无论使用哪种后端,Teammate 的身份信息都通过 CLI 参数传递:
const teammateArgs = [
`--agent-id ${quote([teammateId])}`,
`--agent-name ${quote([sanitizedName])}`,
`--team-name ${quote([teamName])}`,
`--agent-color ${quote([teammateColor])}`,
`--parent-session-id ${quote([getSessionId()])}`,
]
每个 Teammate 拥有确定性的 ID(格式为 agentName@teamName)、唯一的颜色标识(用于 UI 区分),以及对父会话的引用。
8.4.3 基于 Mailbox 的消息传递
Swarm 架构中,Agent 间通信采用 文件系统 Mailbox 机制——一种经典的异步消息队列模式:
文件布局:
~/.claude/teams/{team_name}/inboxes/
├── team-lead.json # Leader 的收件箱
├── researcher.json # Researcher 的收件箱
└── implementer.json # Implementer 的收件箱
每个 Agent 的收件箱是一个 JSON 文件,包含消息数组:
// teammateMailbox.ts
type TeammateMessage = {
from: string // 发送者名称
text: string // 消息内容
timestamp: string // 时间戳
read: boolean // 是否已读
color?: string // 发送者颜色
summary?: string // UI 预览摘要
}
写入消息使用文件锁保证并发安全:
async function writeToMailbox(
recipientName: string,
message: Omit<TeammateMessage, 'read'>,
teamName?: string,
) {
const inboxPath = getInboxPath(recipientName, teamName)
// 使用带重试的文件锁,防止多个 Agent 同时写入
const LOCK_OPTIONS = {
retries: { retries: 10, minTimeout: 5, maxTimeout: 100 },
}
// ... 加锁、读取、追加、写入、释放锁
}
这种基于文件系统的消息传递看似"原始",但有几个重要优势:
- 零外部依赖:不需要 Redis、RabbitMQ 等消息中间件
- 跨进程通信:tmux 后端的 Teammate 是独立进程,文件是天然的 IPC 通道
- 持久化:消息自动持久化到磁盘,进程崩溃后不丢失
- 可调试:可以直接查看 JSON 文件来调试通信问题
8.4.4 广播与结构化消息
SendMessageTool 支持三种消息路由模式:
// 点对点消息
SendMessage({ to: "researcher", message: "查找 auth 模块的入口点" })
// 广播消息
SendMessage({ to: "*", message: "所有人暂停,需求变更了" })
// 结构化消息(用于协议级通信)
SendMessage({ to: "worker-1", message: {
type: "shutdown_request",
reason: "任务已完成"
}})
结构化消息支持关闭请求/响应协议和计划审批流程,使得 Swarm 具备了进程生命周期管理的能力。
8.5 结果合并与通知机制
8.5.1 同步 Agent 的流式合并
同步 Agent 的结果通过 AsyncGenerator 实时流回父 Agent:
// runAgent.ts - 同步 Agent 的消息流
for await (const message of query({
messages: initialMessages,
systemPrompt: agentSystemPrompt,
toolUseContext: agentToolUseContext,
maxTurns: maxTurns ?? agentDefinition.maxTurns,
})) {
if (isRecordableMessage(message)) {
// 1. 持久化到 sidechain transcript(磁盘)
await recordSidechainTranscript([message], agentId, lastRecordedUuid)
// 2. yield 给父 Agent
yield message
}
}
父 Agent 的 AgentTool 实现会收集所有 yield 的消息,通过 finalizeAgentTool 提取最终结果:
function finalizeAgentTool(agentMessages, agentId, metadata): AgentToolResult {
const lastAssistantMessage = getLastAssistantMessage(agentMessages)
return {
agentId,
content: lastAssistantMessage.message.content.filter(b => b.type === 'text'),
totalToolUseCount: countToolUses(agentMessages),
totalDurationMs: Date.now() - metadata.startTime,
totalTokens: getTokenCountFromUsage(lastAssistantMessage.message.usage),
usage: lastAssistantMessage.message.usage,
}
}
8.5.2 异步 Agent 的通知机制
异步 Agent 的结果通过 <task-notification> XML 消息注入父 Agent 的对话流:
<task-notification>
<task-id>agent-a1b2c3</task-id>
<status>completed</status>
<summary>Agent "Investigate auth bug" completed</summary>
<result>Found null pointer in src/auth/validate.ts:42...</result>
<usage>
<total_tokens>15000</total_tokens>
<tool_uses>8</tool_uses>
<duration_ms>12500</duration_ms>
</usage>
</task-notification>
这些通知以 user 角色消息的形式注入,使父 Agent(或 Coordinator)能够根据结果决定下一步行动。runAsyncAgentLifecycle 函数管理整个异步生命周期:
// agentToolUtils.ts - 简化的异步 Agent 生命周期
async function runAsyncAgentLifecycle({ taskId, makeStream, ... }) {
try {
for await (const message of makeStream(onCacheSafeParams)) {
agentMessages.push(message)
updateAsyncAgentProgress(taskId, getProgressUpdate(tracker), rootSetAppState)
}
// 成功完成
const agentResult = finalizeAgentTool(agentMessages, taskId, metadata)
completeAsyncAgent(agentResult, rootSetAppState)
enqueueAgentNotification({ taskId, status: 'completed', finalMessage, ... })
} catch (error) {
if (error instanceof AbortError) {
// 被用户或 Coordinator 中止
killAsyncAgent(taskId, rootSetAppState)
enqueueAgentNotification({ taskId, status: 'killed', ... })
} else {
// 执行失败
failAsyncAgent(taskId, errorMessage(error), rootSetAppState)
enqueueAgentNotification({ taskId, status: 'failed', error: msg, ... })
}
}
}
注意三种终态:completed(成功)、killed(被中止)、failed(失败)。被中止的 Agent 还会尝试提取部分结果(extractPartialResult),让 Coordinator 了解 Agent 在中止前完成了什么工作。
8.6 资源隔离与生命周期管理
8.6.1 工具权限隔离
不同类型的 Agent 拥有不同的工具权限。filterToolsForAgent 函数实现了精确的权限控制:
function filterToolsForAgent({ tools, isBuiltIn, isAsync, permissionMode }) {
return tools.filter(tool => {
if (tool.name.startsWith('mcp__')) return true // MCP 工具始终允许
if (ALL_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false // 全局禁止列表
if (!isBuiltIn && CUSTOM_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
if (isAsync && !ASYNC_AGENT_ALLOWED_TOOLS.has(tool.name)) return false
return true
})
}
异步 Agent 的工具集被严格限制在 ASYNC_AGENT_ALLOWED_TOOLS 白名单中,防止后台 Agent 执行需要用户交互的操作。自定义 Agent(用户定义的)则有额外的工具限制,防止用户定义的 Agent 执行敏感操作。
8.6.2 权限模式继承
Agent 的权限模式(permission mode)遵循一套严格的继承规则:
// runAgent.ts - 权限模式覆盖逻辑
const agentGetAppState = () => {
const state = toolUseContext.getAppState()
// 父 Agent 的 bypassPermissions 或 acceptEdits 始终优先
if (agentPermissionMode &&
state.toolPermissionContext.mode !== 'bypassPermissions' &&
state.toolPermissionContext.mode !== 'acceptEdits') {
toolPermissionContext = { ...toolPermissionContext, mode: agentPermissionMode }
}
// 异步 Agent 默认不显示权限提示(自动拒绝)
if (isAsync && !canShowPermissionPrompts) {
toolPermissionContext = {
...toolPermissionContext,
shouldAvoidPermissionPrompts: true
}
}
}
这确保了安全模式不会被子 Agent 降级:如果父 Agent 在 bypassPermissions 模式(已获得用户完全信任),子 Agent 继承这种信任;如果父 Agent 在常规模式,子 Agent 可以有自己的权限模式,但不能超越父 Agent 的安全级别。
8.6.3 完善的清理机制
Agent 生命周期结束时,runAgent 的 finally 块确保所有资源被正确释放:
finally {
await mcpCleanup() // 清理 Agent 专属 MCP 服务器
clearSessionHooks(rootSetAppState, agentId) // 清理会话钩子
cleanupAgentTracking(agentId) // 清理 prompt cache 追踪
agentToolUseContext.readFileState.clear() // 释放文件缓存
initialMessages.length = 0 // 释放消息数组
unregisterPerfettoAgent(agentId) // 释放 Perfetto 追踪条目
clearAgentTranscriptSubdir(agentId) // 释放 transcript 子目录映射
// 释放 todos 条目,防止内存泄漏
rootSetAppState(prev => {
if (!(agentId in prev.todos)) return prev
const { [agentId]: _removed, ...todos } = prev.todos
return { ...prev, todos }
})
// 杀死后台 shell 任务,防止僵尸进程
killShellTasksForAgent(agentId, getAppState, rootSetAppState)
}
特别值得注意的是 killShellTasksForAgent——如果子 Agent 通过 run_in_background 启动了后台 shell 命令,这些进程会在 Agent 结束时被清理。没有这个机制,长时间运行的会话可能会累积大量 PPID=1 的孤儿进程。
8.7 架构图解
Claude Code 的多 Agent 协调形成了三种拓扑结构:
┌─────────────────────────────────────────────────────┐
│ 拓扑一:星形(Agent 派生) │
│ │
│ ┌──────────┐ │
│ │ 主 Agent │ │
│ └──┬───┬───┘ │
│ ┌────┘ └────┐ │
│ ┌──────▼──┐ ┌──────▼──┐ │
│ │ Explore │ │ General │ │
│ │ (只读) │ │ Purpose │ │
│ └─────────┘ └─────────┘ │
│ 同步返回结果 同步返回结果 │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 拓扑二:星形+异步(Coordinator 模式) │
│ │
│ ┌─────────────┐ │
│ │ Coordinator │ │
│ └──┬───┬───┬──┘ │
│ ┌────┘ │ └────┐ │
│ ┌──────▼──┐ ┌───▼────┐ ┌─▼────────┐ │
│ │Worker-1 │ │Worker-2│ │ Worker-3 │ │
│ │(研究) │ │(研究) │ │(实现) │ │
│ └────┬────┘ └───┬────┘ └────┬──────┘ │
│ │ │ │ │
│ <task-notification> 异步通知回 Coordinator │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 拓扑三:网状(Swarm 架构) │
│ │
│ ┌──────────┐ Mailbox ┌──────────┐ │
│ │ Leader │◄─────────────►│Teammate-1│ │
│ │(team-lead)│ │(researcher) │
│ └──┬───┬───┘ └──────────┘ │
│ │ │ Mailbox ┌──────────┐ │
│ │ └──────────────────►│Teammate-2│ │
│ │ │(implementer) │
│ │ Mailbox └─────┬────┘ │
│ └──────────────────┐ │ │
│ ┌─────▼────┐ │ Mailbox │
│ │Teammate-3│◄────┘ │
│ │(verifier) │ │
│ └──────────┘ │
│ 各 Agent 独立进程,通过文件 Mailbox 通信 │
└─────────────────────────────────────────────────────┘
三种拓扑对应不同的复杂度级别:
- 星形同步:简单任务分解,父 Agent 同步等待子 Agent 结果
- 星形异步:Coordinator 并行派发任务,通过通知收集结果
- 网状:完全去中心化,Teammate 之间可以直接通信(包括广播)
8.8 思考题
思考题 1:Fork 子 Agent 的 prompt cache 共享策略
buildForkedMessages 函数为所有 Fork 子 Agent 生成统一的 tool_result 占位符,差异仅在最后一个 text block。请思考:如果某个 Fork 子 Agent 需要额外的上下文信息(比如特定文件的内容),如何在不破坏 prompt cache 共享的前提下传递?是否可以借鉴"差分编码"的思想?
思考题 2:Mailbox 的一致性保证
当前 Mailbox 使用文件锁保证并发写入安全,但没有使用分布式事务。在以下场景中可能出现什么问题:Leader 同时向 3 个 Teammate 发送任务,其中一个 Teammate 的 Mailbox 写入失败?系统如何保证"要么全部送达,要么全部回滚"?在 Claude Code 的设计中,为什么可以接受"尽力而为"的语义而非严格的事务语义?
思考题 3:Agent 递归深度控制
Fork 子 Agent 通过 isInForkChild 检测对话历史中的 <fork-boilerplate> 标签来防止递归 Fork。但如果对话被 auto-compact(自动压缩)后,标签被移除了怎么办?源码中使用 querySource 作为备用检测机制。请分析这种"多重防御"策略的设计思想,以及它如何平衡安全性和 prompt cache 效率。
8.9 小结
Claude Code 的多 Agent 协调系统并非一个单一的框架,而是一组互补的机制,它们共同解决了"让多个 AI Agent 有效协作"这一核心挑战:
Agent 派生算法通过
runAgent和createSubagentContext提供了精确的上下文传递和状态隔离能力。三种上下文模式(全新、继承、Fork)适配了从简单搜索到复杂实现的各种场景。Coordinator 调度算法实现了一种"只协调不执行"的中心化模式,通过四阶段任务分解(研究、综合、实现、验证)和精确的并发控制,让复杂任务可以被系统化地拆解和并行执行。
Swarm 并行架构通过文件系统 Mailbox 实现了去中心化的 Agent 通信,支持 In-Process、Tmux、iTerm2 三种后端,在轻量级和完全隔离之间提供了灵活选择。
资源管理贯穿整个设计——从工具权限的分层控制,到文件缓存的克隆隔离,到 AbortController 的级联取消,再到
finally块中的全面清理——确保多 Agent 系统在任何终止条件下都不会泄漏资源。
这些设计的共同哲学是:宁可多一层隔离,不可少一重防护。每个 Agent 默认是完全隔离的,只有在明确需要时才选择性地共享状态。这种"默认隔离、显式共享"的原则,是构建可靠多 Agent 系统的关键。
从算法思想的角度,Claude Code 的多 Agent 系统综合运用了操作系统中的进程管理(fork/exec)、消息队列(Mailbox)、级联取消(AbortController 树)和最小权限原则,将这些经典的系统设计范式成功应用于 AI Agent 协调领域。这提醒我们:AI 系统的工程挑战,往往能在传统系统设计中找到成熟的解决方案。