Claude Code 源码分析(一):多 Agent 协调器架构 —— 一个工业级 Coordinator-Worker 模式的完整实现

本系列文章基于 Claude Code 2.1.88 版本的 TypeScript 源码进行分析。该源码通过 npm 发布包(@anthropic-ai/claude-code)内附带的 source map 还原,共计 4756 个文件,其中包含 1884 个 .ts/.tsx 源文件。本系列仅用于技术研究,源码版权归 Anthropic 所有。

引言

在 AI Agent 领域,"多 Agent 协作"是当前讨论最多、落地最少的话题之一。多数开源框架停留在概念验证阶段,而 Claude Code 的源码揭示了一套已在生产环境运行的 Coordinator-Worker 多 Agent 架构。本文将从源码层面完整剖析这套系统的设计思路、实现细节与工程取舍。

涉及的核心源码文件:

  • src/coordinator/coordinatorMode.ts ------ 协调器模式的入口与系统提示
  • src/tools/AgentTool/AgentTool.tsx ------ Worker 派生的核心实现
  • src/tools/AgentTool/forkSubagent.ts ------ Fork 子代理机制
  • src/tools/AgentTool/agentToolUtils.ts ------ Agent 工具过滤与生命周期
  • src/tools/AgentTool/runAgent.ts ------ Agent 执行引擎
  • src/tools/SendMessageTool/ ------ Worker 继续执行机制
  • src/tools/TaskStopTool/ ------ Worker 终止机制
  • src/Task.ts ------ 任务状态模型

一、架构总览:Coordinator 与 Worker 的角色分离

Claude Code 的多 Agent 架构将系统分为两个明确的角色:

Coordinator(协调器) 负责理解用户意图、分解任务、综合结果、与用户沟通。协调器本身不执行文件操作或命令执行,它只拥有三个工具:

  • AgentTool ------ 派生新的 Worker
  • SendMessageTool ------ 向已有 Worker 发送后续指令
  • TaskStopTool ------ 终止运行中的 Worker

Worker(工作者) 由协调器异步派生,独立执行研究、实现、验证等具体任务。每个 Worker 拥有独立的上下文、工具集和生命周期。

这种分离在 coordinatorMode.ts 中通过系统提示(System Prompt)进行定义:

typescript 复制代码
export function getCoordinatorSystemPrompt(): string {
  return `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`
}

协调器模式通过环境变量 CLAUDE_CODE_COORDINATOR_MODE 控制开关,并支持会话恢复时的模式匹配------如果恢复的会话是协调器模式,当前环境会自动切换:

typescript 复制代码
export function matchSessionMode(
  sessionMode: 'coordinator' | 'normal' | undefined,
): string | undefined {
  if (sessionIsCoordinator) {
    process.env.CLAUDE_CODE_COORDINATOR_MODE = '1'
  } else {
    delete process.env.CLAUDE_CODE_COORDINATOR_MODE
  }
}

二、任务模型:从派生到终止的完整生命周期

2.1 任务类型与状态

Task.ts 定义了任务的类型体系和状态机:

typescript 复制代码
export type TaskType =
  | 'local_bash'           // 本地 Shell 任务
  | 'local_agent'          // 本地 Agent 子任务
  | 'remote_agent'         // 远程 Agent(CCR 环境)
  | 'in_process_teammate'  // 进程内协作者
  | 'local_workflow'       // 本地工作流
  | 'monitor_mcp'          // MCP 监控任务
  | 'dream'                // 后台推理任务

export type TaskStatus =
  | 'pending' | 'running' | 'completed' | 'failed' | 'killed'

状态转换是单向的。isTerminalTaskStatus 函数用于判断任务是否已进入终态,防止向已结束的 Worker 注入消息:

typescript 复制代码
export function isTerminalTaskStatus(status: TaskStatus): boolean {
  return status === 'completed' || status === 'failed' || status === 'killed'
}

2.2 任务 ID 生成

任务 ID 采用类型前缀 + 随机字符的方式生成:

typescript 复制代码
const TASK_ID_PREFIXES: Record<string, string> = {
  local_bash: 'b',
  local_agent: 'a',
  remote_agent: 'r',
  in_process_teammate: 't',
  local_workflow: 'w',
  monitor_mcp: 'm',
  dream: 'd',
}

const TASK_ID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'

export function generateTaskId(type: TaskType): string {
  const prefix = getTaskIdPrefix(type)
  const bytes = randomBytes(8)
  let id = prefix
  for (let i = 0; i < 8; i++) {
    id += TASK_ID_ALPHABET[bytes[i]! % TASK_ID_ALPHABET.length]
  }
  return id
}

前缀设计使得从 ID 即可判断任务类型(a 开头的是 Agent 任务,b 开头的是 Bash 任务)。36^8 约 2.8 万亿种组合,源码注释中明确提到这是为了"抵御暴力符号链接攻击"。


三、Worker 派生:AgentTool 的实现

3.1 Agent 定义与发现

Claude Code 支持三类 Agent 定义:

  • 内置 Agent (built-in):代码中硬编码,如 general-purposeExplorePlan
  • 自定义 Agent(custom):用户通过 Markdown 或 JSON 文件定义
  • 插件 Agent(plugin):通过插件系统注册

内置 Agent 的定义结构如下(以 general-purpose 为例):

typescript 复制代码
export const GENERAL_PURPOSE_AGENT: BuiltInAgentDefinition = {
  agentType: 'general-purpose',
  whenToUse: 'General-purpose agent for researching complex questions, 
    searching for code, and executing multi-step tasks.',
  tools: ['*'],           // 可使用所有工具
  source: 'built-in',
  baseDir: 'built-in',
  getSystemPrompt: getGeneralPurposeSystemPrompt,
}

Explore Agent 则是一个只读的快速搜索专家,明确禁止了文件编辑工具:

typescript 复制代码
export const EXPLORE_AGENT: BuiltInAgentDefinition = {
  agentType: 'Explore',
  disallowedTools: [
    AGENT_TOOL_NAME,          // 不能再派生子 Agent
    FILE_EDIT_TOOL_NAME,      // 不能编辑文件
    FILE_WRITE_TOOL_NAME,     // 不能写入文件
    NOTEBOOK_EDIT_TOOL_NAME,  // 不能编辑 Notebook
  ],
  model: 'haiku',             // 使用更快的小模型
  omitClaudeMd: true,         // 不加载项目记忆文件
  getSystemPrompt: () => getExploreSystemPrompt(),
}

这种设计体现了"最小权限原则"------每个 Agent 只拥有完成其任务所需的最小工具集。

3.2 工具过滤机制

agentToolUtils.ts 中的 filterToolsForAgent 实现了多层工具过滤:

typescript 复制代码
export function filterToolsForAgent({
  tools, isBuiltIn, isAsync, permissionMode
}): Tools {
  return tools.filter(tool => {
    // MCP 工具对所有 Agent 开放
    if (tool.name.startsWith('mcp__')) return true
    
    // 全局禁止列表
    if (ALL_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
    
    // 自定义 Agent 额外禁止列表
    if (!isBuiltIn && CUSTOM_AGENT_DISALLOWED_TOOLS.has(tool.name)) 
      return false
    
    // 异步 Agent 只允许白名单内的工具
    if (isAsync && !ASYNC_AGENT_ALLOWED_TOOLS.has(tool.name)) 
      return false
    
    return true
  })
}

过滤逻辑分为四层:MCP 工具豁免、全局黑名单、自定义 Agent 黑名单、异步 Agent 白名单。这种分层设计确保了不同类型的 Agent 拥有恰当的能力边界。

3.3 派生流程

AgentTool.tsx 中的 call 方法是 Worker 派生的入口。整个流程可以概括为:

  1. 权限检查:验证 Agent 类型是否被权限规则拒绝
  2. MCP 依赖检查 :如果 Agent 声明了 requiredMcpServers,等待相关服务器连接就绪
  3. 系统提示构建:根据 Agent 定义生成系统提示,附加环境信息
  4. 工具集组装:根据 Agent 定义过滤可用工具
  5. 执行模式选择:同步执行或异步后台执行
  6. Agent 运行 :调用 runAgent 启动独立的对话循环

MCP 依赖检查的实现值得注意------它采用了轮询等待模式,最长等待 30 秒,并且在检测到任何必需服务器已失败时提前退出:

typescript 复制代码
if (hasPendingRequiredServers) {
  const MAX_WAIT_MS = 30_000
  const POLL_INTERVAL_MS = 500
  const deadline = Date.now() + MAX_WAIT_MS
  while (Date.now() < deadline) {
    await sleep(POLL_INTERVAL_MS)
    // 提前退出:如果任何必需服务器已失败
    const hasFailedRequiredServer = currentAppState.mcp.clients.some(
      c => c.type === 'failed' && requiredMcpServers.some(...)
    )
    if (hasFailedRequiredServer) break
    if (!stillPending) break
  }
}

四、Fork 子代理:一种基于上下文继承的派生模式

除了常规的 Agent 派生,Claude Code 还实现了一种名为 Fork 的特殊派生模式(forkSubagent.ts)。

4.1 设计动机

常规派生中,Worker 从零开始,只接收协调器提供的 prompt。Fork 模式则让子代理继承父代理的完整对话上下文和系统提示,适用于需要基于当前对话状态进行并行工作的场景。

typescript 复制代码
export const FORK_AGENT = {
  agentType: 'fork',
  tools: ['*'],
  maxTurns: 200,
  model: 'inherit',           // 继承父代理的模型
  permissionMode: 'bubble',   // 权限提示冒泡到父终端
  getSystemPrompt: () => '',  // 不使用自己的系统提示
}

4.2 Prompt Cache 优化

Fork 的一个关键设计目标是最大化 API 请求的 prompt cache 命中率。所有 Fork 子代理共享相同的消息前缀,只有最后一个文本块(per-child directive)不同:

typescript 复制代码
export function buildForkedMessages(
  directive: string,
  assistantMessage: AssistantMessage,
): MessageType[] {
  // 保留完整的父 assistant 消息(所有 tool_use 块)
  const fullAssistantMessage = { ...assistantMessage, uuid: randomUUID() }
  
  // 为每个 tool_use 生成相同的占位符 tool_result
  const toolResultBlocks = toolUseBlocks.map(block => ({
    type: 'tool_result',
    tool_use_id: block.id,
    content: [{ type: 'text', text: FORK_PLACEHOLDER_RESULT }],
  }))
  
  // 只有最后的 directive 文本块不同
  const toolResultMessage = createUserMessage({
    content: [...toolResultBlocks, { type: 'text', text: buildChildMessage(directive) }],
  })
  
  return [fullAssistantMessage, toolResultMessage]
}

所有 Fork 子代理的 tool_result 内容完全相同('Fork started --- processing in background'),确保 API 请求前缀字节一致,从而共享 prompt cache。源码注释中明确提到:重新调用 getSystemPrompt() 可能因 GrowthBook 状态变化而产生不同结果,因此直接传递父代理已渲染的系统提示字节。

4.3 递归保护

Fork 子代理保留了 AgentTool 在其工具池中(为了 cache-identical 的工具定义),但在调用时通过两层检查阻止递归 Fork:

typescript 复制代码
// 第一层:检查 querySource
if (toolUseContext.options.querySource === `agent:builtin:${FORK_AGENT.agentType}`) {
  throw new Error('Fork is not available inside a forked worker.')
}

// 第二层:检查消息历史中的 Fork 标记
if (isInForkChild(toolUseContext.messages)) {
  throw new Error('Fork is not available inside a forked worker.')
}

第一层检查基于 querySource(抗压缩------即使对话被自动压缩,querySource 仍然保留)。第二层检查基于消息内容中的 <fork-boilerplate> 标签,作为兜底。

4.4 子代理行为约束

Fork 子代理的 directive 中包含了严格的行为约束:

typescript 复制代码
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.
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. Do NOT emit text between tool calls. Use tools silently, 
   then report once at the end.
6. Stay strictly within your directive's scope.
7. Keep your report under 500 words.
8. Your response MUST begin with "Scope:".

Output format:
  Scope: <echo back your assigned scope>
  Result: <the answer or key findings>
  Key files: <relevant file paths>
  Files changed: <list with commit hash>
  Issues: <list --- include only if there are issues>
</fork-boilerplate>`
}

这段约束解决了一个实际问题:子代理继承了父代理的系统提示,而父代理的系统提示可能包含"默认使用 Fork"的指令。如果不加约束,子代理会尝试再次 Fork,形成无限递归。


五、并发策略与任务编排

5.1 四阶段工作流

协调器的系统提示中定义了标准的四阶段工作流:

阶段 执行者 目的
Research(研究) Worker(并行) 调查代码库,定位文件,理解问题
Synthesis(综合) Coordinator 阅读研究结果,理解问题,编写实现规格
Implementation(实现) Worker 按规格进行定向修改,提交代码
Verification(验证) Worker 测试变更是否正确

5.2 并发规则

系统提示中明确规定了并发策略:

  • 只读任务(研究):可自由并行,鼓励从多个角度同时调查
  • 写入任务(实现):同一文件集合内一次只允许一个 Worker
  • 验证任务:可与不同文件区域的实现任务并行

5.3 Continue vs. Spawn 决策

协调器在收到 Worker 结果后,需要决定是继续该 Worker 还是派生新的 Worker。系统提示中给出了明确的决策矩阵:

场景 机制 原因
研究恰好覆盖了需要编辑的文件 Continue Worker 已有文件上下文
研究范围广但实现范围窄 Spawn 避免拖入探索噪声
纠正失败或扩展近期工作 Continue Worker 有错误上下文
验证另一个 Worker 写的代码 Spawn 验证者应以全新视角审视
首次实现方向完全错误 Spawn 错误方向的上下文会污染重试

这个决策矩阵的核心逻辑是"上下文重叠度"------如果 Worker 已有的上下文与下一步任务高度相关,继续;否则派生新 Worker 以获得更干净的上下文。

5.4 综合(Synthesis)的强制要求

系统提示中对协调器的"综合"职责有严格要求:

复制代码
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.

协调器必须在 Worker 的研究结果基础上,自行理解问题并编写包含具体文件路径、行号、修改内容的实现规格。这种设计避免了"懒惰委托"------即协调器不理解问题就直接转发给下一个 Worker。


六、Worker 间通信:异步通知机制

6.1 通知格式

Worker 的结果通过 <task-notification> XML 格式异步回传给协调器:

xml 复制代码
<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>

这些通知以 user-role 消息的形式注入协调器的对话流。协调器通过 <task-notification> 开头标签来区分真实用户消息和 Worker 通知。

6.2 Worker 继续

通过 SendMessageTool,协调器可以向已完成的 Worker 发送后续指令,Worker 保留其完整的对话上下文继续执行。这避免了为后续任务重新构建上下文的开销。

6.3 Worker 终止

通过 TaskStopTool,协调器可以终止运行中的 Worker。被终止的 Worker 仍然可以通过 SendMessageTool 继续------这意味着"终止"更像是"暂停",Worker 的上下文不会被销毁。


七、隔离机制

7.1 Worktree 隔离

Agent 支持 isolation: 'worktree' 模式,在临时 git worktree 中运行,与主工作目录完全隔离:

typescript 复制代码
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.`
}

7.2 Scratchpad 共享

协调器模式下支持 Scratchpad 目录,Worker 可以在该目录中自由读写而无需权限提示,用于跨 Worker 的持久化知识共享:

typescript 复制代码
if (scratchpadDir && isScratchpadGateEnabled()) {
  content += `\nScratchpad directory: ${scratchpadDir}
  Workers can read and write here without permission prompts. 
  Use this for durable cross-worker knowledge.`
}

7.3 远程隔离

对于内部用户,还支持 isolation: 'remote' 模式,将 Agent 发送到远程 CCR 环境执行,实现完全的进程级隔离。


八、工程细节与设计取舍

8.1 循环依赖处理

coordinatorMode.ts 中有一段注释揭示了模块依赖管理的复杂性:

typescript 复制代码
// Checks the same gate as isScratchpadEnabled() in
// utils/permissions/filesystem.ts. Duplicated here because importing
// filesystem.ts creates a circular dependency (filesystem -> permissions
// -> ... -> coordinatorMode).

为了避免循环依赖,代码选择了"重复检查"而非"共享导入"。这种务实的取舍在大型 TypeScript 项目中很常见。

8.2 Feature Flag 驱动

整个协调器模式被 feature('COORDINATOR_MODE') 包裹,支持编译时的死代码消除(DCE)。Fork 子代理同样被 feature('FORK_SUBAGENT') 控制。这意味着在外部构建中,这些实验性功能的代码会被完全移除。

8.3 进程内协作者的限制

进程内协作者(in_process_teammate)有特殊限制:不能派生后台 Agent,不能派生其他协作者。这些限制在 AgentTool.call 中通过显式检查实现:

typescript 复制代码
if (isInProcessTeammate() && teamName && run_in_background === true) {
  throw new Error('In-process teammates cannot spawn background agents.')
}

if (isTeammate() && teamName && name) {
  throw new Error('Teammates cannot spawn other teammates --- 
    the team roster is flat.')
}

九、总结

Claude Code 的多 Agent 协调器架构展示了一套经过生产验证的设计方案。其核心设计决策可以归纳为:

其一,角色分离彻底。协调器只做理解、分解、综合,不执行具体操作。Worker 只执行具体任务,不与用户直接交互。这种分离使得每个角色的行为可预测、可约束。

其二,上下文管理精细。从 Continue vs. Spawn 的决策矩阵,到 Fork 子代理的 prompt cache 优化,再到 Worktree 隔离和 Scratchpad 共享,系统在上下文的继承、隔离、共享之间做了细致的权衡。

其三,安全边界明确。每个 Agent 类型有独立的工具白名单/黑名单,递归派生有多层防护,进程内协作者有额外限制。这些约束确保了多 Agent 系统不会失控。

其四,工程务实。循环依赖通过代码重复解决,实验性功能通过 Feature Flag 控制,MCP 依赖通过轮询等待处理。这些都是在理想设计与工程现实之间的务实取舍。

对于正在构建多 Agent 系统的团队,这套架构中最值得借鉴的是"综合"环节的强制要求------协调器必须理解 Worker 的研究结果后才能指导下一步工作,而不是简单地在 Worker 之间传递消息。这一设计从根本上避免了多 Agent 系统中常见的"信息衰减"问题。

相关推荐
Jayin_chan2 小时前
大语言模型(LLM)输出机制(方便自己查阅)
人工智能·语言模型·自然语言处理
李元豪2 小时前
3分分类计算差值
人工智能·分类·数据挖掘
云烟成雨TD2 小时前
Spring AI 1.x 系列【22】深度拆解 ToolCallbackProvider 生命周期与调用链路
java·人工智能·spring
萌>__<新2 小时前
AI聊天助手-测试报告
人工智能·python
KC2702 小时前
OpenAkita 深度解析:开源多Agent协作框架的实战指南
人工智能·aigc·ai编程
元拓数智2 小时前
基于数据关系映射的企业AI系统权限最小化落地方法
人工智能
柠萌f2 小时前
从“尝鲜”到“落地”:易元AI真实商家案例拆解(美妆/服饰/3C)
人工智能
人工智能AI技术2 小时前
阿里云发布Qwen3.5-Omni,全模态大战开启
人工智能
用户446594547872 小时前
用 React 写 CLI 是什么体验?—— Ink 框架深度解析与实战
人工智能