Skip to content

第 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:

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 界面?)

小结

这一章的五个要素,每一个都对应一个真实的坑:

  1. 异步执行 —— 对应"群聊卡死"
  2. brief 解耦 —— 对应"上下文爆炸"
  3. 双层验证 —— 对应"烂产出直接交付"
  4. 自动重试 —— 对应"偶发失败全推给人"
  5. 边界权限 —— 对应"要么不安全要么不好用"

但最诚实的总结是:就算五个要素都做到了,协作也不会完美。 自动重试有天花板(解决不了系统性缺陷),LLM 裁判会判错,权限审批增加摩擦。双 Agent 协作是一个"工程上可行但永远需要维护"的方案,不是一个"搭好了就不用管"的银弹。

下一章,我们把视角拉到最高,看看这套协作在整个系统里处于什么位置——人、PM Agent、开发 Agent,三层怎么协同。

下一章

第 10 章 · 人-Agent-Agent 三层模型 —— 从整体架构看双 Agent 协作,以及人在这套体系里扮演什么角色。