Coordinator-Worker 与 Context 继承:结构化协作的工程实现
Coordinator-Worker 与 Context 继承:结构化协作的工程实现
《Claude Code 架构解密》精读笔记 · 第10篇 · 第6章后半(6.6-6.12)
上一篇我们拆解了 Fork-and-Delegate 的轻量并行分叉机制。当任务复杂度升级——“先调研、再实现、后验证”——Fork 的隐式编排就力不从心了。这时需要一个"指挥者"来显式调度工作流,这就是 Coordinator-Worker 模式的用武之地。本篇将从结构化编排、异步生命周期、上下文缓存共享三个维度,解析 Claude Code 如何把多 Agent 协作从"散兵游勇"升级为"正规军"。
一、导语:从游击队到正规军
Fork-and-Delegate 像游击战——每个子 Agent 继承完整上下文、独立执行、无需协调。但"对代码库做安全审计→修复高危漏洞→验证修复"这样的多阶段任务,需要严格的阶段控制和结果聚合。
核心矛盾:Fork 的"继承式共享"带来了缓存效率和上下文连贯性,但代价是缺乏编排结构;Coordinator 的"隔离式编排"带来了结构化控制和安全性,但代价是缓存失效和额外成本。
Claude Code 的解法不是二选一,而是两种模式互斥并存——根据任务特征选型,同一时刻只有一种模式激活。
二、架构图解:七模式协作全景
第6章的七个设计模式并非孤岛,它们形成了一张协作网络:
┌─────────────────────┐
│ Agent Type Registry │ ← 加载 Agent 定义
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Tool Sandboxing │ ← 过滤工具集
└─────┬────────┬──────┘
│ │
┌───────────────▼┐ ┌────▼──────────────┐
│ Fork-and-Delegate│ │ Coordinator-Worker │ ← 两种互斥编排
└───────┬────────┘ └─────┬──────────────┘
│ │
┌───────▼──────────────────▼──────┐
│ Async Agent Lifecycle │ ← 统一生命周期
└───────┬──────────────────┬──────┘
│ │
┌────────────▼──────┐ ┌───────▼──────────┐
│ Context Cache │ │ Scoped Memory │ ← 成本优化 + 长期记忆
│ Sharing (Fork专属) │ │ (所有Agent共享) │
└───────────────────┘ └──────────────────┘
协作路径解读:
- Agent Type Registry → 提供定义
- Tool Sandboxing → 过滤权限
- Fork / Coordinator → 互斥编排
- Async Lifecycle → 统一生命周期管理
- Context Cache → Fork 专属成本优化
- Scoped Memory → 全局跨会话记忆
三、核心点拆解
3.1 Coordinator-Worker:编排者不执行
角色分离的极端设计:Coordinator 只有 4 个工具——Agent(启动 Worker)、TaskStop(停止 Worker)、SendMessage(继续 Worker 对话)、SyntheticOutput(结构化输出)。不能读文件、不能写文件、不能执行 Shell。
相对应地,Worker 拥有丰富的执行工具,但被禁止创建子 Agent(防嵌套递归)和向用户提问(后台无交互界面)。
四阶段工作流(软约束):
Research(调研)→ Synthesis(综合)→ Implementation(实现)→ Verification(验证)
"软约束"的精妙之处:LLM 可根据实际情况灵活调整——简单任务跳过 Research,纯分析任务跳过 Implementation。
XML 通知协议:Worker 完成时用结构化 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>
为什么用 XML 不用 JSON?LLM 对 XML 标签的解析更鲁棒——JSON 一个多余逗号就崩,XML 标签的嵌套结构对 LLM 更自然。而且 <task-notification> 可以嵌入普通文本中,不需要额外的通信通道。
3.2 自包含 Prompt 与 Scratchpad 间接通信
Coordinator-Worker 与 Fork 最关键的区别:上下文隔离。
| 维度 | Fork-and-Delegate | Coordinator-Worker |
|---|---|---|
| 上下文共享 | 子 Agent 继承父 Agent 完整对话历史 | Worker 看不到 Coordinator 的任何对话 |
| Prompt 要求 | 只需简短的 Fork 指令 | 必须自包含所有必要上下文 |
| 缓存效率 | 高(Prompt Cache 共享) | 低(每个 Worker 独立请求) |
Coordinator 的 system prompt 有一条明确规则:"Prompts must be self-contained. Workers cannot see your conversation."启动 Worker 时必须将所有必要信息嵌入 prompt——文件路径、行号、具体需求、背景上下文。
这种隔离带来两个好处:
- 安全隔离:Worker 看不到 Coordinator 与用户的对话,降低信息泄露风险
- 独立性:Worker 可以被独立测试和调试
Scratchpad 间接通信:Worker 之间无法直接通信,Coordinator 可指定一个共享目录作为"草稿纸"——Worker 写入中间结果,其他 Worker 读取。一种简单但有效的间接通信模式。
3.3 Async Agent Lifecycle:五阶段状态机
长时间运行的 Agent 不应阻塞主线程,但异步执行引入了新的复杂性——进度反馈、错误处理、资源清理、前后台切换。
五阶段生命周期:
Created → Running → Completed
→ Failed
→ Killed
核心实现 runAsyncAgentLifecycle() 五阶段:
| 阶段 | 动作 | 关键点 |
|---|---|---|
| Phase 1 | 初始化——创建进度追踪器 | tracker 记录 toolUseCount / token 用量 / recentActivities |
| Phase 2 | 消息流处理——实时追踪进度 | for await (const message of makeStream()) 逐消息更新 |
| Phase 3 | 完成处理 | finalizeAgentTool + completeAsyncAgent |
| Phase 4 | 安全审查 | classifyHandoffIfNeeded 分类审查 |
| Phase 5 | 通知 Coordinator/父 Agent | enqueueAgentNotification XML 格式通知 |
进度追踪器的 token 统计细节:
- 输入 token 取最新值(API 每次返回的是累计值,包含缓存命中部分)
- 输出 token 逐轮累加(每轮输出是增量)
这个细节反映了对 LLM API 计费模型的深入理解。
3.4 前台/后台切换与自动后台化
Agent 支持前台后台动态切换。核心机制是 backgroundSignal——一个 Promise,当 Agent 被切换到后台时 resolve。Agent 执行逻辑可以 await 这个信号来调整行为:前台即时显示进度,后台只在完成时通知。
自动后台化定时器(默认 120 秒)解决了一个 UX 问题:用户启动 Agent 后可能忘记手动切后台,长时间运行的前台 Agent 会阻塞主界面。120 秒后自动后台化是合理的默认行为。
3.5 十步资源清理链:最精密的工程
异步 Agent 生命周期中最精密的部分。运行中的 Agent 可能持有 MCP 连接、Hook 注册、文件缓存、Shell 进程等多种资源。finally 块确保这些资源被可靠释放:
1. mcpCleanup() // 关闭 MCP 连接
2. clearSessionHooks() // 清除 Hook 注册
3. cleanupAgentTracking() // 清除 Cache 检测
4. readFileState.clear() // 释放文件缓存
5. initialMessages.length = 0 // 释放消息引用
6. unregisterPerfettoAgent() // 注销 Tracing
7. (保留位)
8. delete todos[agentId] // 删除 Todo 条目
9. killShellTasksForAgent() // 杀死后台 Shell
10. killMonitorMcpTasksForAgent() // 杀死 Monitor 任务
清理顺序的设计哲学:
- 先关闭外部连接(MCP),正在执行的工具调用可能依赖它们
- 再清除注册信息(Hook、Tracing),依赖于 MCP 状态
- 然后释放内存缓存(文件缓存、消息引用)
- 最后清理进程资源(Shell、Monitor),最独立的资源
第 5 步的微妙之处:为什么用 initialMessages.length = 0 而不是 initialMessages = []?因为 .length = 0 会原地清空数组,所有持有该数组引用的代码都能观察到清空效果。重新赋值只改变引用,旧数组仍被其他引用持有,无法被 GC 回收。在 Fork 场景中,上下文消息可能非常大,这个差异对内存管理至关重要。
3.6 通知防重复:乐观锁模式
function enqueueAgentNotification({ taskId, status, finalMessage }) {
let shouldEnqueue = false
updateTaskState(taskId, setAppState, task => {
if (task.notified) return task // 已通知过
shouldEnqueue = true
return { ...task, notified: true }
})
if (!shouldEnqueue) return
// 构建 XML 通知...
}
在状态更新回调中设置 notified 标志,利用状态更新的原子性保证"检查+标记"不可分割。典型的乐观锁模式——不使用显式锁,而是通过原子操作保证一致性。
3.7 Retain/Evict 懒清理
UI 层面的内存管理:
- 用户查看 Agent 面板 →
retain: true,阻止驱逐 - 用户离开面板 → 开始 30 秒倒计时(
PANEL_GRACE_MS) - 倒计时结束且任务已完成 → 驱逐面板数据释放内存
"懒清理"策略在响应速度(用户切回来时数据仍在)和内存效率(长时间不看的数据被释放)之间取得了平衡。
3.8 Context Cache Sharing:四维一致性保证
成本优化的架构洞察:假设父 Agent 有 50,000 tokens 对话历史,Fork 出 3 个子 Agent——
| 方案 | 成本 |
|---|---|
| 朴素方案 | 3 × 50,000 = 150,000 input tokens |
| Prompt Cache 方案 | 50,000 缓存写入(一次)+ 3 × 50,000 缓存读取(便宜 90%) |
要利用 Prompt Cache,必须确保多个请求的前缀字节级一致——哪怕一个字节的差异都会导致缓存失效。
四维一致性保证:
| 维度 | 机制 | 代码关键字 |
|---|---|---|
| 维度 1:统一占位符 | 所有 Fork 子 Agent 的 tool_result 使用同一文本 | FORK_PLACEHOLDER_RESULT |
| 维度 2:精确工具列表 | useExactTools: true 跳过工具解析,直接使用父级工具列表 |
useExactTools |
| 维度 3:模型继承 | model: 'inherit' 避免模型切换导致缓存失效 |
model: 'inherit' |
| 维度 4:文件状态缓存克隆 | Fork 子 Agent 继承父 Agent 的文件读取缓存 | cloneFileStateCache |
架构启示:LLM 系统的成本优化不仅仅是算法问题,更是系统架构问题——成本优化是跨切面关注点(cross-cutting concern),需要在消息构建、工具解析、模型选择、缓存管理多个层面协同设计。
3.9 Fork vs Coordinator:系统性对比
| 维度 | Fork-and-Delegate | Coordinator-Worker |
|---|---|---|
| 上下文共享 | 继承父 Agent 完整上下文 | Worker 无父上下文,需自包含 prompt |
| 工具权限 | 继承父级所有工具 | Coordinator 只有编排工具,Worker 有执行工具 |
| 缓存效率 | 高(Prompt Cache 共享) | 低(每个 Worker 独立请求) |
| 任务编排 | 隐式(父 Agent 自行决定) | 显式(四阶段工作流) |
| 互动能力 | 无(子 Agent 静默执行) | Worker 间可通过 Scratchpad 间接通信 |
| 递归深度 | 硬限制:禁止递归 Fork | 硬限制:Worker 不可嵌套 Agent |
| 适用场景 | 并行独立任务,需要共享上下文 | 复杂工作流,需要阶段控制 |
| 成本模型 | 低(缓存共享) | 高(独立请求) |
选择指南:
- 并行独立子任务 + 需要理解完整上下文 → Fork
- 多阶段编排 + 子任务间有依赖 → Coordinator
- 成本敏感 → Fork(Prompt Cache 共享可省 90% 输入成本)
- 安全敏感 → Coordinator(Worker 看不到 Coordinator 与用户的对话)
四、横向对比:三大框架的多 Agent 编排哲学
| 维度 | Claude Code | LangGraph | OpenAI Agents SDK | AutoGen |
|---|---|---|---|---|
| 编排模型 | 隐式(Fork)/ Prompt 引导(Coordinator) | 显式有向图 | 函数调用交接 | 对话协议 |
| 灵活性 | LLM 动态决定任务分解 | 图结构编译时确定 | 声明式轻量 | Agent 协商 |
| 可预测性 | 较低(LLM 决策不确定) | 较高(图结构确定) | 中等 | 较低 |
| 并行度 | 真正并行(Fork/Worker) | 支持并行分支 | 串行交接 | 支持并行对话 |
| 上下文传递 | Fork 共享 / Worker 自包含 | 图边传递状态 | 交接时传递完整上下文 | 对话消息传递 |
| 复杂度 | 较高(7 个模式) | 中等(图定义) | 较低 | 中等 |
| 适用场景 | 通用 AI 编程助手 | 预定义工作流 | 单一信任域 | 多 Agent 研究/讨论 |
Claude Code 的选择逻辑:
- vs LangGraph:任务类型不可预知,需要 LLM 自主判断如何分解
- vs Agents SDK:需要进程级隔离的生产级复杂场景,不是单一信任域内的轻量交接
- vs AutoGen:Agent 间对话引入额外 LLM 调用成本 + 不确定性传播,不如 Coordinator 集中编排
五、实战启示:三个可复用设计模式
模式一:角色分离 + 最小权限
编排者不执行,执行者不编排
这个原则可以推广到任何多角色系统——Coordinator 只有调度工具、Worker 只有执行工具、记忆 Agent 只能编辑一个文件。每个角色的权限边界越窄,系统的安全推理越简单。
模式二:十步确定性清理链
资源清理不是 try-finally 里的三行代码,而是精心排序的多步链条:
- 外部连接先关(MCP)
- 注册信息次之(Hook、Tracing)
- 内存缓存随后(文件、消息)
- 进程资源最后(Shell、Monitor)
initialMessages.length = 0 的细节值得记住:原地清空 vs 重新赋值,在共享引用场景中有本质区别。
模式三:四维缓存一致性
在 LLM 系统中做成本优化,不是在某个组件里做缓存,而是在多个维度上协调一致:
- 消息层面:统一占位符
- 工具层面:精确工具列表
- 模型层面:继承父级模型
- 缓存层面:文件状态克隆
任何一个维度的疏忽都会导致缓存前缀断裂,90% 的成本节省瞬间归零。成本优化是跨切面架构关注点,不是局部优化。
六、下期预告
第 10 篇完成了第 6 章(Agent 编排)的精读。七个模式从"轻量分叉"到"结构化编排"再到"生命周期管理",构建了一套完整的多 Agent 协作体系。
下一篇进入第 7 章——上下文管理,这是 Agent 系统的"灵魂所在":有限的上下文窗口如何装下无限的需求?
第 11 篇:Context 的生死抉择——补丁压缩、截断算法与 Session Memory
- 7.1 上下文窗口的核心矛盾
- 7.2 补丁压缩处理机制
- 7.3 Session Memory——实时更新要素系统
- 7.4 消息截断算法——在正确的地方"按一下"
本文是《Claude Code 架构解密》精读笔记系列第 10 篇,覆盖第 6 章后半(6.6-6.12)。系列共 20 篇,持续更新中。
更多推荐
所有评论(0)