内容
はじめに
Claude Code(v2.1.47)には、Agent Teamsという実験的な機能が搭載されています。複数のClaude Codeセッションが、リーダーとチームメイトという構成で連携し、共通の作業に取り組みます。私は、似た目的を持ちながらアーキテクチャが大きく異なるマルチエージェント型のコーディングオーケストレーターHiveを開発してきたので、Anthropicのアプローチが内部でどのように動いているのか知りたいと思いました。
この記事では、次の方法で調べてわかったことを記録します。
- 公式ドキュメントを読む
- 過去のチームセッションがディスク上に残したアーティファクトを調べる
- ClaudeにClaude Codeのバイナリ(v2.1.47)を解析させ、実装の詳細を調べる(笑!)
目次
- 1. アーキテクチャの概要
- 2. 共有タスクリスト
- 3. エージェント間の通信
- 4. エージェントの起動とライフサイクル
- 5. 品質ゲートとフック
- 6. トークンのコスト
- 7. アーキテクチャのまとめ
- 出典
1. アーキテクチャの概要
エージェントチームは、4つの構成要素から成ります。
| 構成要素 | 役割 |
|---|---|
| チームリーダー | チームを作成し、チームメイトを起動するメインの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. 共有タスクリスト
ファイル形式
各タスクは、~/.claude/tasks/{team-name}/内に個別のJSONファイルとして保存されます。過去のセッションの実例を示します。
{
"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 |
並行性制御
2つの特別なファイルが連携を支えています。
.lock:ファイルシステムレベルの排他制御(flock())に使われる0バイトのファイル。私のマシンで観察した42個のタスクディレクトリすべてに存在していました。.highwatermark:単一の整数(例:"3"、"13")を含みます。自動インクリメントで次に使用できるタスクIDです。
タスクの取得
タスクの取得にはファイルロックを使い、競合状態を防ぎます。チームメイトは、IDの小さいタスクから優先的に取得します。blockedBy配列が空でないタスクは、開始を妨げているタスクがすべて終了状態になるまで取得できません。
観察結果:タスクディレクトリの大半は空
私のマシンにある42個のタスクディレクトリのうち、実際にタスクのJSONファイルが入っていたのは5個だけでした。残りの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-in-JSONというエンコーディングに注目してください。textフィールドは、シリアライズされたメッセージオブジェクトを含むJSON文字列です。外側のエンベロープには、from、text、timestamp、readというフィールドがあります。
メッセージの種類
typeフィールドは、textペイロード内にあり、次の種類をサポートしています。
| 種類 | 方向 | 用途 |
|---|---|---|
task_assignment |
リーダー → チームメイト | 詳細をすべて含めてタスクを割り当てる |
message |
任意 → 任意 | 1人の受信者にダイレクトメッセージを送る |
broadcast |
リーダー → 全員 | すべてのチームメイトに同じメッセージを送る |
shutdown_request |
リーダー → チームメイト | 正常終了を要求する |
shutdown_response |
チームメイト → リーダー | シャットダウンを承認または拒否する |
plan_approval_request |
チームメイト → リーダー | レビューのためにプランを提出する |
plan_approval_response |
リーダー → チームメイト | フィードバックを添えて承認または拒否する |
idle_notification |
チームメイト → リーダー | チームメイトのターン終了時に自動送信される |
配信の仕組み
書き込み経路:送信者は、受信者の受信トレイであるJSON配列ファイルに新しいエントリを追記します。
読み取り経路:受信者は、自分の受信トレイファイルをポーリングします。新しいメッセージは、合成された会話ターンとして挿入されます(ユーザーが送信したかのように表示されます)。
ブロードキャスト:文字どおり、すべてのチームメイトの受信トレイファイルに同じメッセージを書き込みます。トークンのコストはチームの規模に比例して増えます。
通信は、ファイルへの追記と読み取りだけで成り立っています。送信から受信までの遅延は、受信者のポーリング間隔に依存します。
チームメイト間のDMの可視性
チームメイトが別のチームメイトにDMを送ると、リーダーへのアイドル通知に短い要約が含まれます。これにより、リーダーはメッセージの全文を受け取らずに、チームメイト間の協力の様子を把握できます。
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}/が削除される- 稼働中のチームメイトが1つでも残っているとクリーンアップは失敗するため、先にシャットダウンする必要がある
権限の継承
チームメイトは起動時に、リーダーの権限モードを継承します。リーダーが--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) エージェントチームのチームメイトが進行中のタスクを抱えたままターンを終えたとき、という2つの状況で実行されます。
フックハンドラーの種類
| 種類 | 説明 |
|---|---|
command |
シェルスクリプト。stdinでJSONを受け取り、終了コードで判断を伝える。 |
prompt |
単一ターンのLLM評価。{ok, reason}を返す。 |
agent |
読み取りツールを使える複数ターンのサブエージェント。最大50ターン。 |
6. トークンのコスト
チームメイトがプランモードで動作する場合、エージェントチームは通常のセッションと比べて約7倍のトークンを使用します。各チームメイトは個別のClaudeインスタンスとして、それぞれ完全なコンテキストウィンドウを維持します。
基準となる目安
- Claude Codeの平均利用コスト:約6ドル/開発者/日
- エージェントチーム:基準となるコストに加え、おおむねチームの規模に比例するコスト
7. アーキテクチャのまとめ
| 項目 | Claude Code Agent Teams |
|---|---|
| 連携の基盤 | フラットファイル(~/.claude/tasks/、~/.claude/teams/) |
| タスクの形式 | タスクごとに1つのJSONファイル + 取得用の.lock |
| メッセージ送受信 | JSON形式の受信トレイファイル(追記 + ポーリング) |
| エージェントのライフサイクル | 自らライフサイクルを管理するCLIプロセス |
| 作業の分離 | 共有の作業ディレクトリ |
| マージ戦略 | なし(エージェントがファイルを直接編集する) |
| 再試行/エスカレーション | 手動(リーダーが判断するか、ユーザーが介入する) |
| トポロジー | リーダー + フラットなピア、ピア間のメッセージ送受信 |
| スケジューリング | 自主的な取得(チームメイトが次のタスクを取得する) |
| 状態の永続性 | ファイルのみ。プロセス内でのチームメイトの再開機能はない |
| 品質ゲート | シェルフック(TeammateIdle、TaskCompleted) |
| トークンの追跡 | セッション単位のみ。エージェント間の集計はない |
| 停止の検出 | 手動(ユーザーがチームメイトの停止に気づく) |
| 並行性制御 | 暗黙的(チームの規模 = チームメイトの数) |
| 依存関係のモデル | タスクファイルのblocks/blockedBy |
出典
公式ドキュメント
- Claude Codeセッションのチーム:アーキテクチャ
- 対話モード — タスクリスト
- エージェントチーム — タスクの割り当てと取得
- エージェントチーム — コンテキストと通信
- エージェントチーム — チームメイトのシャットダウン
- エージェントチーム — チームのクリーンアップ
- エージェントチーム — 権限
- エージェントチーム — 制限事項
- エージェントチーム — ベストプラクティス
- フック
- コスト — エージェントチームのトークンコスト
- 設定
- サブエージェント
ディスク上のアーティファクト(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。