第 9 章 双 Agent 协作五要素
第 8 章讲了什么时候该拆。这一章讲拆了之后怎么协作。我从实践中提炼了五个要素——不是理论推导出来的,是每一个都对应一个踩过的坑。
开篇:CC 卡住了,但我不知道为什么
我第一次让 Agent 调 Claude Code 生成网页的时候,出了一个问题。
Agent 在群里说了一句"我去处理了,好了通知你",然后……就没有然后了。群聊安静了好几分钟。我打开日志一看,CC 确实在跑,但它卡在了权限请求上——它想执行一个操作,需要人批准,但这个请求没有传到群里给我。日志里 CC 的输出还被截断了,我根本看不到它到底想干什么。
这就是"双 Agent 协作"刚上手时的真实状态——你以为让一个 Agent 调另一个 Agent 很简单,实际上每一个环节都藏着坑。后来花了不少时间把协作链路一环环补齐,总结下来五个要素。
先看全貌:三层协作架构
在逐个展开五个要素之前,先给一张全景图,让你知道这些要素最终拼成一个什么东西。
我们的目标架构是这样的:
┌─────────────────────────────────────────────────┐
│ 人 │
│ 职责:审批越界操作、重试失败后兜底 │
│ 介入频率:低(只在关键节点出现) │
└──────────────────────┬──────────────────────────┘
│ 审批 / 兜底
┌──────────────────────▼──────────────────────────┐
│ PM Agent(主 Agent) │
│ 职责:理解需求、拆任务、写 brief、验收产出 │
│ 介入频率:中(每个任务都参与) │
└──────────────────────┬──────────────────────────┘
│ 调用 + 验证
┌──────────────────────▼──────────────────────────┐
│ 开发 Agent(子 Agent) │
│ 职责:搜索、编码、文件操作 │
│ 介入频率:高(被调时执行) │
└─────────────────────────────────────────────────┘人、PM Agent、开发 Agent,三层。人只在关键节点出现,PM Agent 管日常,开发 Agent 干活。
接下来要讲的五个要素,就是让这三层真正能跑起来的五个工程环节。它们分别对应这条链路上的五个关键问题:怎么调(异步)、怎么传任务(brief)、怎么验产出(双层验证)、失败了怎么办(重试)、权限怎么管(边界控制)。
要素一:异步执行,别阻塞主流程
第一个要解决的问题是:CC 跑一次要一两分钟,这段时间群聊怎么办?
如果同步等 CC——Agent 发起调用,死等 CC 跑完,再继续——群聊会卡住一两分钟没有任何响应。用户体验极差。
我的解法是异步。Agent 调 CC 的时候,引擎在后台启动 CC 进程,然后立刻返回给 Agent 一句话:"已启动后台任务,CC 执行中,完成后会通知。" Agent 拿到这句话,马上在群里回复用户"我去处理了,好了通知你"。群聊不阻塞。
那 CC 跑完之后怎么通知 Agent?这里有个关键设计叫合成触发。CC 执行结束后,引擎不直接把结果塞给 Agent,而是构造一条新消息,当作 Agent 收到的一条新输入,重新走一遍 Agent 循环。这条消息的内容是引擎拼出来的:
- 验证通过:"CC 任务完成,验证通过。项目已生成文件:xxx。请读取项目计划文件恢复上下文,然后通知群里。"
- 验证没过:"CC 任务完成但验证未通过。质量反馈:xxx。请读取项目计划文件。"
- 彻底失败:"CC 任务失败。没有生成文件。"
为什么要这么做?因为 CC 跑了一两分钟,Agent 那时候的上下文早就被其他群聊消息挤走了。直接在旧上下文上追加 CC 的结果,Agent 已经不记得自己当初为什么要启动这个任务。合成触发让 CC 的结果作为一条新消息进来,Agent 重新构建上下文,重新理解"我在处理一个什么任务"。
还有一个防重入锁:同一个项目如果已经有 CC 在跑了,再调一次会被拒绝。因为 CC 写的是文件,两个 CC 同时写同一个项目会打架——这正好印证了第 8 章说的"写入操作要串行"。
要素二:给指针,不给内容
第二个问题是:Agent 怎么把任务传给 CC?
最直觉的做法是把所有信息塞进 CC 的 prompt 里——项目背景、需求细节、用户偏好,全写进去。我试过,结果上下文爆炸。CC 的上下文窗口被这些原始信息塞满,反而没空间干活了。
后来我换了个思路:给指针,不给内容。
Agent 调 CC 的时候,CC 收到的指令只有两样东西:一个文件路径,一句简短的任务描述。比如:
项目计划文件在 /path/to/plan.md,请先读取它了解项目上下文。
任务:生成初稿网页所有细节都在 plan.md 里。而 plan.md 是引擎自动生成的——从项目的数据库记录里渲染出一份结构化的 Markdown:基本信息、目标、约束、已确认的决策、待解决的问题、当前进度、输出要求。
CC 自己去读这个文件,按需获取信息。这样 CC 的上下文窗口留给了真正的工作(写代码),而不是被一堆背景信息占满。
这个设计还有个额外好处:plan.md 可以增量演进。每次 CC 跑完,引擎把执行结果和质量反馈追加到 plan.md 的历史记录里。下次 CC 再跑,它能看到"上次我做了什么、哪里没做好"。这不是从头开始,是带着记忆继续。
CC 还支持 --resume 参数——同一个项目的 CC 会话 ID 会被保存下来,下次调用时复用这个会话。CC 记得"上一次我们做到哪了",不用每次重新解释项目背景。
要素三:双层验证,不能信任子 Agent 的产出
第三个问题最关键:CC 跑完了,产出了一份网页,怎么知道它做得对不对?
第 8 章讲过——子 Agent 是工具不是同事,它的产出不能直接信任。那怎么验证?我做了两层。
第一层:自动化检查。 纯代码,不涉及 LLM。检查文件存不存在、大小够不够、HTML 结构完不完整(有没有 <html> 标签、<body> 标签、闭合标签、内容长度够不够)。这些是机器能客观判断的,快且准。
第一层通过之后,才进第二层。
第二层:LLM 裁判打分。 用一个独立的 LLM 调用,按 brief 的要求评估 CC 产出的 HTML 质量。裁判输出严格 JSON:
{"score": 8, "feedback": "内容完整但样式简单", "meets_requirements": true, "issues": ["配色单调"]}score ≥ 7 算通过,低于 7 判失败。 7 分这个阈值是试出来的——太严(要 9 分才过)会导致大量"其实还行但要返工"的浪费;太松(5 分就过)会放过明显有问题的产出。
为什么用两层而不是一层?因为它们各有盲区。自动化检查只能看"格式对不对",看不出"内容好不好"。LLM 裁判能看内容,但它自己也不稳定(第 7 章讲过这个问题)。两层叠加——先过客观的格式关,再过主观的质量关——能拦掉大部分问题。
要素四:验证失败要能自动重试
第四个问题:验证没过怎么办?
最简单的做法是告诉用户"CC 做的东西不行,你再试一次"。但这样体验很差——用户什么都没做错,凭什么要他来处理 CC 的失败?
我的解法是自动重试。验证失败后,引擎把质量反馈拼进任务描述,重新启动 CC:
任务:生成初稿网页
## 上次执行的问题
配色单调,缺少响应式布局。
请修正以上问题。CC 拿到带反馈的新任务,加上 --resume 保持会话,相当于"带着上次的记忆和这次的改进意见继续做"。最多重试两次(maxCCRetries = 2)。
但这里有个诚实的教训要讲。 自动重试不是万能的。我有一次跑一个比较复杂的任务,连续重试了三次全挂了。翻 plan.md 发现——三次的反馈内容几乎一样,CC 每次都在同一个地方犯错。重试机制把同样的反馈塞了三遍,CC 还是没改对。
这说明什么?自动重试能解决"偶发失误",解决不了"系统性缺陷"。 如果 CC 是因为偶发的随机性没做好,带着反馈重试能修好。但如果 CC 是因为能力不够(比如它就是不会做某种布局),重试多少次都没用——这时候应该停下来,让人介入。
要素五:边界感知的权限控制
最后一个问题,也是我开头那个"CC 卡住但没通知我"的坑的根源:CC 执行的时候需要各种权限(读文件、写文件、跑命令),怎么管?
最粗暴的做法是全放行——给 CC 加 --dangerously-skip-permissions,让它想干什么干什么。我一开始就是这么干的。但这个命令的名字里带"dangerously"不是开玩笑的——CC 可以不受限制地读写任何文件、执行任何命令。在群聊场景里,用户随时可能发消息触发 CC,全放行等于给所有人一个没有护栏的 root 权限。
后来我做了边界感知的权限控制。逻辑很简单:
CC 工作目录内的文件操作(Write/Edit)→ 自动批准。 因为工作目录就是给它用的,在里面写文件是预期行为。
工作目录外的操作,或者任何 Bash 命令 → 推送到群聊,让人审批。 因为这些操作的后果不可控——CC 可能读到不该读的文件,或者执行一条危险命令。
每次调 CC 之前,引擎还会在它的工作目录下预生成一份 .claude/settings.json 白名单,列出允许的工具和命令。CC 在白名单内的操作不需要额外审批,白名单外的才走人工审批。
审批的交互是在群聊里完成的——越界操作触发一个权限请求事件,推到群里,用户看到后点"允许"或"拒绝",结果传回引擎,引擎再把"y"或"n"写进 CC 的标准输入。
这套机制做完之后,开头那个"CC 卡住但没通知我"的问题就解决了——越界操作会自动推到群里,我能看到 CC 想干什么,决定放不放行。
五要素全景
把五个要素串起来,双 Agent 协作的完整链路是这样的:
用户在群里说"帮我做个网页"
│
▼
PM Agent 收到,判断需要 CC
│
├─ 要素②:写 plan.md,给 CC 文件路径不给内容
│
├─ 要素①:异步启动 CC,群里回复"我去处理了"
│
▼
CC 后台执行(1-2 分钟)
│
├─ 要素⑤:工作区内自由操作,越界推送群聊审批
│
▼
CC 完成,产出文件
│
├─ 要素③:第一层自动化检查 + 第二层 LLM 裁判打分
│
├── 通过 → 合成触发通知 Agent → Agent 通知群里
│
└── 没过 → 要素④:带反馈自动重试(最多 2 次)
│
├── 重试后通过 → 通知
└── 重试用完仍失败 → 通知用户,人介入五个要素缺一个都会出问题:
- 没有异步 → 群聊卡死
- 没有 brief 解耦 → 上下文爆炸
- 没有双层验证 → 烂产出直接交付
- 没有自动重试 → 偶发失败全推给人
- 没有权限控制 → 要么不安全(全放行),要么不好用(全审批)
这一章的工具:双 Agent 协作检查清单
🔧 双 Agent 协作检查清单
如果你在让一个 Agent 调用另一个 Agent,逐项检查:
异步与回调
- [ ] 子 Agent 执行的时候,主流程会不会卡住?
- [ ] 子 Agent 完成后,结果怎么传回主 Agent?(合成触发?回调函数?轮询?)
- [ ] 主 Agent 收到结果时,上下文还在吗?(不在的话怎么恢复?)
上下文传递
- [ ] 你是把内容塞进 prompt,还是给文件路径让子 Agent 自己读?
- [ ] 子 Agent 有没有"上次的记忆"?(会话保持?历史记录?)
- [ ] 任务描述是结构化的,还是自然语言闲聊?
产出验证
- [ ] 子 Agent 的产出有没有自动验证?(还是直接信任?)
- [ ] 验证是单层还是双层?(格式检查 + 质量评估?)
- [ ] 验证标准写清楚了吗?(LLM 裁判有没有锚点?)
失败处理
- [ ] 验证失败后会自动重试吗?
- [ ] 重试带不带反馈?(还是盲目重试?)
- [ ] 重试上限设了吗?用完之后怎么办?
权限与安全
- [ ] 子 Agent 的操作权限是怎么管的?(全放行?全审批?边界感知?)
- [ ] 什么操作自动批准,什么操作需要人审批?标准是什么?
- [ ] 审批的交互在哪里完成?(群聊?命令行?Web 界面?)
小结
这一章的五个要素,每一个都对应一个真实的坑:
- 异步执行 —— 对应"群聊卡死"
- brief 解耦 —— 对应"上下文爆炸"
- 双层验证 —— 对应"烂产出直接交付"
- 自动重试 —— 对应"偶发失败全推给人"
- 边界权限 —— 对应"要么不安全要么不好用"
但最诚实的总结是:就算五个要素都做到了,协作也不会完美。 自动重试有天花板(解决不了系统性缺陷),LLM 裁判会判错,权限审批增加摩擦。双 Agent 协作是一个"工程上可行但永远需要维护"的方案,不是一个"搭好了就不用管"的银弹。
下一章,我们把视角拉到最高,看看这套协作在整个系统里处于什么位置——人、PM Agent、开发 Agent,三层怎么协同。
下一章
第 10 章 · 人-Agent-Agent 三层模型 —— 从整体架构看双 Agent 协作,以及人在这套体系里扮演什么角色。