Claude Code 代理团队实战:让三个队友互相质疑着审同一段代码
代理团队是一项实验功能:一个会话当组长,开出几个各有独立上下文的队友,队友之间能直接互发消息。本站实测了一个三人的评审团队:三个队友互发了 20 条消息,推翻了组长清单里的一条错误结论。也记下了协调上的毛病:有人没等别人的发现就交了报告,行号要组长复核。整个会话估算 1.51 美元。
本站已实测,测试日期:2026-10-12。
这篇教程解决一个问题:一件事想让几个 Claude 同时从不同角度去做,而且希望它们互相看对方的结论、互相挑错,该怎么组织。
子代理能把活分出去,但每个子代理只向派它的主对话汇报,彼此不说话。worktree 能同时开几个会话,但它们互不知道对方的存在。代理团队(agent teams)补的是这一块:一个会话当组长,开出几个队友,每个队友是一个独立的 Claude Code 实例,有自己的上下文,队友之间可以直接互发消息,你也可以绕过组长直接和某个队友说话。
先说清楚它的状态:官方文档写明这是实验功能,默认关闭,在恢复会话、任务协调、关闭这几处有已知的限制。
本站用它做了一次三人的代码评审,下面是全过程。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-12 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04 |
| 模型 | 组长 Opus 5.5,三个队友 Sonnet 5.5 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例仓库 | Express 的源码,v5.3.0。这次克隆下来没有装依赖,所以测试跑不了,评审全靠读代码 |
什么时候用它,什么时候不用
和子代理的区别,官方文档的对照:
| 子代理 | 代理团队 | |
|---|---|---|
| 上下文 | 各自独立,结果交回给派它的一方 | 各自独立,完全自主 |
| 沟通 | 把结果交回去 | 队友之间直接互发消息 |
| 协调 | 主对话管所有的事 | 靠消息自己协调,另有一份共享的任务清单 |
| 适合 | 只要结果的、目标明确的活 | 需要讨论和配合的复杂工作 |
| 花费 | 较低 | 较高,每个队友都是一个完整的实例 |
文档列的几类合适的任务:调研和评审(几个人各看一面,再互相质疑)、各管一块的新模块、用几个互相竞争的假设去查一个 bug、跨前端后端和测试的改动。
不合适的:有先后顺序的任务、要改同一个文件的任务、互相依赖很多的任务。这些用一个会话或者子代理更有效。文档的原话是,代理团队有协调的开销,token 用得明显更多。
第一次用,文档建议从不写代码的任务开始:审一个 PR、调研一个库、查一个 bug。我们照这个建议选了评审。
打开这个功能
设一个环境变量。只想这一次用,启动时带上:
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 claude
想一直开着,写进 settings.json:
{
"env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
}
}
两条要知道的,来自官方文档:
- 打开之后,普通的委派也会变。 Claude 有时会自己给子代理起名字;这个功能开着的时候,有名字的子代理会作为队友启动。所以你没要求组队,也可能冒出一个团队来。不想要这种行为,把变量设回
0。 - 只在交互式会话里有效。
claude -p不会开队友,有名字的子代理在那里照旧当普通的子代理跑。
开一个团队
不需要任何配置文件,用一段话说清楚要几个队友、各管什么、怎么配合:
组一个代理团队,评审 lib/response.js 里 res.sendFile 的实现和 test/res.sendFile.js 里它的测试。
开三个队友,都用 Sonnet,名字分别叫 security、errors、coverage:
security 看安全问题(路径穿越、root 选项、点文件);
errors 看错误处理(回调什么时候被调用、连接中断、文件不存在);
coverage 看测试有没有漏掉重要的行为。
只读代码,谁都不要改文件。每个队友得出发现之后,发给另外两个队友,请他们质疑。
最后由你汇总成一份不超过八条的清单,每条写明文件和行号,并标出哪些是两个以上队友都认同的。
这段话里有几处是照着官方的建议写的:
- 给每个队友起名字。 之后要点名某个队友、或者让队友之间互相找,都靠名字。
- 每人一个不重叠的角度。 文档解释过为什么:一个评审者容易盯着一类问题看,分开之后安全、错误处理、测试覆盖才都能看透。
- 指定模型。 队友用 Sonnet,组长用 Opus,省一些。
- 把背景写进这段话。 队友会自动加载项目的 CLAUDE.md、MCP 服务器和 skill,但不继承组长的对话历史,它知道的只有组长开它时给的那段话。
发出去之后:
● 3 background agents launched (↓ to manage)
├ @security
├ @errors
└ @coverage
● 三个队友(security、errors、coverage,都用 Sonnet)已经开始工作,全部只读。每个人得出发现后
会发给另外两人,请他们质疑。三份最终报告都到齐后,我会核对行号,再汇总成不超过八条的清单。
输入框下面多出一个面板,列着组长和三个队友,各自的用时和用量在跳:
● main
◯ security Security review of sendFile 17s · ↓ 21.0k tokens
◯ errors Error-handling review of sendFile 13s · ↓ 20.7k tokens
◯ coverage Test coverage review of sendFile 7s · ↓ 19.4k tokens
确认开出来的真是团队
官方文档提醒:Claude 有时会用普通的子代理代替团队,而子代理也出现在同一个面板里,光看面板分不出来。
可以看团队的配置文件。它在 ~/.claude/teams/ 下,以“session-”加会话 ID 的前八位命名:
{
"name": "session-60aa4fe0",
"members": [
{"name": "team-lead", "agentType": "team-lead"},
{"name": "security", "agentType": "general-purpose", "model": "sonnet"},
{"name": "errors", "agentType": "general-purpose", "model": "sonnet"},
{"name": "coverage", "agentType": "general-purpose", "model": "sonnet"}
]
}
有这个文件,成员里有 team-lead,就是团队。文档说明这个文件是运行时的状态,不要手改,也不要事先写好放在那里,会被覆盖。
看队友在干什么,直接和它说话
面板的操作:
| 按键 | 作用 |
|---|---|
↓ |
把焦点从输入框移到面板 |
↑ ↓ |
在面板里选 |
Enter |
打开选中的那个的会话 |
Esc |
取消选中。正在看某个队友的会话时,Esc 会打断它当前这一轮 |
选中 security 回车,屏幕换成了它的会话:最上面是一条 Message from @team-lead,下面是它在读代码、跑 grep,输入框的提示变成了 Message @security…。
在这里打字,消息直接发给这个队友,不经过组长。我们补了一个要求:
❯ 补充一个要求:也确认一下 dotfiles 选项的默认值是什么,写进你的发现里。
它当时正忙,面板上这一行显示 1 queued,表示消息排着队。后来它的报告里有了这一条:
● Teammate @security finished
默认值:`dotfiles` 的默认值是 `'ignore'`,也就是对 dotfile 返回 404。依据是 `send/index.js:114-116`,
选项为 `undefined` 时取 `'ignore'`。
要回到组长的会话,按 ↓ 把焦点移到面板,选中 main 回车。不要按 Esc 退出来,那会打断队友正在做的事。
官方文档还有一种显示方式:每个队友占一个独立的窗格,要在 tmux 或 iTerm2 里用,启动时加 --teammate-mode auto。我们用的是默认的方式,没有试窗格。
这次评审的经过
整个过程 7 分钟出头。组长的会话里陆续出现队友交回的报告,中间有几件事值得记下来。
队友之间确实在互相说话
我们从会话记录里数了发消息的次数(只数了调用,没看内容):
| 谁发的 | 发给谁 | 条数 |
|---|---|---|
| security | errors、coverage | 各 4 条 |
| errors | security、coverage | 各 3 条 |
| coverage | security、errors | 各 3 条 |
| coverage | 组长 | 1 条 |
| 组长 | 三个队友 | 共 4 条 |
队友之间一共 20 条。这是子代理做不到的。
一条错误的结论被推翻了
这是这次最能说明团队价值的一幕。组长先给过一版清单,第一条是:encodeURI 不编码 %,所以文件名里带 % 的文件会返回 400。
coverage 没有接受这个说法,自己用 node 跑了一下。security 和 errors 随后复核,security 撤回了它原来的那条发现。组长自己也重跑了一遍:
● Bash(node -e "console.log(encodeURI('/a/100%.txt'), encodeURI('/a/%2e%2e%2f'))")
⎿ /a/100%25.txt /a/%252e%252e%252f
● 我之前给你的清单第 1 条是错的,需要撤回。那条说"encodeURI 不编码 %,所以文件名带 % 的文件会
返回 400"。coverage 用 node 实测推翻了它,security 和 errors 随后确认,我自己也跑了一遍:
encodeURI('/a/100%.txt') 得到的是 /a/100%25.txt。……这一条只剩"给特殊字符文件名补回归测试"
的价值,优先级低,不再列入。
如果只有一个评审者,这条看上去很有道理的结论多半就留在清单里了。官方文档讲“互相竞争的假设”那个用例时说的就是这件事:一个代理找到一个说得通的解释就容易停下,几个独立的调查者互相找茬,留下来的结论才更可能是对的。
最后的清单
组长汇总的八条,每条有位置,并标了谁认同(节选):
│ 1 │ lib/response.js:404 │ opts.etag = this.app.enabled('etag') 直接改写调用者传入的
│ │ │ options 对象,还会覆盖用户自己设的 etag。没有测试覆盖。 │ 三人 │
│ 3 │ lib/response.js:413 │ 没传回调时,ECONNABORTED 和 syscall === 'write' 的错误被
│ │ │ 直接吞掉,不交给 next。……但没有测试锁定这个行为。 │ 三人 │
│ 4 │ test/res.sendFile.js:845-899 │ root 选项的测试只有 ../ 两种情况。缺少:%2e%2e%2f 编码穿越、
│ │ │ %00 空字节、root 下的点文件、符号链接逃出 root…… │ 三人 │
│ 8 │ lib/response.js:379 │ 函数入口取的是 req.next。在 router 之外调用、又没传回调时,
│ │ │ next 是 undefined,一出错就会抛 TypeError。严重性低。 │ errors、security │
八条里六条三人都认同,两条两人认同。它还单独列了“讨论中确认没问题”的结论和“被降级、没有列入”的几条,并说明没有发现可以利用的安全漏洞。
这份清单我们没有逐条去验证结论对不对。行号抽查了四处(lib/response.js 的 379、404、410、413 行),都和清单里写的代码对得上。
协调上的毛病
团队不是开出来就自己转得很顺。这次看到的几处:
有人没等别人的发现就交了报告。 errors 第一个交,交的时候还没收到另外两人的质疑。security 交的时候更直接:
● Teammate @security finished
没有收到 errors 和 coverage 的回应,所以下面每条发现的队友意见都是"未回复"。
● security 交报告时,errors 和 coverage 的发现还没送到它那里。我让三人补上这一轮互相质疑。
组长发现了,要求补一轮。三个队友都是各干各的,谁先做完谁先交,“互相质疑”这一步要靠组长盯着才真的发生。
队友给的行号不准。 组长核对之后自己说的:
● errors 这次给的两处行号更正也不对,正确位置是 !path 在 lib/response.js:382、opts.etag 在 :404。
汇总时我会按实际行号写。
我们在提示词里要求了“每条写明文件和行号”,组长因此去复核了。没有这一步,错的行号会直接进最终的清单。
报告太长被截断。 有一次队友的报告超过了长度上限:
● Teammate @errors finished
[result truncated — this drain's results exceeded 16000 characters; ask the agent for it via SendMessage]
同一个队友反复“finished”。 每个队友都交了好几次“最终报告”,每收到一轮质疑就更新一次。组长要分辨哪一版才算数。
有队友跑到项目外面去了。 这个仓库没装依赖,security 想看 send 这个包的源码,就到服务器上另一个目录的 node_modules 里去读了。auto 模式下没有人拦它。这在我们的示例环境里无害,但说明队友和组长一样,活动范围取决于权限设置,不取决于你以为的“项目目录”。
出过一次接口报错。 security 中途显示过 API error · Retrying in 8s · attempt 2/10,重试之后恢复了,没有影响结果。原因我们没有查。
官方文档列的已知问题里,和这些对得上的有:任务状态可能滞后、队友遇到错误可能停下而不是恢复、组长有时会不等队友就自己动手或者过早认为事情做完了。文档给的应对都是一句话的事:告诉组长“等队友做完再继续”或者“继续”。
关掉队友
点名让它关:
❯ 请 coverage 队友关闭。
│ coverage has shut down.
组长发的是一个关闭请求,队友可以同意,也可以拒绝并说明理由。
整个会话退出之后,我们看了一下留下了什么:~/.claude/teams/ 下这个团队的目录被删掉了,~/.claude/tasks/ 下同名的目录还在。这和文档的说法一致:团队配置随会话结束清理,任务清单留在本地,按会话记录同样的保留期限清理。
花了多少
Total cost: $1.51
Total duration (API): 12m 26s
Total duration (wall): 7m 9s
Usage by model:
claude-opus-5-5: 50 input, 9.9k output, 875.3k cache read, 73.0k cache write ($0.74)
claude-sonnet-5-5: 128 input, 38.3k output, 1.5m cache read, 96.7k cache write ($0.77)
两个数放在一起看:模型一共工作了 12 分 26 秒,实际只过了 7 分 9 秒,差出来的就是并行省下的时间。三个 Sonnet 队友合计 0.77 美元,组长一个人 0.74 美元,组长花在协调、复核和汇总上的并不比队友少。
这是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。作为对照,子代理那篇里一个评审员审一个测试文件,整个会话是 0.36 到 0.63 美元。团队贵在每个队友都是一个完整的实例,而且消息来回的每一轮都要再读一遍上下文。
官方文档关于规模的建议:多数情况从 3 到 5 个队友开始;token 的花费随人数线性增长;三个目标明确的队友,往往比五个各自为战的强。
几条实用的建议
来自官方文档,加上这次的体会:
- 要它们互相质疑,就在提示词里写明,并且要求组长在所有人都回应之后再汇总。 这次没写后半句,结果多跑了一轮。
- 要求结论带文件和行号,组长才有东西可以复核。
- 两个队友不要改同一个文件。 文档说那样会互相覆盖。这次是只读的任务,没有这个问题。要动手改的任务,按文件把活分开。
- 不要放着不管。 隔一会儿进某个队友的会话看一眼,方向不对就直接跟它说。
- 权限的确认会冒到组长这里来。 队友沿用组长的权限模式。用 Manual 模式的话,三个队友的确认框都在组长的会话里弹,很烦。文档建议先在权限设置里把常用的操作预先批准。
- 可以用子代理的定义当队友的角色。 在
.claude/agents/里定义过的角色(比如只读的评审员),开队友时说“用某某类型开一个队友”就能复用,它的工具限制和模型都会带过去。这个我们没有试。
已知的限制
官方文档列的,选几条最容易碰到的:
- 恢复会话不会把队友带回来。
/resume和/rewind之后,组长可能还在给已经不存在的队友发消息。告诉它重新开。 - 一个会话只有一个团队,不能跨会话共用。
- 队友不能再开队友,只有组长能管理团队。
- 组长是固定的,不能把某个队友提成组长。
- 权限模式在开队友时不能单独指定,开出来之后可以逐个改。
这次实测没有覆盖到的
- 只做了只读的评审。 几个队友同时改代码的情况没有试,那里的文件冲突和任务认领才是协调最难的部分。
- 共享的任务清单没有用上。 任务目录在整个过程中是空的,这次的协调全靠消息。
- 独立窗格的显示方式、用子代理定义开队友、让队友先出计划、
TeammateIdle这类 hook 都没有试。 - 清单的八条结论没有逐条验证。
- 仓库没有装依赖,所以没有一个队友能跑测试,结论都来自读代码。这是我们准备环境时的疏忽,不是功能的限制。
- 只跑了一次。 队友什么时候交报告、互相发多少条消息,每次都会不一样。
- 通过中转站跑多模型的团队只验证了这一次。 组长和队友用不同的模型,除了一次自动恢复的接口报错,没有遇到问题。
下一步
- 只要结果、不需要互相讨论:子代理实战。
- 几件互不相干的事同时做:用 worktree 同时开两个会话。
- 系列的下一篇:把 Claude Code 接进脚本和 CI。