
看到 Graph Engineering 的讨论后，我一直想算一笔更具体的账：多开几个 Agent 到底值不值。

我拿同一份代码审查样本跑了两组实验：

1. 一个 Agent 独立完成综合审查。
2. 三个 Agent 分别检查安全、并发与生命周期、I/O 与可靠性，再合并结果。

Anthropic 公布的 Research 系统数据中，多 Agent 使用的 token 大约是普通聊天的 15 倍；需要共享完整上下文或包含大量紧密依赖的任务也不适合多 Agent。[^anthropic-multi-agent] 所以这次除了看发现了多少问题，我还记录了耗时、输出 token、重复结论和误报。

结果很直白：三个 Agent 确实找得更多，但增加的主要是中风险边界，token 和去重工作也跟着上去了。

<!--more-->

## 实验怎么做

### 审查样本

样本是一份 88 行的 Go HTTP 导出功能 diff。接口接收文件名和一组 URL，在后台下载内容、写入文件，并通过另一个接口返回任务状态。

我在样本里保留了 12 个可以从代码或 Go 官方文档确认的问题，覆盖：

- 并发读写。
- Request Context 生命周期。
- 文件路径与并发写入。
- SSRF。
- HTTP 超时和状态码。
- 错误处理、资源释放与容量限制。

完整的[审查样本](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/review-fixture.diff)、[统一审查要求](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/review-prompt.txt)和实验结束后公开的[答案表](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/oracle.txt)都放在文章目录中。Reviewer 执行时只能读取样本和审查要求，看不到答案表。

答案表中有两项直接依赖 Go 的运行语义：

- Server Request 的 Context 会在 `ServeHTTP` 返回时取消。[^go-http]
- `http.DefaultClient` 的总 Timeout 为零，也就是没有总超时。[^go-http]

普通 Go map 也不支持未同步的并发读写。[^go-maps]

### 两组 Reviewer

两组使用相同的 Agent 配置和新上下文。

单 Agent 组收到统一要求：只报告 diff 引入的具体正确性、安全、可靠性或并发问题；每条 finding 必须指出代码位置和现实失败路径。

三 Agent 组读取相同 diff 和要求，但各自增加一个审查范围：

| Reviewer | 负责范围 |
| --- | --- |
| Security | 文件路径、外部 URL、不可信输入、信息泄漏 |
| Concurrency | goroutine、Request 生命周期、共享状态、ID 和并发写入 |
| Reliability | HTTP 行为、I/O 错误、资源释放、超时和状态报告 |

三名 Reviewer 并行运行，完成后由主会话按失败原因去重。相同问题即使标题或严重程度不同，也只算一个有效发现。

统一要求之外，每个 Reviewer 只增加表中的一项职责。Anthropic 把这种做法称为 Parallelization 的 sectioning：把独立的关注点交给不同 LLM 调用，再统一聚合。[^anthropic-building-agents]

### 指标口径

- 原始 finding：Reviewer 返回的 finding 数量。
- 有效发现：可以映射到答案表，并且失败路径成立的唯一问题。
- 重复结论：原始 finding 减去合并后的唯一问题。
- 误报：答案表之外、同时缺少代码支持的失败路径。
- 漏报：答案表中没有被发现的问题。
- 耗时：从派发到全部 Reviewer 返回的墙钟时间。

平台没有提供每个 Agent 的账单级输入、推理和工具 token。文中的 token 只采用一套可复现的文本口径：

1. 使用 `gpt-tokenizer` 的 GPT-4o encoding 统计输出 token。
2. 「可观察 token 下限」等于样本与统一 prompt 的 token，加上输出 token。
3. 不包含 system prompt、角色补充指令、内部推理和工具调用，因此不能当作实际账单。

同一套 tokenizer 下，样本和统一 prompt 共 753 token。多 Agent 组需要至少读取三遍，所以即使还没生成结果，已经重复消耗了这部分输入。

## 实验结果

| 指标 | 单 Agent | 三 Agent |
| --- | ---: | ---: |
| 墙钟时间 | 41 秒 | 58 秒 |
| 原始 finding | 8 | 19 |
| 有效发现 | 8 | 11 |
| 重复结论 | 0 | 8 |
| 误报 | 0 | 0 |
| 漏报 | 4 | 1 |
| 答案表覆盖率 | 66.7% | 91.7% |
| 输出 token | 631 | 1552 |
| 可观察 token 下限 | 1384 | 3811 |

汇总结果见 [results.csv](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/results.csv)，逐项归并关系见 [scoring.csv](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/scoring.csv)。四份未经修改的 Reviewer 输出也一并保留：

- [单 Agent 输出](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/single-agent-output.txt)
- [Security Agent 输出](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/security-agent-output.txt)
- [Concurrency Agent 输出](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/concurrency-agent-output.txt)
- [Reliability Agent 输出](https://jimyag.com/posts/single-agent-vs-multi-agent/files/experiment/reliability-agent-output.txt)

三 Agent 组比单 Agent 多用 17 秒，覆盖率从 66.7% 提升到 91.7%。它没有把耗时放大三倍，因为三个 Reviewer 是并行运行的；但输出 token 是单 Agent 的 2.46 倍，可观察 token 下限是 2.75 倍。

### 多出来的三个问题

三 Agent 组额外找到：

1. `io.Copy` 没有大小限制，远端可以持续返回内容并耗尽磁盘。
2. 日志记录完整 URL，签名参数或 query token 可能进入日志系统。
3. 下载后没有检查 HTTP 状态码，`404` 和 `500` 页面也会作为正常数据写入导出文件。

这三条都成立，但在这个样本里属于中风险边界。单 Agent 已经找到了路径穿越、SSRF、并发 map、Request Context 失效、错误状态、ID 冲突和同名文件并发写入等主要问题。三 Agent 这次补到的是长尾问题。

### 三个 Agent 仍然会一起漏报

两组都没有指出 `http.DefaultClient` 缺少总超时。Go 文档明确写明 Client 的 `Timeout` 为零时不设置超时，而 `DefaultClient` 使用 Client 零值。[^go-http]

三个 Reviewer 都没兜住这条。如果「HTTP Client 必须设置超时」是团队的固定规则，交给静态检查、封装好的 Client 或 review checklist 更合适。

### 合并不是免费的

三名 Reviewer 一共返回 19 条 finding，合并后只剩 11 条，其中 8 条是重复项：

```text
Request Context 取消：2 个 Reviewer 重复
并发 map：2 个 Reviewer 重复
路径穿越：2 个 Reviewer 重复
SSRF：2 个 Reviewer 重复
ID 冲突：2 个 Reviewer 重复
同名文件写冲突：2 个 Reviewer 重复
Response Body 延迟关闭：2 个 Reviewer 重复
下载大小无限制：2 个 Reviewer 重复
```

不做归并，这 19 条会直接变成一组重复、严重程度还不一致的 review 评论。

## 四个判断问题

### 1. 任务能否拆成真正独立的部分

安全、并发和 I/O 可以从不同视角独立阅读同一份 diff，三者不需要等待彼此的结果，因此适合并行。

如果任务是「先理解一个状态机，再修改实现，最后根据测试失败继续修」，后一步持续依赖前一步形成的完整认识。强行拆成多个 Agent 会重复建立上下文，还会增加交接损耗。这种任务通常更适合一个 Agent 连续推进。

### 2. Agent 是否需要频繁共享完整上下文

本次实验中，每名 Reviewer 只需要一份固定 diff，不需要交换中间状态。唯一共享点是最后的 finding 列表。

如果三个 Agent 必须不断同步当前假设、未提交修改和测试状态，并行带来的时间收益很容易被通信成本抵消。Anthropic 的多 Agent Research 文章也把「需要所有 Agent 共享同一上下文」列为不适合当前多 Agent 架构的场景。[^anthropic-multi-agent]

### 3. 输出是否容易合并，并且有明确验证标准

代码审查至少可以要求每条 finding 包含代码位置和现实失败路径，再通过语言文档、测试或复现判断是否成立。即使如此，本次实验仍有 8 条重复结论需要合并。

如果任务输出主要依赖审美，没有统一结构，也没有可拒绝结果的标准，多个 Agent 往往只会生成更多互相矛盾的意见。

### 4. 任务价值是否覆盖额外成本

在这份样本中，多 Agent 用约 2.46 倍输出 token 换来 3 条额外的中风险 finding，并把漏报从 4 条降到 1 条。

是否划算取决于失败成本：

| 场景 | 判断 |
| --- | --- |
| 高风险安全审查、事故调查、关键架构评审 | 长尾问题可能抵得上额外成本 |
| 普通小 PR、低风险文档修改 | 单 Agent 找到主要问题后通常已经足够 |
| 可以用测试或静态检查覆盖的问题 | 优先增加确定性检查，不要增加 Reviewer |
| 必须在短时间内探索多个独立假设 | 多 Agent 并行更容易体现时间价值 |

## 我会怎么用

1. 先让一个 Agent 完成综合审查。
2. 根据任务风险决定是否需要第二轮。
3. 第二轮不要重复综合 prompt，而是只补安全、并发、兼容性等明确缺口。
4. 让各 Reviewer 使用相同的 finding 格式。
5. 合并时按失败原因去重，不按标题去重。
6. 能用测试、编译器、静态规则确认的问题，交给确定性工具。

Claude Code 的并行 Agent 文档也建议根据上下文、协调方式和文件冲突选择 subagent、agent team 或 worktree，并明确提醒多个会话会增加 token 使用。[^claude-parallel]

## 这次实验不能证明什么

这只是一次小样本实验，不能推出「三个 Agent 的覆盖率固定高 25 个百分点」。

它还有几个限制：

1. 样本是人为构造的 88 行 Go diff，不代表大型真实 PR。
2. 每组只运行一次，没有测量模型输出的随机波动。
3. 多 Agent 组同时改变了 Reviewer 数量和职责 prompt，测到的是「专业分工后的团队」，不是纯粹的数量效应。
4. 有效发现和误报由我根据答案表人工归并，仍然存在判断误差。
5. token 只统计可见文本，不能代表平台实际计费。
6. 墙钟时间包含调度开销，也会受当时服务负载影响。

这组数据只能用来观察成本结构，不能当作通用 benchmark。对普通代码审查，我会继续默认使用一个 Agent，确认有独立风险面需要补查时再增加专门 Reviewer。测试、静态检查和固定规则能解决的问题，仍然交给确定性工具。

## 参考资料

- [Anthropic: Building effective agents](https://www.anthropic.com/engineering/building-effective-agents)
- [Anthropic: How we built our multi-agent research system](https://www.anthropic.com/engineering/multi-agent-research-system)
- [Claude Code: Run agents in parallel](https://code.claude.com/docs/en/agents)
- [Go: package net/http](https://pkg.go.dev/net/http)
- [Go maps in action](https://go.dev/blog/maps)

[^anthropic-multi-agent]: Anthropic, [How we built our multi-agent research system](https://www.anthropic.com/engineering/multi-agent-research-system).
[^go-http]: Go, [package net/http](https://pkg.go.dev/net/http).
[^go-maps]: Go, [Go maps in action](https://go.dev/blog/maps).
[^anthropic-building-agents]: Anthropic, [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents).
[^claude-parallel]: Claude Code, [Run agents in parallel](https://code.claude.com/docs/en/agents).

