1. 单Agent的瓶颈:为什么"一个人包打天下"越来越吃力
刚开始用 Codex CLI 那阵子,我确实觉得一个 Agent 就够了。写个函数、补个测试、解释一段报错,它都能接住。但项目一旦超过几千行、涉及多语言栈、还要同时改前端和后端,问题就来了:上下文窗口被塞满、任务边界模糊、改完 A 文件忘了 B 文件的依赖,最后变成"它很努力,但方向全错"。
这不是模型能力的问题,而是单 Agent 架构的固有天花板。一个 Agent 同时扮演需求理解者、架构设计者、编码实现者、测试验证者四个角色,每个角色对上下文的需求是冲突的。写代码需要大量局部细节,做架构需要全局视野,验证又需要独立的批判视角。把这些塞进一个上下文里,必然互相挤占。
我踩过最典型的一次坑:让 Codex 一次性重构一个 Express 项目的路由层,它改得很漂亮,但完全没注意到有个中间件依赖旧的路由命名。跑起来直接 500。它"看到"了那个中间件文件,但在长上下文里那个信息被稀释了。这就是单 Agent 的注意力衰减问题。
多 Agent 协同要解决的核心,就是把冲突的角色拆开,让每个 Agent 只背自己那份上下文。下面这张表是我实测下来单 Agent 和多 Agent 在几个维度上的差异:
| 维度 | 单 Agent | 多 Agent 协同 |
|---|---|---|
| 上下文占用 | 所有信息挤在一个窗口 | 每个 Agent 只加载职责相关上下文 |
| 任务边界 | 模糊,容易越界改动 | 明确,通过接口契约约束 |
| 错误发现 | 自己写自己验,盲区大 | 独立验证 Agent 能抓出实现者的盲区 |
| 并发能力 | 串行,一个任务一个任务来 | 可并行处理无依赖的子任务 |
| 调试成本 | 出错后难定位是哪一步 | 每个 Agent 有独立日志,定位快 |
需要说明的是,多 Agent 不是"越多越好"。我见过有人一上来就搞七八个 Agent,结果协调开销比干活还大。Agent 数量应该由任务的自然边界决定,而不是拍脑袋定的。
2. 拆角色的艺术:我的三Agent最小可用组合
多 Agent 协同最容易犯的错,是按"技术栈"拆——一个前端 Agent、一个后端 Agent、一个数据库 Agent。听起来合理,实际很糟,因为技术栈之间是强耦合的,拆开后沟通成本极高。我试过几轮之后,稳定下来的拆法是按职责阶段拆,最小可用组合是三个:
2.1 Planner:只做拆解,不碰代码
Planner 的职责非常纯粹:读需求,输出一份结构化的任务清单,每个任务包含目标、涉及文件、验收标准、依赖关系。它绝对不写代码,这是纪律。
为什么不让 Planner 顺手把代码也写了?因为一旦它开始写代码,它的上下文就会被具体实现细节污染,后续拆解任务时就会不自觉地"迁就"自己已经写的那部分,失去全局视角。我实测过,让 Planner 只输出 JSON 格式的任务清单,拆解质量比让它"边想边写"高出一大截。
Planner 的输出我固定成这个结构:
{ "tasks": [ { "id": "T1", "goal": "为 /api/users 增加分页参数校验", "files": ["src/routes/users.js", "src/validators/userValidator.js"], "acceptance": "传入 page<1 或 pageSize>100 时返回 400", "depends_on": [] }, { "id": "T2", "goal": "补充分页参数的单元测试", "files": ["tests/users.test.js"], "acceptance": "覆盖边界值 0、1、100、101", "depends_on": ["T1"] } ] }depends_on这个字段是多 Agent 并行的关键。没有它,Executor 就不知道该等谁,只能全部串行,多 Agent 的意义就没了。
2.2 Executor:按任务清单干活,一次只做一个
Executor 拿到单个任务,加载任务指定的文件,改完就退出。它不关心其他任务,也不做计划。这种"短生命周期"设计是刻意的——每个任务一个干净的上下文,避免长会话里的注意力衰减。
我一开始让 Executor 常驻,连续处理多个任务,结果发现它会把前一个任务的假设带到后一个任务里。比如 T1 里它假设了某个工具函数存在,T2 里就默认这个函数可用,但实际 T1 根本没创建它。改成"一个任务一个 Executor 实例"之后,这类串味问题基本消失。
2.3 Verifier:专职找茬,且必须独立
Verifier 是最容易被省略、但价值最高的角色。它拿到 Executor 的产出后,不看 Executor 的思路,只看结果和验收标准。它的提示词里我会明确写:"你的任务是找出这个实现不满足验收标准的地方,如果找不到,明确说明你验证了哪些边界。"
独立性的关键:Verifier 不能复用 Executor 的上下文。我见过有人为了省 token,让同一个会话里先写后验,结果 Verifier 满脑子都是"我刚才为什么这么写",根本挑不出毛病。必须开新会话,这是硬要求。
三个角色的分工用一张表说清楚:
| 角色 | 输入 | 输出 | 上下文策略 | 禁止事项 |
|---|---|---|---|---|
| Planner | 需求描述 | 任务清单 JSON | 全局,不加载具体代码 | 禁止写代码 |
| Executor | 单个任务 | 代码改动 | 只加载任务相关文件 | 禁止改任务范围外的文件 |
| Verifier | 改动+验收标准 | 验证报告 | 全新会话,只看结果 | 禁止参考实现思路 |
3. 用 Codex CLI 把协同跑起来:目录结构与调度脚本
理论说完了,落地才是关键。Codex CLI 本身是个命令行工具,它不内置多 Agent 调度,所以调度逻辑得我们自己写。我用的是最土但最稳的办法:一个 shell 脚本 + 三个提示词模板 + 一个共享的任务目录。
3.1 目录结构设计
project/ ├── .agents/ │ ├── prompts/ │ │ ├── planner.md │ │ ├── executor.md │ │ └── verifier.md │ ├── tasks/ │ │ ├── pending/ # Planner 产出的任务 │ │ ├── running/ # 正在执行的任务 │ │ └── done/ # 已完成的任务 │ └── logs/ │ ├── planner.log │ ├── executor.log │ └── verifier.log └── src/这个结构的好处是状态全部落在文件系统上,任何一步崩了都能从文件恢复,不用维护内存里的状态机。任务在pending里就是待办,被 Executor 拿走就移到running,Verifier 通过后移到done。简单粗暴,但极其可靠。
3.2 Planner 的调用
Planner 只需要跑一次,把需求喂进去:
codex exec \ --prompt-file .agents/prompts/planner.md \ --input "需求:给用户列表接口加分页和参数校验" \ > .agents/tasks/planner_output.json然后写个小脚本把planner_output.json拆成一个个任务文件丢进pending/:
import json, os, pathlib with open(".agents/tasks/planner_output.json") as f: data = json.load(f) pending = pathlib.Path(".agents/tasks/pending") pending.mkdir(parents=True, exist_ok=True) for task in data["tasks"]: (pending / f"{task['id']}.json").write_text( json.dumps(task, ensure_ascii=False, indent=2) )3.3 Executor 的调度循环
Executor 的调度核心是依赖检查。一个任务只有在它所有depends_on都进了done/之后才能执行:
#!/bin/bash while true; do for task_file in .agents/tasks/pending/*.json; do [ -e "$task_file" ] || break task_id=$(basename "$task_file" .json) deps=$(jq -r '.depends_on[]?' "$task_file") ready=true for dep in $deps; do [ -f ".agents/tasks/done/$dep.json" ] || ready=false done if [ "$ready" = true ]; then mv "$task_file" ".agents/tasks/running/$task_id.json" codex exec \ --prompt-file .agents/prompts/executor.md \ --input "$(cat .agents/tasks/running/$task_id.json)" \ >> .agents/logs/executor.log 2>&1 mv ".agents/tasks/running/$task_id.json" ".agents/tasks/done/$task_id.json" fi done sleep 2 done这段脚本我用了很久,sleep 2是防止空转烧 CPU。如果你要并行跑多个 Executor,把for循环里的执行部分丢到后台加&就行,但要注意同一文件不能被两个任务同时改,这个约束得在 Planner 拆任务时就保证。
3.4 Verifier 的触发
Verifier 在任务进done/之后触发,开全新会话:
codex exec \ --prompt-file .agents/prompts/verifier.md \ --input "任务:$(cat .agents/tasks/done/T1.json) 改动文件:$(git diff --name-only HEAD~1)" \ > .agents/logs/verify_T1.log注意:Verifier 的输入里我特意只给"任务定义"和"改动文件列表",不给 Executor 的对话记录。这是保证独立性的关键,宁可多花点 token 重新读文件,也不要让 Verifier 被实现思路带偏。
4. 提示词模板:三个角色各写什么
多 Agent 协同的效果,八成取决于提示词。我调了很多版,下面这三份是当前稳定在用的,直接可以抄。
4.1 Planner 提示词
你是一个任务拆解专家。你的唯一职责是把需求拆成可独立执行的任务清单。 规则: 1. 每个任务必须能由一个不了解其他任务的执行者独立完成 2. 每个任务必须指定明确的文件范围 3. 每个任务必须有可验证的验收标准 4. 用 depends_on 标注任务依赖,无依赖的任务会被并行执行 5. 你绝对不写任何代码,只输出 JSON 输出格式: {"tasks": [{"id": "T1", "goal": "...", "files": [...], "acceptance": "...", "depends_on": [...]}]}第 1 条规则是灵魂。它逼着 Planner 把任务拆到"自包含"的程度,而不是"你懂的"那种模糊描述。
4.2 Executor 提示词
你是一个代码执行者。你只处理分配给你的这一个任务。 规则: 1. 只修改任务 files 字段列出的文件,绝不越界 2. 严格按 acceptance 字段实现,不多做也不少做 3. 如果发现任务描述有歧义,停下来输出 "BLOCKED: 原因",不要猜 4. 完成后输出改动摘要,不要输出完整代码 任务: {{TASK_JSON}}第 3 条"BLOCKED"机制特别有用。Executor 遇到歧义时硬猜,往往就是 bug 的来源。让它主动阻塞,把问题抛回给 Planner 或人,比它自作聪明强得多。
4.3 Verifier 提示词
你是一个独立的验证者。你没有参与实现,也不应该假设实现是正确的。 规则: 1. 逐条对照 acceptance 字段验证 2. 主动构造边界用例,不要只跑 happy path 3. 如果发现不满足,明确指出文件和行号 4. 如果全部通过,列出你验证了哪些边界 任务定义: {{TASK_JSON}} 改动文件: {{CHANGED_FILES}}Verifier 提示词里"你没有参与实现"这句话是刻意写的,它会显著提升挑刺的积极性。实测下来,加了这句话之后,Verifier 抓出的边界问题多了将近一倍。
5. 实测中的坑:协同不是免费的午餐
多 Agent 跑起来之后,我踩的坑比单 Agent 时期还多,只是坑的类型变了。下面这几个是最值得说的。
5.1 任务粒度太粗,Executor 直接摆烂
第一次拆任务,我让 Planner 拆得"粗一点",结果它给出一个"实现用户模块"的任务,涉及 12 个文件。Executor 拿到之后,改了两个文件就输出"完成"。为什么?因为上下文塞不下 12 个文件,它只能挑重点改,剩下的它"以为"不用改。
任务粒度的经验值:单个任务涉及文件不超过 3 个,改动行数预期不超过 150 行。超过这个量级,Planner 就该继续拆。这个数字不是拍脑袋的,是我统计了二十多个任务后,Executor 完成质量开始明显下降的临界点。
5.2 依赖环:Planner 也会犯糊涂
有一次 Planner 拆出 T1 依赖 T2、T2 又依赖 T1 的循环。调度脚本直接死锁,两个任务永远在pending里等对方。后来我在调度脚本里加了个检测:
def has_cycle(tasks): graph = {t["id"]: t["depends_on"] for t in tasks} visited, stack = set(), set() def dfs(node): if node in stack: return True if node in visited: return False visited.add(node); stack.add(node) for dep in graph.get(node, []): if dfs(dep): return True stack.remove(node) return False return any(dfs(n) for n in graph)Planner 输出后先跑一遍这个检测,有环就打回重拆。别指望模型永远不犯错,用代码兜底比用提示词祈祷靠谱。
5.3 Verifier 太宽松,形同虚设
早期 Verifier 经常输出"看起来没问题"。我分析了一下,原因是它的提示词里"验证"这个词太温和。改成"你的任务是找出这个实现不满足验收标准的地方"之后,它变得挑剔多了。措辞对模型行为的影响,比我想象的大得多。
还有一个技巧:给 Verifier 一个"必须列出至少一个潜在风险"的硬性要求,哪怕实现完全正确。这会逼它认真思考边界,而不是敷衍通过。
5.4 上下文串味:共享文件是重灾区
多个 Executor 并行时,如果两个任务都碰了同一个工具文件,后写的会覆盖先写的。我的解法是在 Planner 阶段就做文件级互斥:同一个文件只能出现在一个任务的files里。如果确实需要多个任务改同一文件,就强制串行,用depends_on串起来。
这个约束听起来很严,但它把并发冲突从"运行时随机出现"变成了"拆解时就能发现",排查成本天差地别。
6. 并发与成本:多 Agent 到底值不值
聊到这儿肯定有人问:多 Agent 是不是更费钱?答案是看任务类型。我拿同一个重构任务做了对比测试:
| 方案 | Token 消耗 | 耗时 | 一次通过率 |
|---|---|---|---|
| 单 Agent 串行 | 约 45k | 12 分钟 | 60% |
| 三 Agent 协同 | 约 78k | 7 分钟 | 88% |
Token 多了七成,但耗时少了四成,一次通过率从 60% 提到 88%。关键在那个一次通过率——单 Agent 方案里,40% 的情况要人工返工,返工的时间成本远超多花的 token。
所以我的判断标准是:任务越复杂、验收标准越明确,多 Agent 越划算。反过来,如果只是改个变量名、加个日志,单 Agent 直接上,别搞协同,纯属浪费。
6.1 并发度的控制
并行不是越多越好。我实测下来,同时跑 3 个 Executor 是甜点。超过 3 个之后,文件冲突概率上升,而且 Codex CLI 的调用本身有速率限制,排队反而更慢。这个数字跟你的机器配置和账号额度有关,可以自己压测找平衡点。
6.2 成本优化的几个实操点
- Planner 只跑一次,别每个任务都重新规划,那是纯浪费。
- Verifier 只验证改动文件,不要让它读整个仓库。
- 任务清单用 JSON 而不是自然语言,模型解析 JSON 比解析散文省 token。
- 失败的验证报告要缓存,同一个问题别让 Verifier 反复发现。
7. 从三 Agent 到更多:什么时候该扩展
三 Agent 能覆盖大部分场景,但有些任务确实需要更多角色。我扩展过的两个场景:
场景一:需要外部知识时加 Researcher。比如任务涉及一个我不熟的第三方库,Planner 拆出来的任务里 Executor 老是猜 API 用法。加一个 Researcher,专门去查文档、输出 API 用法摘要,Executor 拿着摘要干活,准确率明显提升。
场景二:需要长期维护时加 Reviewer。如果项目要持续迭代,加一个 Reviewer 定期扫描done/里的改动,检查是否有技术债累积、命名是否一致。它不阻塞流程,只在后台跑,输出改进建议。
但我要泼盆冷水:每加一个 Agent,协调复杂度是平方级上升的。三 Agent 的交互路径是 3 条,四 Agent 就是 6 条,五 Agent 是 10 条。我建议先用三 Agent 跑顺,真的遇到瓶颈了再加,别一上来就堆角色。
7.1 一个判断该不该加 Agent 的土办法
问自己:这个新角色能不能用一句不带"和"的话描述它的职责?能,就加;不能,说明职责还没想清楚,加了也是添乱。比如"Researcher 负责查文档并输出 API 摘要"——可以。"Reviewer 负责检查代码质量和命名规范并给出建议"——这里有个"并",说明它其实是两个角色,得再拆。
8. 我踩过的那些具体报错和修法
最后分享几个实操中真实遇到的报错,都是热词里高频出现的,估计不少人也卡过。
unable to locate the codex cli binary:这个基本是 PATH 没配好。装完之后which codex确认一下,没有的话把 npm 全局 bin 目录加进 PATH。别急着重装,九成是路径问题。
cc switch local proxy failed while handling codex endpoint:这类报错通常跟本地网络配置有关,检查一下是不是有别的进程占了端口,或者配置文件里的地址写错了。我遇到过一次是配置文件里多了个空格,排查了半小时。
npm: 无法加载文件:Windows 上 PowerShell 的执行策略问题。用管理员权限跑一次Set-ExecutionPolicy RemoteSigned就好,但改完记得心里有数,这是系统级设置。
Codex 无法发送消息:先看是不是上下文超了。多 Agent 场景下特别容易超,因为每个 Agent 都往会话里塞东西。我的做法是给每个 Agent 设一个 token 预算,超了就强制截断,宁可信息少点也别整个会话崩掉。
这些报错看着杂,但规律是一样的:先确认环境,再确认配置,最后才怀疑模型。我见过太多人一报错就重装,其实问题根本不在安装上。
多 Agent 协同这套东西,说到底不是技术炫技,而是把"一个人扛所有"变成"每个人扛自己那份"。Codex 是个好工具,但让它一个人包打天下,它累,你也累。拆开之后,每个环节都简单了,出问题也好定位了。我现在做稍大一点的项目,基本都会先花十分钟搭好这套骨架,后面省下的返工时间远不止十分钟。