内容
引言
Claude Code(v2.1.47)提供了一项名为 Agent Teams 的实验性功能:多个 Claude Code 会话通过由负责人和队友组成的拓扑结构,协同处理共享工作。我一直在开发 Hive,这是一个多智能体编程编排器,目标相似,架构却截然不同,因此我想了解 Anthropic 的方案在底层是如何运作的。
本文记录了我通过以下方式得到的发现:
- 阅读官方文档
- 检查先前团队会话在磁盘上留下的实际文件
- 让 Claude 分析 Claude Code 二进制文件(v2.1.47),了解实现细节(哈!)
目录
1. 架构概览
一个智能体团队由四个部分组成:
| 组件 | 作用 |
|---|---|
| 团队负责人 | 创建团队并启动队友的主 Claude Code 会话 |
| 队友 | 独立的 Claude Code 实例,各自拥有独立的上下文窗口 |
| 任务列表 | 以独立 JSON 文件形式存储在磁盘上的共享工作项 |
| 邮箱 | 每个智能体各自用于接收消息的收件箱文件 |
整个协调层都基于文件。位于 ~/.claude/ 的文件系统是唯一的协调基础:
~/.claude/
├── teams/{team-name}/
│ ├── config.json # team membership registry
│ └── inboxes/{agent-name}.json # per-agent mailbox
└── tasks/{team-name}/
├── .lock # flock() for concurrent task claiming
├── .highwatermark # auto-increment counter
├── 1.json # individual task files
├── 2.json
└── ...
这是一种从根本上去中心化的设计。负责人只是另一个拥有额外工具(TeamCreate、TeamDelete、SendMessage)的 Claude 会话。没有后台进程。协调通过共享文件访问自然实现。
在一个正在进行的会话中,如果你让 Claude 启动一个团队来执行某类任务,然后在另一个窗口运行以下命令,就能实时观察文件系统的更新。
watch -n 0.5 'tree ~/.claude/teams/ 2>/dev/null; echo "---"; tree ~/.claude/tasks/ 2>/dev/null'
例如,使用以下提示词:
can you spawn an agent team to examine this codebase?
- have one look for bugs
- have one look for complexity
- have one look for good things to call out and play devil's advocate against the other two agents
我观察到了如下结果:
teams
└── code-review
├── config.json
└── inboxes
├── bug-hunter.json
├── complexity-analyst.json
├── devils-advocate.json
└── team-lead.json
团队配置
位于 ~/.claude/teams/{team-name}/config.json 的团队配置包含一个 members 数组,队友通过读取它来发现彼此:
{
"members": [
{ "name": "team-lead", "agentId": "abc-123", "agentType": "leader" },
{
"name": "researcher",
"agentId": "def-456",
"agentType": "general-purpose"
}
]
}
名称是主要的寻址方式(虽然存在 UUID,但并不用于路由)。所有消息传递和任务分配都使用 name 字段。
2. 共享任务列表
文件格式
每个任务都以独立 JSON 文件的形式存储在 ~/.claude/tasks/{team-name}/ 中。下面是先前一次会话中的真实示例:
{
"id": "1",
"subject": "Hunt for bugs across the codebase",
"description": "...",
"activeForm": "Hunting for bugs",
"owner": "bug-hunter",
"status": "completed",
"blocks": [],
"blockedBy": []
}
任务结构:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 数字 ID,通过 .highwatermark 自动递增 |
subject |
string | 祈使句形式的标题(例如“运行测试”) |
description |
string | 详细需求和验收标准 |
activeForm |
string | 现在进行时形式,用于加载指示器显示(“正在运行测试”) |
status |
string | pending → in_progress → completed(或 deleted) |
blocks |
string[] | 被此任务阻塞的任务 ID |
blockedBy |
string[] | 此任务开始前必须完成的任务 ID |
并发控制
两个特殊文件负责协调:
.lock:一个 0 字节文件,用于文件系统级互斥(flock())。在我机器上观察到的全部 42 个任务目录中都存在。.highwatermark:包含一个整数(例如"3"、"13"),表示下一个可用的自增任务 ID。
任务认领
任务认领使用文件锁来避免竞态条件。队友倾向于优先选择 ID 最小的任务。如果某个任务的 blockedBy 数组非空,就必须等所有阻塞它的任务都进入终态后,才能认领该任务。
观察结果:大多数任务目录为空
在我机器上的 42 个任务目录中,只有 5 个包含实际的任务 JSON 文件。其余 37 个只有 .lock 和 .highwatermark。这可能意味着任务完成后会被清理,也可能是这些会话中的 Claude 使用了内部任务列表(自任务列表功能推出以来就已提供),但没有将任务拆分为子任务文件。
3. 智能体间通信
邮箱模式
每个智能体都有一个位于 ~/.claude/teams/{team-name}/inboxes/{agent-name}.json 的 JSON 数组文件。下面是先前一次会话中的真实收件箱,当时团队负责人向一个 controlplane-agent 分派了工作:
[
{
"from": "team-lead",
"text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"Phase 2: Control-plane - remove participants/presence\",\"description\":\"Remove multiplayer code from the control-plane package...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"2026-02-18T02:37:16.890Z\"}",
"timestamp": "2026-02-18T02:37:16.890Z",
"read": false
}
]
注意这种 JSON 内嵌 JSON 的编码方式:text 字段是一个 JSON 字符串,其中包含序列化后的消息对象。外层信封包含 from、text、timestamp 和 read 字段。
消息类型
type 字段位于 text 载荷中,支持以下类型:
| 类型 | 方向 | 用途 |
|---|---|---|
task_assignment |
负责人 → 队友 | 分配任务并提供完整细节 |
message |
任意成员 → 任意成员 | 向单个接收者发送私信 |
broadcast |
负责人 → 全体队友 | 向每位队友发送相同消息 |
shutdown_request |
负责人 → 队友 | 请求正常关闭 |
shutdown_response |
队友 → 负责人 | 同意或拒绝关闭 |
plan_approval_request |
队友 → 负责人 | 提交计划以供审核 |
plan_approval_response |
负责人 → 队友 | 批准或拒绝,并提供反馈 |
idle_notification |
队友 → 负责人 | 队友的回合结束时自动发送 |
投递机制
写入路径:发送者向接收者的收件箱 JSON 数组文件追加一条新记录。
读取路径:接收者轮询自己的收件箱文件。新消息会作为合成的对话回合注入(看起来就像用户发送的消息)。
广播:确实就是将同一条消息写入每位队友的收件箱文件。Token 成本随团队规模线性增长。
通信就是文件追加加上文件读取。发送与接收之间的延迟取决于接收者的轮询间隔。
队友间私信的可见性
当一位队友向另一位队友发送私信时,发给负责人的空闲通知中会包含一段简短摘要。这样,负责人就能了解队友之间的协作情况,而无需接收完整的消息内容。
4. 智能体的创建与生命周期
队友的创建方式
每位队友都是一个独立的 claude CLI 进程。负责人通过 Task 工具,使用 team_name 和 name 参数来启动它们。启动的进程会设置以下环境变量:
CLAUDE_CODE_TEAM_NAME:在启动的队友进程中自动设置CLAUDE_CODE_PLAN_MODE_REQUIRED:如果需要审批计划,则设置为true
上下文初始化
队友会像任何新会话一样,加载相同的项目上下文:
- 工作目录中的
CLAUDE.md文件 - MCP 服务器
- 技能
- 负责人提供的启动提示词
负责人的对话历史不会传递给队友。 每位队友都从头开始,仅以启动提示词作为上下文。
内部实现
根据对 Claude Code v2.1.47 的二进制分析,队友上下文通过 AsyncLocalStorage 管理,包含以下字段:
agentId、agentName、teamNameparentSessionId、colorplanModeRequired
关键内部函数:
isTeammate()/isTeamLead():角色检测waitForTeammatesToBecomeIdle():供负责人使用的同步原语getTeammateContext()/setDynamicTeamContext():运行时上下文管理
空闲检测
每个 LLM 回合结束后,队友都会自动进入空闲状态,并向负责人发送一条 idle_notification。这是正常的等待状态,并不表示出错或状态失效。向空闲队友发送消息会唤醒它(下一个轮询周期会读取到收件箱中的消息)。
关闭协议
- 负责人向队友发送
shutdown_request - 队友可以同意(正常退出)或拒绝(说明原因并继续工作)
- 通过
TeamDelete清理团队,会删除~/.claude/teams/{team-name}/和~/.claude/tasks/{team-name}/ - 如果仍有任何队友处于活动状态,清理就会失败;必须先关闭它们
权限继承
队友在启动时继承负责人的权限模式。如果负责人使用 --dangerously-skip-permissions 运行,所有队友也会如此。启动后可以修改各个队友的模式,但无法在启动时为每位队友单独配置。
5. 质量门禁与钩子
Agent Teams 与 Claude Code 的钩子系统集成,以强制执行质量要求:
TeammateIdle 钩子
在队友即将进入空闲状态时触发。退出码 2 会将 stderr 作为反馈发送,并阻止其进入空闲状态,让队友继续工作。
{
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "my-project"
}
TaskCompleted 钩子
在任务被标记为完成时触发。退出码 2 会阻止完成操作,并将 stderr 作为反馈返回。
{
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "my-project"
}
它会在两种情况下触发:(1)任何智能体通过 TaskUpdate 显式将任务标记为完成时,或(2)智能体团队中的队友结束回合时,仍有进行中的任务。
钩子处理器类型
| 类型 | 说明 |
|---|---|
command |
Shell 脚本。通过 stdin 接收 JSON,通过退出码表达决定。 |
prompt |
单回合 LLM 评估。返回 {ok, reason}。 |
agent |
配有读取工具的多回合子智能体。最多 50 个回合。 |
6. Token 成本
当队友在计划模式下运行时,智能体团队消耗的 token 约为标准会话的 7 倍。每位队友都是独立的 Claude 实例,各自维护完整的上下文窗口。
基准参考
- Claude Code 平均用量:约 6 美元/开发者/天
- 智能体团队:在基准用量之上,大致随团队规模成比例增加
7. 架构总结
| 维度 | Claude Code Agent Teams |
|---|---|
| 协调基础 | 平面文件(~/.claude/tasks/、~/.claude/teams/) |
| 任务格式 | 每个任务一个 JSON 文件 + 用于认领的 .lock |
| 消息传递 | JSON 收件箱文件(追加 + 轮询) |
| 智能体生命周期 | 自行管理的 CLI 进程 |
| 工作隔离 | 共享工作目录 |
| 合并策略 | 无(智能体直接编辑文件) |
| 重试/升级处理 | 手动(由负责人决定,或由用户介入) |
| 拓扑结构 | 负责人 + 扁平的队友结构,支持点对点消息传递 |
| 调度 | 自主认领(队友领取下一个任务) |
| 状态持久性 | 仅依靠文件;不支持在进程内恢复队友 |
| 质量门禁 | Shell 钩子(TeammateIdle、TaskCompleted) |
| Token 跟踪 | 仅按会话跟踪,不跨智能体汇总 |
| 停滞检测 | 手动(由用户发现队友已停止工作) |
| 并发控制 | 隐式(团队规模 = 队友数量) |
| 依赖模型 | 任务文件中的 blocks/blockedBy |
资料来源
官方文档
- Claude Code 会话团队:架构
- 交互模式 — 任务列表
- 智能体团队 — 分配和认领任务
- 智能体团队 — 上下文与通信
- 智能体团队 — 关闭队友
- 智能体团队 — 清理团队
- 智能体团队 — 权限
- 智能体团队 — 限制
- 智能体团队 — 最佳实践
- 钩子
- 成本 — 智能体团队的 token 成本
- 设置
- 子智能体
磁盘上的文件(Claude Code v2.1.47)
在 /Users/tau/.claude/ 观察到:
- 包含
config.json和inboxes/{agent-name}.json文件的团队目录 - 包含
.lock、.highwatermark和独立任务 JSON 文件的任务目录 - 从
team-lead发给cp-agent的任务分配消息示例,时间戳为2026-02-18T02:37:16.890Z
二进制分析
Claude Code v2.1.47 二进制文件。通过字符串分析识别出的内部函数:getTeamName、getAgentName、getAgentId、isTeammate、isTeamLead、waitForTeammatesToBecomeIdle、getTeammateContext、setDynamicTeamContext、createTeammateContext。AsyncLocalStorage 上下文字段:agentId、agentName、teamName、parentSessionId、color、planModeRequired。