Claude Code 源码分析第 21 课 · 第 03
第21课

协调器模式

⏱ ~30 分钟 ⚡ 高级 📂 coordinatorMode.ts · 工具.ts

🗺 什么是协调器模式?

协调员模式是 Claude Code 中的一流操作模式,其中克劳德专门充当协调员 dispatcher - 它本身从不运行工具。 每个副作用操作都委托给协调器生成和指导的工作代理。 协调员的工作是思考、计划、综合、沟通; 工人的工作就是执行。

一个环境变量控制整个模式。 CLAUDE_CODE_COORDINATOR_MODE=1 激活它。 功能 isCoordinatorMode() 在运行时读取此内容(无缓存),因此您可以在恢复的会话中翻转它,并且更改会立即生效。

这种分离并不是表面上的。 与常规 Claude Code 会话相比,协调器会收到不同的系统提示、不同的工具集和不同的上下文块。 了解每个部分如何连接在一起是本课程的主题。

flowchart TD U["🧑 User"] C["📋 Coordinator
CLAUDE_CODE_COORDINATOR_MODE=1"] W1["💻 Worker A
research"] W2["💻 Worker B
implementation"] W3["💻 Worker C
verification"] U -->|"request"| C C -->|"Agent(...)"| W1 C -->|"Agent(...)"| W2 C -->|"SendMessage(...)"| W2 C -->|"Agent(...)"| W3 W1 -->|"task-notification"| C W2 -->|"task-notification"| C W3 -->|"task-notification"| C C -->|"synthesis + status"| U style U fill:#1a1816,stroke:#c47a50,color:#b8b0a4 style C fill:#22201d,stroke:#c47a50,color:#c47a50 style W1 fill:#1f241d,stroke:#6e9468,color:#6e9468 style W2 fill:#241f2a,stroke:#8e82ad,color:#8e82ad style W3 fill:#262117,stroke:#b8965e,color:#b8965e

协调员仅与用户交谈并指导工作人员。 工作人员仅与协调员交谈(通过 task-notification XML 消息)。 协调器从不执行 Bash、编辑文件或直接读取文件系统。

🔑 激活和会话匹配

两个导出函数控制协调器模式是否打开并在会话恢复期间保持一致。

Show: isCoordinatorMode() and matchSessionMode() —coordinatorMode.ts
coordinator/coordinatorMode.ts TypeScript
export function isCoordinatorMode(): boolean {
  if (feature('COORDINATOR_MODE')) {
    return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
  }
  return false
}

/**
 * Checks if the current coordinator mode matches the session's stored mode.
 * If mismatched, flips the environment variable so isCoordinatorMode()
 * returns the correct value for the resumed session.
 * Returns a warning message if the mode was switched, or undefined if not.
 */
export function matchSessionMode(
  sessionMode: 'coordinator' | 'normal' | undefined,
): string | undefined {
  if (!sessionMode) return undefined  // old session, no tracking

  const currentIsCoordinator = isCoordinatorMode()
  const sessionIsCoordinator  = sessionMode === 'coordinator'

  if (currentIsCoordinator === sessionIsCoordinator) return undefined

  // Flip the env var — isCoordinatorMode() reads it live, no caching
  if (sessionIsCoordinator) {
    process.env.CLAUDE_CODE_COORDINATOR_MODE = '1'
  } else {
    delete process.env.CLAUDE_CODE_COORDINATOR_MODE
  }

  logEvent('tengu_coordinator_mode_switched', { to: sessionMode })

  return sessionIsCoordinator
    ? 'Entered coordinator mode to match resumed session.'
    : 'Exited coordinator mode to match resumed session.'
}

请注意 matchSessionMode() mutates process.env 直接而不是返回新的模式值。 因为 isCoordinatorMode() 没有内部缓存 - 它在每次调用时读取环境变量 - 写入 process.env.CLAUDE_CODE_COORDINATOR_MODE 足以在进程生命周期的剩余时间内翻转整个模式。

需要功能门。 即使设置了环境变量,协调器模式也仅在以下情况下激活: COORDINATOR_MODE Bun 捆绑功能已启用。 在生产构建中,此门控制哪些组织或实验可以看到该模式。 在开发版本中它是默认打开的。

🚫 工具限制:协调员可以使用什么

这是与正常训练最重要的机械差异。 协调器允许的工具集被定义为一个常量 constants/tools.ts:

constants/tools.ts TypeScript
/**
 * Tools allowed in coordinator mode — only output and agent management
 * tools for the coordinator itself.
 */
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
  AGENT_TOOL_NAME,         // 'Agent'       — spawn a new worker
  TASK_STOP_TOOL_NAME,     // 'TaskStop'    — kill a running worker
  SEND_MESSAGE_TOOL_NAME,  // 'SendMessage' — continue an existing worker
  SYNTHETIC_OUTPUT_TOOL_NAME,  // internal output tool
])

四个工具。 这是协调器可用的整个接口。 不 Bash, 不 Read, 不 Edit, 不 Glob, 不 Grep。 协调器无法接触文件系统、运行命令或直接调用 MCP 工具。 它只能产生工人、向他们发送消息并阻止他们。

Agent

产生一个新工人

创建一个具有自己的上下文、工具访问权限和系统提示的新工作人员。 使用 subagent_type: "worker"。 默认情况下,工作人员异步运行并在完成时通知协调员。

SendMessage

继续现有的工人

向由其标识的工作人员发送后续消息 task_id。 工作线程保留了之前运行的完整上下文——用它来进行修正和后续操作,而不是生成新的。

TaskStop

停止正在运行的工人

终止执行中的工人。 当用户在启动后更改需求或方法明显错误时使用。 停止工作的工人仍然可以通过以下方式继续 SendMessage.

SyntheticOutput

内部输出信号

在内部用于将结构化输出呈现给协调员自己的回合。 通常不会在协调器提示中显式调用 - 它是多代理通知机制的一部分。

工人可以使用完整的工具。 该限制仅适用于协调员。 工人通过产生 Agent 收到完整的 ASYNC_AGENT_ALLOWED_TOOLS 集(Bash、Read、Edit、Glob、Grep、WebSearch、Skill、Notebook、Worktree 等)以及来自连接服务器的 MCP 工具。

📦 工人背景: getCoordinatorUserContext()

当协调器模式处于活动状态时, getCoordinatorUserContext() 将上下文块注入到协调器的对话中,描述工作人员可以访问哪些工具。 这就是协调员如何知道要委托什么以及在什么级别的具体程度。

Show: getCoordinatorUserContext() —coordinatorMode.ts
coordinator/coordinatorMode.ts TypeScript
// Tools hidden from workers even though they are in ASYNC_AGENT_ALLOWED_TOOLS
const INTERNAL_WORKER_TOOLS = new Set([
  TEAM_CREATE_TOOL_NAME,    // 'TeamCreate'
  TEAM_DELETE_TOOL_NAME,    // 'TeamDelete'
  SEND_MESSAGE_TOOL_NAME,   // 'SendMessage'
  SYNTHETIC_OUTPUT_TOOL_NAME,
])

export function getCoordinatorUserContext(
  mcpClients: ReadonlyArray<{ name: string }>,
  scratchpadDir?: string,
): { [k: string]: string } {
  if (!isCoordinatorMode()) return {}

  // Simple mode: workers only get Bash, Read, Edit
  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(', ')

  let content = `Workers spawned via the Agent tool have access to these tools: ${workerTools}`

  if (mcpClients.length > 0) {
    const serverNames = mcpClients.map(c => c.name).join(', ')
    content += `\n\nWorkers also have access to MCP tools from connected MCP servers: ${serverNames}`
  }

  if (scratchpadDir && isScratchpadGateEnabled()) {
    content += `\n\nScratchpad directory: ${scratchpadDir}\nWorkers can read and write here without permission prompts.`
  }

  return { workerToolsContext: content }
}

该函数中发生了三件事:

  • 工具枚举。 工作工具列表是在运行时通过过滤计算的 ASYNC_AGENT_ALLOWED_TOOLS,删除 INTERNAL_WORKER_TOOLS set (TeamCreate、TeamDelete、SendMessage、SyntheticOutput),它们是仅限协调器的原语。
  • MCP 服务器广告。 如果连接了任何 MCP 客户端,则会附加其名称。 这告诉协调器它可以指示工作人员按名称调用 MCP 工具。
  • 暂存器注入。 如果存在共享暂存器目录并且功能门已打开,则会注入该路径,以便协调员可以告诉工作人员在那里读/写以获得持久的跨工作人员知识,而无需权限提示。
简单模式(CLAUDE_CODE_SIMPLE=1)是一个精简的变体。 工作人员只能进行 Bash、阅读和编辑——没有网络搜索、没有笔记本、没有工作树管理。 此模式适合需要最小化、可预测的代理表面的 SDK 消费者。

📜 协调器系统提示

协调器模式的核心是它的系统提示符——由 getCoordinatorSystemPrompt()。 这是一份 300 多行的文档,定义了协调员的角色、其工具合同、四阶段任务工作流程以及用于编写工作人员指令的详细提示工程指南。 以下是注释的六个主要部分。

第 1 课 节——角色定义
协调器/coordinatorMode.ts — getCoordinatorSystemPrompt() Text
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

Every message you send is to the user. Worker results and system
notifications are internal signals, not conversation partners —
never thank or acknowledge them. Summarize new information for
the user as it arrives.

关键约束:协调器始终面向用户,而不是工作人员。 工人 task-notification 消息被视为内部信号,而不是对话轮次。

第 2 课 节——工具合同和任务通知格式

工作人员结果作为用户角色消息到达,其中包含 <task-notification> XML 信封。 这就是协调员如何知道工作人员已完成的方式:

<task-notification> <task-id>agent-a1b</task-id> <status>completed</status> <!-- 已完成 | 失败 | 被杀了--> <summary>代理“调查身份验证错误”已完成</summary> <result>在 src/auth/validate.ts:42 中发现空指针...</result> <usage> <total_tokens>4820</total_tokens> <tool_uses>11</tool_uses> <duration_ms>18432</duration_ms> </usage> </task-notification>

The <task-id> value 是代理 ID — 将其传递给 SendMessage as to 继续该特定工人。 <result> and <usage> 是可选的。

第 3 课 节 — 四阶段任务工作流程
Phase
Who
Purpose
Research
工人(平行)
调查代码库,查找文件,了解问题
Synthesis
Coordinator
阅读调查结果,使用文件路径和行号制定具体的实施规范
Implementation
Workers
根据规范进行有针对性的更改、运行测试、提交
Verification
Workers
证明代码独立工作——不要橡皮图章实施

综合是协调器最重要的阶段。 系统提示明确禁止使用“基于您的发现”之类的短语——协调员必须了解研究并将其转化为精确的规格,然后才能将工作交还给工人。

第 4 课 节 — 并行规则

系统提示将并发视为一流的设计原则:

  • 只读任务(研究) — 根据需要自由地并行运行
  • 写入繁重的任务(实施) — 每组重叠文件一次一个
  • Verification - 可以与不同文件区域的实现一起运行

要并行启动工作人员,请制作多个 Agent 工具在单个协调器轮次中调用。 Worker 异步运行; 协调器不会在启动另一个之前阻止等待一个。

并行启动示例(协调员轮流) Text
Let me investigate from two angles.

Agent({ description: "Investigate auth bug", subagent_type: "worker",
        prompt: "Find null pointer sources in src/auth/. Report file paths,
                 line numbers, types. Do not modify files." })

Agent({ description: "Research auth test coverage", subagent_type: "worker",
        prompt: "Find all test files for src/auth/. Report gaps around
                 session expiry. Do not modify files." })

Investigating both in parallel — I will report back with findings.
第 5 课 节——继续与产生新的决策逻辑
Situation Action Why
研究准确探索了需要编辑的文件 Continue 工作人员已经拥有上下文中的文件并获得了明确的计划
研究范围广泛,但实施范围狭窄 新鲜产卵 避免将探索噪音拖入重点实施中
纠正失败或扩展最近的工作 Continue Worker 有错误上下文并且知道它刚刚尝试了什么
验证不同工作人员编写的代码 新鲜产卵 验证者应该以新的眼光看待代码
第一次尝试使用了完全错误的方法 新鲜产卵 错误方法上下文会污染重试
不相关的任务 新鲜产卵 没有可重用的有用上下文
第 6 课 节 — 工人提示指南(好与坏)

工作人员看不到协调员的对话。 每个提示都必须完全独立。

错误提示

  • “修复我们讨论的错误”
  • “根据您的发现,实施修复”
  • “为最近的更改创建 PR”
  • “测试出了问题,你能看一下吗?”

良好的提示

  • “修复 src/auth/validate.ts:42 中的空指针。当 Session.expired 为 true 时,用户字段未定义。在 user.id 访问之前添加空检查 — 如果为空,则返回 401 并显示“会话过期”。运行测试、提交、报告哈希值。”
  • “验证空检查修复:在启用该功能的情况下运行身份验证测试,检查边缘情况(过期+缓存的令牌),调查任何失败 - 不要认为不相关而忽略。”
切勿写“基于您的发现”。 这句话将理解委托给了工作人员,而不是协调员来完成其工作。 经过研究后,协调员必须将发现综合到特定的文件路径、行号和确切的更改中,然后编写一个提示来证明其理解。

协调器与单代理模式

两种模式都使用 Claude Code 相同的底层基础设施 - 会话、工具、权限、MCP 客户端。 区别在于主实例所扮演的角色。

Dimension Single-Agent Coordinator
Role 工作本身 调度和合成; 从不执行
刀具数量 完整的工具集(Bash、Read、Edit、Glob...) 仅 4 个工具:Agent、SendMessage、TaskStop、SyntheticOutput
文件系统访问 直接读/写 无 — 必须委托给工人
系统提示 标准 Claude Code 提示 getCoordinatorSystemPrompt() — 任务工作流程、并行调度、综合规则
工人背景 未注射 getCoordinatorUserContext() 注入工作工具列表 + MCP 服务器 + 暂存器
Parallelism 顺序工具调用 通过并行方式实现多个并发工作线程 Agent calls
会议恢复 模式无关 matchSessionMode() 翻转环境变量以恢复恢复会话的正确模式
简单模式变体 N/A CLAUDE_CODE_SIMPLE=1 限制工作人员只能 Bash + 阅读 + 编辑
Scratchpad N/A 注入工作上下文的共享目录——无需许可的跨工作存储

📋 完整的示例会话

这个带注释的示例跟踪了所有四个阶段的完整身份验证错误修复。 每个“你:”块都是一个单独的协调员回合。 “用户:”块是 task-notification 消息在轮次之间到达。

显示:完整注释的协调员会议
示例:Auth 空指针修复 协调员会议
// User request
User: "There's a null pointer in the auth module. Can you fix it?"

// Turn 1 — coordinator launches research workers in parallel
You:
  Let me investigate from two angles.

  Agent({ description: "Investigate auth bug", subagent_type: "worker",
    prompt: "Investigate src/auth/. Find where null pointer exceptions
             could occur around session handling and token validation.
             Report specific file paths, line numbers, and types.
             Do not modify files." })

  Agent({ description: "Research auth tests", subagent_type: "worker",
    prompt: "Find all test files related to src/auth/. Report structure,
             coverage, and gaps around session expiry. Do not modify files." })

  Investigating from two angles — I will report back with findings.

// task-notification from Worker A
User:
  <task-notification>
  <task-id>agent-a1b</task-id>
  <status>completed</status>
  <summary>Agent "Investigate auth bug" completed</summary>
  <result>Found null pointer in src/auth/validate.ts:42. The user field
  on Session (types.ts:15) is undefined when sessions expire but the
  token remains cached. validate() accesses user.id without null check.
  </result>
  </task-notification>

// Turn 2 — synthesize finding, continue same worker with specific spec
You:
  Found the bug — null pointer in validate.ts:42. Still waiting on test coverage.

  SendMessage({ to: "agent-a1b",
    message: "Fix the null pointer in src/auth/validate.ts:42.
              The user field on Session (src/auth/types.ts:15) is
              undefined when sessions expire but the token is still
              cached. Add a null check before user.id access — if
              user is null, return 401 with 'Session expired'.
              Run the auth test suite, commit, report the hash." })

  Fix is in progress. Still waiting for test coverage findings.

// task-notification from Worker B
User:
  <task-notification>
  <task-id>agent-b2c</task-id>
  <status>completed</status>
  <result>Test file: src/auth/__tests__/validate.test.ts. Session expiry
  paths are tested at lines 58 and 72 but only for the happy path —
  no test for expired session + cached token.</result>
  </task-notification>

// Turn 3 — spawn fresh verifier once Worker A reports back
// (Worker A's implementation-heavy context should not contaminate verification)
You:
  Test coverage found: validate.test.ts lacks expired session + cached token cases.
  I will have the verifier add those once the fix lands.

// After Worker A reports fix committed:
You:
  Fix is committed. Launching verification.

  Agent({ description: "Verify auth fix", subagent_type: "worker",
    prompt: "Verify the null-check fix in src/auth/validate.ts:42.
             Add test cases to validate.test.ts (lines 58 and 72 area)
             covering expired session + cached token — expect 401
             with 'Session expired'. Run full auth suite with the
             feature enabled. Investigate any failures — do not dismiss
             as unrelated. Report pass/fail and the updated test hash." })

  Verification in progress.

📝 Scratchpad:跨员工知识

tengu_scratch 功能门处于活动状态并提供了暂存器目录,协调器模式将暂存器路径注入到工作上下文中。 工作人员可以在没有权限提示的情况下在那里读写,使其成为持久发现的共享工作空间。

循环依赖注释。 暂存器门检查重复于 coordinatorMode.ts 作为当地人 isScratchpadGateEnabled() 函数而不是导入自 utils/permissions/filesystem.ts。 评论解释说:导入该文件将创建一个循环依赖链(filesystem → permissions → ... → coordinatorMode)。 实际的暂存器路径是通过依赖注入注入的 QueryEngine.ts,它在依赖图中的位置较高。

协调员可以指示工作人员将发现、部分结果或结构化数据写入暂存器,以便后面的工作人员(或后续轮次的协调员)可以读取它而无需重新做工作。 这对于多个工作人员需要共享上下文的长时间运行的并行研究任务特别有用。

🎯 要点

  • 协调员是 纯调度员 — 它只有 4 个工具(Agent、SendMessage、TaskStop、SyntheticOutput),并且无法接触文件系统、运行命令或直接调用 MCP 工具。
  • 激活是一个单一的环境变量: CLAUDE_CODE_COORDINATOR_MODE=1. isCoordinatorMode() 实时读取它,没有缓存,所以写入 process.env 立即切换模式。
  • getCoordinatorUserContext() 将运行时计算的工作工具列表注入到协调器的上下文中,以便它知道要委托什么。 在简单模式下(CLAUDE_CODE_SIMPLE=1),工作人员只能获得 Bash、Read 和 Edit。
  • 协调器的系统提示强制执行 四阶段工作流程:研究(工人,并行)→综合(协调员)→实施(工人)→验证(工人)。
  • 综合是协调员最重要的工作。 它必须生成特定的文件路径和行号——绝不含糊地“根据您的发现”授权。
  • The 继续与刷新 决策基于上下文重叠:高重叠→继续工作; 低重叠或新鲜验证 → 产生新的。
  • matchSessionMode() 确保恢复的会话通过变异恢复正确的模式 process.env 并记录一个 tengu_coordinator_mode_switched 分析事件。
  • 暂存器是一个无需许可的共享目录,当 tengu_scratch 门处于活动状态 - 对于持久的跨工作人员知识非常有用,无需文件权限提示。

🧪 Quiz

Q1. 协调员可以在协调员模式下使用以下哪些工具?
A Bash、读取、编辑、Glob
B 代理、SendMessage、TaskStop、SyntheticOutput
C 代理、Bash、读取、发送消息
D 所有工具——限制仅适用于工人
Correct. COORDINATOR_MODE_ALLOWED_TOOLS in constants/tools.ts 正好定义了四种工具。 协调器没有文件系统或 shell 访问权限——所有执行都委托给工作人员。
Q2. 什么是 matchSessionMode() 当恢复的会话处于协调器模式但进程以正常模式启动时,该怎么办?
A Sets process.env.CLAUDE_CODE_COORDINATOR_MODE = '1' 并返回警告消息
B 返回错误并拒绝恢复
C 开始新会话而不是恢复
D 记录不匹配但不执行任何操作 - 仅在启动时控制模式
正确的。 因为 isCoordinatorMode() reads process.env 没有缓存,通过写入 matchSessionMode() 足以在过程中翻转模式。 该函数还触发一个 tengu_coordinator_mode_switched 分析事件。
Q3. 为什么会 coordinatorMode.ts 复制暂存器门检查而不是从 utils/permissions/filesystem.ts?
A 性能——导入 filesystem.ts 会显着增加启动延迟
B 暂存器功能仅在协调器模式下可用
C 导入 filesystem.ts 会创建一个循环依赖链
D 无法从权限模块调用 Statsig SDK
正确的。 源代码中的注释明确指出:“导入 filesystem.ts 会创建循环依赖关系(文件系统 → 权限 → ... → coordinatorMode)。” 实际的暂存器路径是通过依赖注入注入的 QueryEngine.ts 它在依赖图中的位置更高。
Q4. 协调员完成接收工作人员 A 的研究,该工作人员准确地探索了需要编辑的文件。 协调员接下来应该做什么?
A 生成一个新的工作人员,并提示“根据工作人员 A 的发现,实施修复”
B 通过 SendMessage 使用包含特定文件路径和行号的综合规范继续工作程序 A
C 生成一个新的 Worker 并将 Worker A 的完整输出作为上下文传递
D 在继续之前要求用户确认调查结果
正确的。 高上下文重叠 → 继续。 Worker A 已经加载了相关文件。 协调员将调查结果综合成特定规范并通过 SendMessage。 选项 A 是反模式(“基于你的发现”)——协调员必须综合,而不是委托理解。
Q5. When CLAUDE_CODE_SIMPLE=1 设置完成后,工作人员在协调员模式下可以使用哪些工具?
A ASYNC_AGENT_ALLOWED_TOOLS 中的所有工具减去内部辅助工具
B 仅代理和发送消息
C 没有工具——简单模式禁用所有工人
D 仅限 Bash、读取 (FileRead) 和编辑 (FileEdit)
正确的。 在 getCoordinatorUserContext(), 什么时候 CLAUDE_CODE_SIMPLE 是真的,工作工具列表被硬编码为 [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME]。 这是为 SDK 消费者提供的精简界面,他们想要可预测的、最少的员工行为。
0 / 5 已回答