Files
analysis_claude_code/s15_agent_teams/README.ja.md
2026-07-31 03:15:58 +08:00

12 KiB
Raw Blame History

s15: Agent Teams — チームランタイムと協調プロトコル

English · 中文 · 日本語

s01 → ... → s13 → s14 → s15s16 → s17 → s18 → s19 → s20 → s21

「1 つの Agent だけでは扱いきれないなら、チームメイトで分担する。」 — 永続チームメイト、メッセージ配信、協調プロトコル。

Harness レイヤー:チーム — 複数 Agent を並行動作させながら制御を保つ。


問題

Agent にバックエンド全体のリファクタリングを頼む場合、設定読み込み、認証、テストを同時に扱うことになる。1 つの Agent が順番に処理することもできるが、時間がかかり、初期の詳細は徐々にコンテキストから抜けていく。

このような仕事は並列化に向いている。しかし、通常のユーザーはチーム構成ではなく目的だけを伝える:

このサンプルバックエンドをリファクタリングしてください。
設定読み込み、認証ロジック、テストを整理し、
既存インターフェースを保ったままテストを通してください。

そのため Harness は、単に Agent を増やすだけでなく、次の 4 点を解決する必要がある:

  1. 並列化が有効かを誰が判断し、追加 Agent の起動を誰が確認するか。
  2. チームメイトが複数の依頼にまたがって、どう身元とコンテキストを保つか。
  3. モデルに受信箱を繰り返し確認させず、結果をどう Lead へ戻すか。
  4. 終了と計画承認を、どう追跡可能で強制可能なプロトコルにするか。

解決策

Agent Teams Overview

s15 は単一 Agent の Harness の外側に、Lead が管理するチームランタイムを追加する:

  • Lead はユーザーとの会話を維持し、分担案を提示して確認を待つ。
  • チームメイト は独立した Agent Loop をバックグラウンドスレッドで実行し、作業後は IDLE になる。
  • MessageBus はファイル受信箱を通して、通常メッセージ、結果、制御イベントを運ぶ。
  • ランタイム配信 は Lead の受信箱を消費し、チームイベントを次のターンへ注入する。
  • 協調プロトコルtyperequest_id、状態遷移で終了と計画承認を扱う。
  • 計画ゲート は、必要な計画が承認されるまで bashwrite_file を遮断する。

モデルはタスクを理解して分担を決める。コードは配信、ライフサイクル、プロトコル制約を担う。


仕組み

1. Lead はチーム案を示し、確認を待つ

チームメイトの起動は、コスト、並行度、ワークスペースを書き換える主体を変える。この境界を通常のツール呼び出しの中に隠してはいけない。Lead の system prompt は次のように定める:

"When parallel work would help, first propose a small team with clear "
"responsibilities and wait for the user's confirmation. Do not call "
"spawn_teammate before the user confirms."

最初の依頼に対して、Lead はまず分担案だけを返す:

次の 3 方向で並行処理することを提案します。
- config設定読み込みの整理
- auth認証ロジックのリファクタリング
- tests回帰テストの追加

確認後にチームメイトを起動します。

ユーザーが「始めてください」と返した後で、Lead は spawn_teammate を呼ぶ。ユーザーが目的を示し、Lead がチームを設計し、ユーザーが実行境界を確認する。

2. 各チームメイトは独立したループを持つ

s06 の Subagent は 1 回限りの呼び出しだが、チームメイトは永続する実行単位である:

s06 Subagent s15 チームメイト
ライフサイクル 1 回の呼び出し後に終了 終了要求まで WORK → IDLE → WORK
コンテキスト 1 つのタスクだけ 複数の依頼をまたいで保持
通信 1 回だけ結果を返す メッセージを受け取り、イベントを送る
協調 一方向の委任 Lead との双方向協調

spawn_teammate_thread() はチームメイトごとに system prompt、messages、ツールを作り、daemon thread でループを実行する。Lead はチームメイトの終了を待たずに、別の依頼や結果を調整できる。

3. MessageBus は通信をモデルのコンテキスト外に置く

Lead とチームメイトが同じ messages 配列を共有すると、あるチームメイトのツール結果が別のチームメイトの推論へ混ざる。MessageBus は各 Agent に .mailboxes/<name>.jsonl 受信箱を与える:

class MessageBus:
    def send(self, from_agent, to_agent, content,
             msg_type="message", metadata=None):
        msg = {
            "from": from_agent,
            "to": to_agent,
            "content": content,
            "type": msg_type,
            "metadata": metadata or {},
        }
        with self._changed:
            append_jsonl(self._path(to_agent), msg)
            self._changed.notify_all()

    def wait_for_messages(self, agent):
        with self._changed:
            while not self.peek(agent):
                self._changed.wait()
            return self._read_unlocked(agent)

ロックは複数スレッドによる受信箱ファイルの破損を防ぐ。Condition により、IDLE のチームメイトはポーリングせずイベント到着まで待機できる。

4. 受信イベントはランタイムが自動配信する

read_inbox() はメッセージを読み、受信箱ファイルを削除する。そのため Lead の消費入口は consume_lead_inbox() だけにする:

def consume_lead_inbox():
    messages = BUS.read_inbox("lead")
    for message in messages:
        if message["type"].endswith("_response"):
            match_response(...)
    return messages

メインループのイベントスレッドは、新しいメッセージが届くと Lead を起こす:

MessageBus → consume_lead_inbox
           → プロトコル状態を更新
           → [Team events] を history へ注入
           → Lead の次ターンを開始

check_inbox はモデルのツールではない。メッセージの到着はランタイムの責務であり、モデルはコンテキストへ配信済みのイベントだけを処理する。

5. 結果と IDLE は別のイベント

チームメイトが 1 件の作業を終えると、ランタイムは次の順序で 2 つのイベントを送る:

result:            "認証をリファクタリングし、関連テストが通りました。"
idle_notification: "Waiting for more work."

result は「今回の作業で何が得られたか」、idle_notification は「新しい仕事を受けられるか」を表す。1 つの曖昧な「done」では両者を区別できない。

IDLE になったチームメイトは終了しない。通常メッセージで WORK に戻り、shutdown_request で終了ハンドシェイクを始める。

6. 制御メッセージには型と request_id を使う

通常の協調は自由文でよいが、終了と承認を意図の推測に任せてはいけない。制御イベントは構造化する:

Team Protocols

@dataclass
class ProtocolState:
    request_id: str
    type: str
    sender: str
    target: str
    status: str
    payload: str


pending_requests: dict[str, ProtocolState] = {}

終了プロトコルは次の経路を通る:

Lead が pending の shutdown request を作る
  → shutdown_request(request_id) をチームメイトへ送る
  → チームメイトが現在の手順を終える
  → shutdown_response(request_id) を Lead へ返す
  → request_id で元の要求を特定する
  → pending が approved になり、チームメイトループが終了する

ID は要求と応答を対応付け、型は誤った応答による状態変更を防ぎ、状態は重複応答の再適用を防ぐ。

7. 計画承認は実行も制約する

計画プロトコルは逆方向に流れる:

Lead → plan_request
チームメイト → plan_approval_request(request_id, plan)
Lead → plan_approval_response(request_id, approve, feedback)

「承認まで待つ」と伝えるだけでは確実なゲートにならない。そこでツール dispatch が計画状態を検査する:

def _run_teammate_tool(name, block, handlers):
    gate = plan_gates.get(name, "not_required")
    if block.name in {"bash", "write_file"} and gate not in {
        "not_required", "approved"
    }:
        return f"Blocked: plan status is {gate}."
    return handlers[block.name](**block.input)

状態が requiredpendingrejected の間、チームメイトはファイルを読み、計画を提出または修正できるが、Shell 実行やファイル書き込みはできない。承認応答で approved になった後にだけツールが解放される。


一連の実行例

s15 >> このサンプルバックエンドをリファクタリングしてください。
       設定読み込み、認証、テストを整理し、
       既存インターフェースを保ってテストを通してください。

Lead: config、auth、tests の 3 方向で並行処理することを提案します。
      チームを開始しますか?

s15 >> 始めてください

[teammate] config spawned
[teammate] auth spawned
[teammate] tests spawned
[bus] auth → lead (result) ...
[bus] auth → lead (idle_notification) ...
[wake: 2 team events → new turn]
Lead: 認証の結果を受け取りました。残りの作業も調整します。

端末には、ユーザー要求、Lead の分担、起動、メッセージ、結果、IDLE、終了イベントが表示される。ユーザーが Lead を指名したり、受信箱の確認を頼んだりする必要はない。


s14 からの変更

コンポーネント s14 s15
Agent 1 つ 1 つの Lead + 永続チームメイト
ユーザーフロー 依頼を直接実行 チーム案を提示してから起動を確認
通信 なし ファイル受信箱 + 自動イベント配信
ライフサイクル 1 つのループ チームメイトの WORK / IDLE / shutdown
結果通知 現在の Agent の出力 resultidle_notification を分離
制御 なし 終了と計画承認プロトコル
強制 チーム制約なし 必須計画が変更系ツールをゲート

試してみる

cd learn-claude-code
python s15_agent_teams/code.py

まず通常の依頼を入力する:

このサンプルバックエンドをリファクタリングしてください。
設定読み込み、認証ロジック、テストを整理し、
既存インターフェースを保ったままテストを通してください。

Lead がチーム案を示したら、次のように返す:

始めてください

spawnedresultidle_notificationplan_approval_*shutdown_* の各イベントと、.mailboxes/ のファイルが生成・消費される流れを確認する。


次へ

s15 では、Lead が各チームメイトへ明示的に仕事を割り当てる。次のセッションでは共有タスクボードを IDLE のチームメイトに公開し、実行可能な仕事を自ら見つけて claim できるようにする。

次へ:s16 Autonomous Agents