📅 时间: 23:10
— 陆游 · 《冬夜读书示子聿》纸上得来终觉浅,绝知此事要躬行。
上一篇我整理的是 Agent 知识地图:workflow、tool calling、state、guardrails、evaluation。那篇解决“该学什么”。
这篇更想回答:如果我要自己做一个能改代码的 Agent,主链路该怎么串?多 Agent 又该怎么加?
我的读法不是功能猎奇,而是倒着问:
源码里哪些设计,是我自己做 Agent 时几乎绕不过去的?
材料来自 npm source map 还原树,约数月前的快照,非官方,版权归 Anthropic,仅供学习。主链路(loop / 工具 / 权限 / Coordinator)仍有参考价值;具体开关、产品行为会漂移,别当最新说明书。

先定结论:做 Agent 通常分三层
读完还原树,我会把“开发一个编程 Agent”拆成三层,而不是一上来 multi-agent:
- 单 Agent 主循环:模型决定下一步,harness 执行工具、回灌结果、控制停止;
- 安全与上下文:权限、确认、压缩、可回看的状态;
- 多 Agent 编排(可选):需要并行研究或隔离脏上下文时,再上 Coordinator / Worker。
Claude Code 源码里,前两层主要在 QueryEngine.ts + query.ts + tools/*;第三层很清楚地落在 src/coordinator/,尤其是 coordinatorMode.ts。
这和 effective agents 的气质一致:先把简单可组合的模式做稳,再加编排复杂度。

第一层:单 Agent 必须先跑通
如果你只做一件事,就做这件事:
用户输入
→ 组装上下文
→ 调模型
→ 若 tool_use:权限检查 → 执行 → tool_result 回灌 → 再调模型
→ 若纯文本 / 达停止条件:结束在还原源码里:
QueryEngine更像会话管家:持有消息、usage、权限拒绝、文件缓存,对外 yield 一轮结果;query.ts才是 agent loop:里面有明确的while (true),负责压缩、调模型、runTools、继续或退出。
QueryEngine.submitMessage() 会把控制交给:
for await (const message of query({
messages,
systemPrompt,
userContext,
systemContext,
canUseTool: wrappedCanUseTool,
toolUseContext: processUserInputContext,
})) {
// 记录消息、向外推流
}自己做时,这一层建议写成很薄的循环 + 很厚的外围:
| 你要有的能力 | 源码里对应的直觉 |
|---|---|
| 统一工具表 + schema | src/tools/* 注册,而不是主循环里 if-else |
| 工具结果必须回灌 | tool_use → 执行 → tool_result 进消息栈 |
| 最大步数 / 中断 / 失败退出 | maxTurns、abort、stop hooks |
| 可观察 | transcript、usage、permission denials |
工具不必一上来很多。编程场景优先这几类就够起步:
- 读文件 / 改文件
- 搜索(glob/grep)
- 受控 shell
- (可选)MCP 扩展外部能力

MCP 的意义不是“多一个时髦词”,而是:核心循环不改,外部能力可插拔。 自己做时,先别把 GitHub、浏览器、数据库全焊死进 prompt 路由。

Dive-into 有个刺眼比例:大约 1.6% AI 决策,98.4% 确定性基础设施。数字不必死抠,方向要对——你做 Agent 时,真正花时间的地方多半是 harness,不是再写一个更花的 Planner。
第二层:权限和上下文,决定 Agent 能不能活得久
编程 Agent 危险的是副作用,不是嘴炮。canUseTool 被包装、拒绝会进 permission_denials,说明产品把“拦下来”当成可观察事件。
自己做时,权限别写成 system prompt 里的“请谨慎”,而要写成路径:
- 读:默认宽;
- 写工作区:中等,最好有 diff / 可回看;
- 任意 shell / 网络 / 外部系统:紧,默认确认或拒绝;
- 会话恢复:不要无脑继承上次的“已批准随便跑”。
上下文同样是硬约束。长跑 Agent 会不断读文件、跑命令、堆日志;query.ts 在调模型前会做 snip / microcompact / autocompact 一类处理。你自己做,至少落地一件:
- 截断过大的 tool 输出;
- 或定期压缩历史;
- 或把脏活丢进子 Agent,只回收摘要。
没有这一层,Agent 不是越用越强,而是越用越胖、越用越飘。
第三层:Coordinator —— 多 Agent 该怎么加
很多人一听 multi-agent,就想“产品经理 + 程序员 + 测试”。Claude Code 的 Coordinator 模式更冷静,也更值得抄。
源码位置:src/coordinator/coordinatorMode.ts
文档:仓库 docs/04-coordinator.md
开关:编译期 feature('COORDINATOR_MODE') + 运行时 CLAUDE_CODE_COORDINATOR_MODE
Coordinator 模式不是“多几个都会写代码的分身”,而是:
| 角色 | 干什么 | 工具面 |
|---|---|---|
| Coordinator | 理解目标、拆任务、综合结果、对用户说话 | 主要是派活 / 通信 / 停工 |
| Worker | 具体研究、改代码、验证 | 完整(或受控)工具集 |

getCoordinatorSystemPrompt() 里写得很直白:你是 coordinator,要 direct workers 去 research / implement / verify,并 synthesize 结果;能直接回答的问题不要为了委派而委派。
工具上,提示词明确给了三类核心动作(名称以常量注入为准):
- Agent:派一个新 Worker
- SendMessage:给已有 Worker 追加指令(复用它已加载的上下文)
- TaskStop:停掉跑偏的 Worker
也就是说,指挥官默认不自己操刀改仓库,而是通过这三件事管理执行面。Worker 侧则从允许工具集里拿 Bash/Read/Edit 等(还可带 MCP),但会滤掉一批“内部编排工具”,避免 Worker 再去建团队、互相乱指挥。
标准工作流是四阶段,不是无限自由群聊。 提示词里把大多数任务拆成:
- Research(Worker,可并行):查代码、定位问题
- Synthesis(必须由 Coordinator 自己做):读发现、形成实施规格
- Implementation(Worker):按规格改
- Verification(Worker):证明改动真的生效,而不是“文件在就行”

并发策略也很工程:
- 只读研究:大胆并行
- 写同一批文件:串行,避免互相覆盖
- 验证:有时可与其他区域的实施并行
这比“所有 Agent 同时开麦”靠谱得多。
核心铁律:禁止甩锅式委派。Worker 看不到完整对话;每条派工必须自包含(路径、改什么、完成标准);Coordinator 必须自己综合,不能写 “based on your findings”。
源码提示词强调:
Workers can’t see your conversation.
Never write “based on your findings”…
You never hand off understanding to another worker.
这直接解决 multi-agent 最常见的翻车:上下文断裂 + 责任漂移。表面上有好几个 Agent,实际上没有人真正理解问题。
Continue 还是 Spawn,不是玄学。 Coordinator 要在“继续已有 Worker(SendMessage)”和“新开 Worker(Agent)”之间选:
| 场景 | 更合适 |
|---|---|
| 刚研究过的文件就是要改的文件 | Continue,复用上下文 |
| 研究很广、实施很窄 | 新开,避免噪声 |
| 修失败、小步推进 | Continue |
| 验证别人刚写的代码 | 新开,独立视角 |
| 第一方案完全错了 | 新开,降低锚定 |
结果怎么回来。 Worker 完成不是靠“聊天里互相 @”,而是结构化通知。提示词约定结果以类似 <task-notification> 的 XML 注入 Coordinator 消息流,带上 task-id、status、summary、result、usage。Coordinator 用 task-id 继续 SendMessage 或 TaskStop。
多 Agent 之间优先用结构化事件,而不是散文式互聊。 事件好路由、好重试、好展示、好计费。
Scratchpad:共享知识用文件,不靠嘴传。 共享状态放在可检视的文件里,不把所有东西硬塞进某个 Agent 的隐藏记忆。
自己实现一个迷你 Coordinator(可抄骨架)
不必复刻 Claude Code。最小可运行版本可以是:
Coordinator(只读工具 + 派工工具)
├─ spawn_worker(prompt, role)
├─ message_worker(id, prompt)
└─ stop_worker(id)
Worker(读写/shell,独立消息栈)
└─ 结束后返回 {id, status, summary, artifacts}再加三条硬规则:
- Coordinator 禁止直接 FileEdit / 任意 Bash(或默认关掉);
- 派工 prompt 必须包含路径、目标、验收标准;
- Research → Synthesis → Implement → Verify 默认按阶段走,写操作默认不并行碰同一文件。
任务板可以用最土的方式:仓库里一个 tasks.json / markdown 清单。文件不够炫,但可审计、可恢复。

如果你现在就要动手:一条建议路径
结合上一篇知识地图和这篇源码阅读,我会按这个顺序做:
不要反过来:先搭五人团队,再发现连 tool_result 都回灌不稳。
和上一篇怎么对上
| 上一篇的词 | 这篇源码里的落点 | 自己做时的优先级 |
|---|---|---|
| Agent Loop | query.ts while 循环 | P0 |
| Tool Calling | tools/* + 回灌 | P0 |
| Guardrails | canUseTool、权限模式 | P0 |
| State | messages / transcript / tasks 文件 | P0 |
| Context | compact / snip | P1 |
| MCP | 工具扩展总线 | P1 |
| Multi-Agent | Coordinator + Worker | P2(有并行/隔离需求再上) |
一句话:
先做“会停、会调工具、会回灌、会拒绝”的单 Agent;再做“会拆任务、会自包含派工、会收结构化结果”的 Coordinator。
结语
Claude Code 源码给我最大的开发启发,不是目录有多全,而是层次很清楚:
- 单 Agent 的核心是短循环 + 厚 harness;
- 多 Agent 的核心不是人设热闹,而是角色工具面分离、阶段化工作流、自包含派工、结构化回传;
- Coordinator 最值钱的一句是:理解不能外包。
如果你也在做 Agent,不妨把验收标准改成这几个问题:
- 没有 multi-agent 时,单循环能不能稳定改代码?
- 危险动作能不能被拦住且留下记录?
- 上了 Worker 之后,派工是否自包含,是否还在甩锅?
- Worker 结果是结构化事件,还是靠大家在上下文里“意会”?
这四个问题比“要不要再加一个角色”更接近真实开发。
参考资料
- pengchengneo/Claude-Code↗(非官方还原,仅学习)
- 本地:
src/QueryEngine.ts、src/query.ts、src/tools/、src/coordinator/coordinatorMode.ts、docs/04-coordinator.md - VILA-Lab/Dive-into-Claude-Code↗ / arXiv:2604.14228↗
- Anthropic: Building effective agents↗
- Anthropic: Writing effective tools for AI agents↗
- Model Context Protocol↗
说明:开关名、工具常量名、权限层数等会随版本变化;文中以本地还原树与社区文档为准,用于建立开发直觉,不保证与线上产品行为一一对应。
