Appearance
第14章 多 Agent 协调与 Swarm
在软件工程任务中,一个复杂的需求往往可以分解为若干彼此独立又相互关联的子任务:调研代码库结构、实现功能变更、运行测试验证、编写提交信息。当这些子任务由单一 Agent 串行执行时,用户不得不等待漫长的上下文轮次;而当其中某个子任务出错时,整个对话的上下文都会被错误信息污染。Claude Code 的多 Agent 协调系统正是为了突破这一瓶颈而设计的——它将一个主 Agent 转变为"协调器"(Coordinator),由协调器将任务分派给多个 Worker Agent 并行执行,最终汇聚结果。
这套系统的设计哲学可以用三个关键词来概括:并行化、专业化、隔离化。并行化让独立的调研任务同时运行;专业化让每个 Worker 专注于自己擅长的领域;隔离化则确保 Worker 之间的上下文不会互相干扰,一个 Worker 的失败不会拖垮整个系统。
本章将从源码层面深入剖析 Claude Code 的多 Agent 协调架构,涵盖协调器模式的激活机制、Worker Agent 的生成与约束、任务类型体系、通信机制、以及并行执行与结果聚合的完整流程。
本章要点
- 协调器模式:理解
coordinatorMode.ts如何将主 Agent 转变为纯粹的任务编排者,以及协调器的系统提示词设计 - Worker 生成:
AgentTool如何根据不同场景生成同步或异步 Worker,Worker 的工具子集约束与递归防护 - 任务类型体系:
LocalShellTask、LocalAgentTask、InProcessTeammateTask、RemoteAgentTask四种任务类型的使用场景与生命周期 - 通信机制:
SendMessageTool的三种消息路由路径——进程内队列、文件信箱系统、跨会话桥接 - Scratchpad 隔离:Worker 之间如何通过 Scratchpad 目录实现持久化的知识共享
- 并行执行:Worker 独立运行查询循环、结果通过
<task-notification>XML 格式汇聚回协调器
14.1 为什么需要多 Agent
14.1.1 单 Agent 的天然局限
在传统的单 Agent 模式下,Claude Code 的所有操作都在一条对话流中串行执行。用户提出需求后,Agent 依次读取文件、分析代码、执行修改、运行测试,每一步都必须等待上一步完成。这种模式存在三个根本性问题。
上下文膨胀。一个复杂任务可能涉及数十个文件的读取和修改,每次工具调用的输入输出都会累积在对话上下文中。当上下文接近模型的窗口限制时,早期的关键信息可能被截断或压缩,导致 Agent "遗忘"已经获取的信息。
错误传播。当 Agent 在执行链的中间环节遇到错误——比如一个测试失败——错误信息和修复尝试会污染后续所有操作的上下文。Agent 可能在错误的修复方向上越陷越深,因为它的全部注意力都被前面的失败尝试所占据。
时间效率低下。调研代码库结构和运行测试验证是两个完全独立的任务,没有任何理由必须串行执行。在单 Agent 模式下,用户不得不等待所有步骤依次完成,即使其中许多步骤本可以同时进行。
14.1.2 多 Agent 的三大优势
Claude Code 的多 Agent 架构从根本上解决了上述问题,其优势体现在三个维度:
并行执行带来的时间收益。协调器可以同时启动多个 Worker 进行独立调研,比如一个 Worker 分析认证模块的源码结构,另一个 Worker 查找相关的测试文件和测试覆盖情况,第三个 Worker 检索项目的 Git 历史以了解相关变更的上下文。这些调研任务之间没有数据依赖,完全可以同时进行。正如源码中协调器系统提示词所强调的:
"Parallelism is your superpower. Workers are async. Launch independent workers concurrently whenever possible -- don't serialize work that can run simultaneously."
在实践中,一个需要调研五个不同模块的任务,在单 Agent 模式下可能需要五轮串行的文件读取和分析;在多 Agent 模式下,五个 Worker 可以同时出发,总耗时接近于最慢的那一个 Worker 的执行时间。
专业化与隔离带来的质量收益。每个 Worker 拥有独立的对话上下文,互不干扰。一个 Worker 在调试过程中产生的大量错误日志和修复尝试,不会污染另一个 Worker 正在进行的代码分析。更重要的是,Worker 的工具集被严格约束——它们不能生成子 Agent,不能访问协调器专有的管理工具,这种约束确保了系统不会出现无限递归。每个 Worker 的上下文都是"干净的",只包含与其任务直接相关的信息,这大大提高了 Worker 对其特定任务的专注度和完成质量。
综合决策带来的准确性收益。协调器可以汇集多个 Worker 的调研结果,从全局视角做出判断。当一个 Worker 报告了 bug 的具体位置和类型特征,另一个 Worker 报告了测试覆盖情况和缺失的边界测试用例,第三个 Worker 报告了相关模块的依赖关系,协调器能够综合这些来自不同视角的信息,制定出完整而精确的修复方案,然后再派发给实现 Worker。这种"分散调研、集中决策"的模式,比单 Agent 的"边调研边决策"模式更不容易遗漏关键信息。
14.2 协调器模式
下图展示了 Coordinator 模式下多 Agent 协作的完整架构,从任务分派到结果聚合的全流程:
14.2.1 模式激活与切换
协调器模式的激活逻辑定义在 src/coordinator/coordinatorMode.ts 中。整个机制基于环境变量和特性标志的组合判断:
typescript
// 源码文件:src/coordinator/coordinatorMode.ts
export function isCoordinatorMode(): boolean {
if (feature('COORDINATOR_MODE')) {
return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
}
return false
}这里有两层控制,形成了一个"编译时+运行时"的双重开关机制。第一层 feature('COORDINATOR_MODE') 是编译时特性标志,由 Bun 的 bundle 系统在构建阶段决定是否包含协调器相关代码。如果特性未启用,整个协调器分支在打包时就会被死代码消除,这意味着发布给普通用户的二进制文件中根本不包含协调器的任何代码,既减小了包体积,也避免了意外激活实验性功能的可能。第二层 CLAUDE_CODE_COORDINATOR_MODE 环境变量是运行时开关,允许在不重新构建的情况下启用或禁用协调器模式,适合在灰度测试期间按用户或按环境逐步开放。
当恢复一个已有的会话时,matchSessionMode 函数确保当前模式与会话存储的模式一致:
typescript
// 源码文件: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
}
// 翻转环境变量以匹配会话模式
if (sessionIsCoordinator) {
process.env.CLAUDE_CODE_COORDINATOR_MODE = '1'
} else {
delete process.env.CLAUDE_CODE_COORDINATOR_MODE
}
// ...
}这个设计解决了一个实际问题:用户可能在协调器模式下创建了一个会话,然后在普通模式下恢复它。如果不进行模式匹配,恢复的会话中包含的 Worker 通知消息会让普通模式的 Agent 困惑不已。
14.2.2 协调器的系统提示词
当协调器模式激活后,getCoordinatorSystemPrompt() 函数会返回一份精心设计的系统提示词,完全替代普通 Agent 的系统提示词。这份提示词是整个多 Agent 系统的"灵魂"——它定义了协调器的角色、可用工具、工作流程和交互规范:
typescript
// 源码文件:src/coordinator/coordinatorMode.ts
export function getCoordinatorSystemPrompt(): string {
return `You are Claude Code, an AI assistant that orchestrates
software engineering tasks across multiple workers.
## 1. Your Role
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
## 2. Your Tools
- **Agent** - Spawn a new worker
- **SendMessage** - Continue an existing worker
- **TaskStop** - Stop a running worker
...`
}提示词的设计体现了几个关键的架构决策。
严格的角色分离。协调器被明确告知"Every message you send is to the user",Worker 的结果是"internal signals, not conversation partners"。这避免了协调器错误地将 Worker 当作对话伙伴来"感谢"或"确认"。
任务阶段化。提示词定义了四个标准阶段:Research(调研)、Synthesis(综合)、Implementation(实现)、Verification(验证),并明确了哪些阶段由 Worker 执行、哪些由协调器自己完成。特别强调 Synthesis 阶段"是协调器最重要的工作"——协调器必须理解调研结果后再制定具体方案,不能懒惰地把理解工作推给 Worker。
并发管理规则。提示词明确规定了并发策略:只读任务(调研)可以自由并行;写操作(实现)同一组文件一次只能一个 Worker;验证可以与不同文件区域的实现并行。这些规则防止多个 Worker 同时修改同一个文件导致冲突。
14.2.3 Worker 工具上下文注入
协调器模式还通过 getCoordinatorUserContext 向用户上下文中注入 Worker 可用工具的信息,让协调器了解 Worker 的能力范围:
typescript
// 源码文件: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(', ')
// ...
}这里可以看到两种截然不同的工具配置模式。CLAUDE_CODE_SIMPLE 模式下,Worker 只有最基本的 Bash、Read、Edit 三个工具——这是一种极简配置,适用于对安全性和可预测性要求极高的场景,Worker 只能执行最原始的文件操作和命令执行。正常模式下,Worker 拥有 ASYNC_AGENT_ALLOWED_TOOLS 集合中除内部管理工具外的所有工具,包括文件搜索、代码搜索、网络检索、笔记本编辑等丰富的能力。
被排除的 INTERNAL_WORKER_TOOLS 是一个值得特别关注的集合。它包括 TeamCreate(创建团队)、TeamDelete(删除团队)、SendMessage(发送消息)和 SyntheticOutput(结构化输出)四种工具。这些工具是 Swarm 团队管理和结构化输出的内部机制,不应暴露给普通 Worker。协调器在告诉 Worker 自己可用的工具时,会将这些内部工具从列表中过滤掉,确保 Worker 不会尝试调用它们。
如果存在 Scratchpad 目录,还会注入 Scratchpad 的路径信息:
typescript
if (scratchpadDir && isScratchpadGateEnabled()) {
content += `\n\nScratchpad directory: ${scratchpadDir}
Workers can read and write here without permission prompts.
Use this for durable cross-worker knowledge.`
}这为 Worker 之间提供了一种持久化的知识共享通道——任何 Worker 都可以在 Scratchpad 目录中留下文件,供后续的 Worker 读取。
14.3 Worker Agent 生成
14.3.1 AgentTool 的路由逻辑
Worker 的生成由 AgentTool(src/tools/AgentTool/AgentTool.tsx)负责,这是整个多 Agent 系统最核心的工具。它的 call 方法包含了复杂的路由逻辑,根据输入参数的组合决定走哪条路径:
typescript
// 源码文件:src/tools/AgentTool/AgentTool.tsx
async call({
prompt, subagent_type, description, model: modelParam,
run_in_background, name, team_name, mode: spawnMode,
isolation, cwd
}: AgentToolInput, toolUseContext, canUseTool, ...) {
// 路径1:团队 Teammate 生成(name + team_name 都存在)
if (teamName && name) {
const result = await spawnTeammate({ name, prompt, ... });
return { data: spawnResult };
}
// 路径2:Fork 子代理(特性标志启用时)
const isForkPath = effectiveType === undefined;
if (isForkPath) {
selectedAgent = FORK_AGENT;
}
// 路径3:常规 Agent 生成
// 异步或同步取决于多个条件的组合
const shouldRunAsync = (run_in_background === true
|| selectedAgent.background === true
|| isCoordinator || forceAsync || assistantForceAsync)
&& !isBackgroundTasksDisabled;
}三条路径对应三种截然不同的使用场景,每种路径在隔离级别、上下文继承和能力范围上都有本质区别:
- Teammate 路径:当
name和team_name参数同时存在时,生成一个具名的团队成员。团队成员拥有持久的身份标识(格式为name@team_name),可以通过 SendMessage 互相通信,支持空闲等待和持续任务认领。这是 Swarm 模式的核心路径,生成的 Teammate 可以在 tmux 分屏或进程内方式运行,并注册到团队文件(Team File)中供其他成员发现。 - Fork 路径:当 fork 特性启用且未指定
subagent_type时,创建一个继承父 Agent 完整上下文的分支 Worker。Fork Worker 的特殊之处在于它使用父 Agent 的系统提示词和工具集,并且通过buildForkedMessages()复制父 Agent 的完整对话历史作为初始上下文。这种设计确保了 Fork Worker 与父 Agent 共享 prompt cache,大幅降低了 API 成本。代价是 Fork Worker 携带了父 Agent 的全部上下文"包袱",不如独立 Worker 那样"轻装上阵"。 - 常规 Agent 路径:最通用的路径,根据
subagent_type选择预定义的 Agent 定义(如worker、researcher等),构建独立的系统提示词和工具集。这种 Worker 从零开始,只接收协调器给定的 prompt 作为输入,具有最清晰的上下文边界。在协调器模式下,这是最常用的路径。
一个重要的安全防护是递归检测。系统通过两重机制防止 Fork Worker 无限递归地生成子 Fork:一是检查 querySource 是否标记为 fork agent 类型,二是扫描上下文消息中是否包含 fork 标记。如果检测到递归,直接抛出错误:"Fork is not available inside a forked worker."
14.3.2 异步 Agent 的生命周期
在协调器模式下,几乎所有 Worker 都走异步路径。异步 Agent 的生命周期管理是一个精密的流程:
typescript
// 源码文件:src/tools/AgentTool/AgentTool.tsx
if (shouldRunAsync) {
const asyncAgentId = earlyAgentId;
// 步骤1:注册后台任务
const agentBackgroundTask = registerAsyncAgent({
agentId: asyncAgentId,
description,
prompt,
selectedAgent,
setAppState: rootSetAppState,
toolUseId: toolUseContext.toolUseId
});
// 步骤2:注册名称到 agentId 的映射(用于 SendMessage 路由)
if (name) {
rootSetAppState(prev => {
const next = new Map(prev.agentNameRegistry);
next.set(name, asAgentId(asyncAgentId));
return { ...prev, agentNameRegistry: next };
});
}
// 步骤3:后台启动 Agent 执行循环(fire-and-forget)
void runWithAgentContext(asyncAgentContext, () =>
wrapWithCwd(() => runAsyncAgentLifecycle({
taskId: agentBackgroundTask.agentId,
abortController: agentBackgroundTask.abortController!,
makeStream: onCacheSafeParams => runAgent({
...runAgentParams,
override: { agentId: ..., abortController: ... },
onCacheSafeParams
}),
metadata, description, toolUseContext,
rootSetAppState,
enableSummarization: isCoordinator || isForkSubagentEnabled(),
getWorktreeResult: cleanupWorktreeIfNeeded
}))
);
// 步骤4:立即返回任务ID,不等待完成
return {
data: {
status: 'async_launched',
agentId: agentBackgroundTask.agentId,
description, prompt,
outputFile: getTaskOutputPath(agentBackgroundTask.agentId),
}
};
}