內容
前言
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. 品質關卡與 Hook
Agent Teams 整合了 Claude Code 的 Hook 系統,用來把關品質:
TeammateIdle Hook
在隊友即將進入閒置狀態時觸發。結束代碼 2 會將 stderr 作為回饋傳送,並阻止隊友進入閒置,讓它繼續工作。
{
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "my-project"
}
TaskCompleted Hook
在任務即將被標記為完成時觸發。結束代碼 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) 代理程式團隊中的隊友結束回合時,仍有進行中的任務。
Hook 處理器類型
| 類型 | 說明 |
|---|---|
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 Hook(TeammateIdle、TaskCompleted) |
| Token 追蹤 | 僅限個別工作階段,不跨代理程式彙總 |
| 停滯偵測 | 手動(使用者察覺隊友已停止) |
| 並行控制 | 隱含決定(團隊規模 = 隊友數量) |
| 相依性模型 | 任務檔案上的 blocks/blockedBy |
資料來源
官方文件
- Claude Code 工作階段團隊:架構
- 互動模式 — 任務清單
- 代理程式團隊 — 指派與認領任務
- 代理程式團隊 — 上下文與通訊
- 代理程式團隊 — 關閉隊友
- 代理程式團隊 — 清理團隊
- 代理程式團隊 — 權限
- 代理程式團隊 — 限制
- 代理程式團隊 — 最佳實務
- Hook
- 成本 — 代理程式團隊的 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。