第 7 章:多代理与验证 — 用分工和验证管理不稳定性
出处
来源:原网页 · 状态:已读 · PDF:book1-claude-code.pdf p.57–64
一句话
多代理依赖清晰分工:研究、实现、验证和综合各自处在不同约束容器里,最后由协调者把结果重新缝合成可交付结果。 真正困难的是隔离各 agent 的不稳定性,同时把结果组织回来。并行的真正价值在于职责更清楚,不在人海战术。
核心概念
任务一大,研究、实现、验证就挤在同一条上下文链上抢预算、抢注意力、抢叙事中心。不加约束的并行只会把单代理的混乱复制几份。Subagent 不是「另一个会说话的窗口」,而是一段需要明确缓存边界、状态边界、验证职责和清理责任的受管执行流程。
Fork 的第一原则是 cache-safe
forkedAgent.ts 职责按这个顺序写:与父代理共享 cache-critical params(确保 prompt cache hit)→ 跟踪整个 query loop 的 usage → 记录指标 → 隔离可变状态,防止干扰主循环。最先出现的是缓存,并非偶然。fork 是运行时层面的受控分叉,哪些参数必须和父请求保持一致,否则 prompt cache 共享失效,成本和延迟立刻变坏。
CacheSafeParams:systemPrompt、userContext、systemContext、toolUseContext、forkContextMessages。专门提醒:别随便改 maxOutputTokens,因为 thinking config 也会受影响,而 thinking config 又是 cache key 的一部分。多代理首先是运行时经济学问题。子代理每次都把父上下文重新烧一遍 token,看上去像并行提效,实际只是把浪费并行化。
状态隔离:子代理首先要减少污染
createSubagentContext() 默认隔离所有 mutable state:readFileState 先 clone;abortController 生成 child controller;getAppState 包装以避开 permission prompt;setAppState 默认 no-op;nested memory 相关集合都重新建。只有明确 opt-in 才共享(shareSetAppState、shareSetResponseLength、shareAbortController)。
子代理最宝贵的地方,在于避免把自己的局部混乱污染主线程。研究中的误判、临时读到的文件状态、一次性推理枝杈、正在进行的工具决策,如果全都直接写回主上下文,得到的只会是更快的脏化。共享要靠明确同意,隔离才是默认伦理。 像数据库事务,不像聊天玩具。
不变式:child.CacheSafeParams == parent.CacheSafeParams;可变状态默认隔离 unless opt-in;父 abort ⇒ 传播 child.abort(父死子亡);SubagentStart fired ⇒ SubagentStop 最终 fired;verification_worker ≠ implementation_worker。
协调者模式:synthesis 才是稀缺能力
Coordinator 的工作:帮用户达成目标;指挥 worker 做 research / implementation / verification;综合结果并和用户沟通;能直接回答的问题就直接回答,不要滥委派。
命门一句:Always synthesize. Worker 回报研究结果后,协调者必须先读懂,再写出具体 prompt;不要说「based on your findings」,不要把理解继续外包给 worker。真正稀缺的不是并行输出,而是有人把 worker 的局部知识重新压成清晰、可执行、可验证的下一步。缺这一层,多代理很快退化成礼貌的任务转发机。研究可以分布式,理解必须重新收束——后续 prompt 必须出现具体文件、具体位置、具体变更。
验证必须独立成阶段
常见任务分成 Research / Synthesis / Implementation / Verification。Verification 的目标是证明代码有效,而不只是确认代码存在。源码近乎不留情面:run tests with the feature enabled;investigate errors, don't dismiss as unrelated;be skeptical;test independently, don't rubber-stamp。
「我改了代码」和「代码因此正确」之间隔着一条很宽的河,模型尤其擅长在上面搭纸桥——它会给出改动、解释、甚至像样的测试输出,但这些都不等于功能真的在系统里站住。实现专注于改,验证专门怀疑这些改动配不配活着。否则「会改代码」很快冒充「能交付结果」。
子代理不是扔出去就算了
Spawn 只是开头,收尾同样重要。SubagentStart / SubagentStop hook:启动时可观测(agent_id、agent_type);停止前可介入(带 agent_transcript_path,exit code 2 可把 stderr 反馈给 subagent 继续跑)。registerAsyncAgent() 注册 cleanup handler,父 abort 自动传播给子 abort controller;任务结束后 evict output、更新状态、解除 cleanup 注册。
关心的核心问题:这个 agent 是否仍在运行;父任务死了它是否该跟着死;输出文件是否还要保留;cleanup callback 有没有泄漏。很多 demo 只做到「我能再起一个 agent」;Claude Code 把 agent 当作会泄漏资源、会残留状态、会在父进程结束后变成孤儿的运行实体。这才像把代理当系统组件处理。
| 事件 | 下一步 |
|---|---|
| parent abort + child in-flight | 传播 child.abort,等 cleanup;不可留孤儿 |
| child crash | evict output,fire stop hook;exit 2 允许 stderr 回灌续跑 |
| child timeout | abort child,写 synthetic result |
| CacheSafeParams 被改 | 拒绝 fork 或重算 cache;必须完整对齐 |
| child 试图写未声明共享的状态 | no-op,不污染父;默认隔离 |
| 记忆与现状不符 | 信任现状,必须先 verify 再 update/delete memory |
| task 结束未取消 cleanup handler | 强制 unregister + evict;不可留回调 |
验证不仅针对代码,也针对记忆和建议
memoryTypes.ts 提醒:memory records can become stale;基于 memory 给用户建议之前,要先 verify current state;记忆与现状冲突,要相信眼下读到的真实状态,并更新或删除 stale memory。验证是整个系统用来抵抗时间漂移和上下文漂移的基本习惯。只验证新写下去的代码、不验证旧记忆旧假设旧索引,仍然会被历史信息带偏。verify 既是 skill,也是组织纪律:在用户准备据此行动之前,总要有人回到当前现实,重新确认这些东西还是真的。
多代理真正解决的是不确定性的分区
研究 worker 在局部上下文里探索,实现 worker 专注修改,验证 worker 专门怀疑,coordinator 在中间收束、综合、接口用户。职责清晰,错误可定位:research 漏线索、synthesis 没吃透、implementation 写错、verification 放水——每一种都能被指认。一锅浓汤式的单代理方案出问题只能整体返工。把不同种类的不确定性关进不同容器,再由 coordinator 组织回来。
关键洞察
抽成工程原则:fork 先看 cache 与状态边界,再谈「人格分工」;子代理默认隔离,可共享必须显式;研究可委派、综合不可外包;验证必须与实现解耦,否则系统会奖励自证正确;agent 生命周期必须可观测、可中止、可清理;并行的真正价值在于职责更清楚。
个人思考
这章把「不要让系统自己给自己打分」写进了运行时:verification_worker ≠ implementation_worker。对照 Anthropic AI-native SDLC 的闸门——人守闸门、循环自己转——这里是机器侧先把实现和验证拆开,人再守更外一层。下一章:整套机制落到团队里,如何从个人技巧变成组织制度。