analysis_claude_code/docs/ja/s10-team-protocols.md
CrazyBoyM c6a27ef1d7 feat: build an AI agent from 0 to 1 -- 11 progressive sessions
- 11 sessions from basic agent loop to autonomous teams
- Python MVP implementations for each session
- Mental-model-first docs in en/zh/ja
- Interactive web platform with step-through visualizations
- Incremental architecture: each session adds one mechanism
2026-02-21 17:02:43 +08:00

7.8 KiB

s10: Team Protocols

同じrequest_idハンドシェイクパターンがシャットダウンとプラン承認の両方を支える -- 1つのFSM、2つの適用。

問題

s09ではチームメイトが作業しコミュニケーションするが、構造化された協調はない。2つの問題が生じる:

シャットダウン: チームメイトをどうやってクリーンに停止するか。スレッドを強制終了するとファイルが中途半端に書かれ、config.jsonが不正な状態になる。グレースフルシャットダウンにはハンドシェイクが必要だ: リーダーが要求し、チームメイトが承認(終了処理を行い退出)するか拒否(作業を継続)するかを判断する。

プラン承認: 実行をどうやってゲーティングするか。リーダーが「認証モジュールをリファクタリングして」と言うと、チームメイトは即座に開始する。リスクの高い変更では、実行開始前にリーダーが計画をレビューすべきだ。ジュニアが提案し、シニアが承認する。

両方の問題は同じ構造を共有している: 一方がユニークなIDを持つリクエストを送り、もう一方がそのIDを参照してレスポンスする。有限状態機械が各リクエストをpending -> approved | rejectedの遷移で追跡する。

解決策

Shutdown Protocol            Plan Approval Protocol
==================           ======================

Lead             Teammate    Teammate           Lead
  |                 |           |                 |
  |--shutdown_req-->|           |--plan_req------>|
  | {req_id:"abc"}  |           | {req_id:"xyz"}  |
  |                 |           |                 |
  |<--shutdown_resp-|           |<--plan_resp-----|
  | {req_id:"abc",  |           | {req_id:"xyz",  |
  |  approve:true}  |           |  approve:true}  |
  |                 |           |                 |
  v                 v           v                 v
tracker["abc"]     exits     proceeds          tracker["xyz"]
 = approved                                     = approved

Shared FSM (identical for both protocols):
  [pending] --approve--> [approved]
  [pending] --reject---> [rejected]

Trackers:
  shutdown_requests = {req_id: {target, status}}
  plan_requests     = {req_id: {from, plan, status}}

仕組み

  1. リーダーがrequest_idを生成し、インボックス経由でshutdown_requestを送信してシャットダウンを開始する。
shutdown_requests = {}

def handle_shutdown_request(teammate: str) -> str:
    req_id = str(uuid.uuid4())[:8]
    shutdown_requests[req_id] = {
        "target": teammate, "status": "pending",
    }
    BUS.send("lead", teammate, "Please shut down gracefully.",
             "shutdown_request", {"request_id": req_id})
    return f"Shutdown request {req_id} sent (status: pending)"
  1. チームメイトはインボックスでリクエストを受信し、shutdown_responseツールを呼び出して承認または拒否する。
if tool_name == "shutdown_response":
    req_id = args["request_id"]
    approve = args["approve"]
    if req_id in shutdown_requests:
        shutdown_requests[req_id]["status"] = \
            "approved" if approve else "rejected"
    BUS.send(sender, "lead", args.get("reason", ""),
             "shutdown_response",
             {"request_id": req_id, "approve": approve})
    return f"Shutdown {'approved' if approve else 'rejected'}"
  1. チームメイトのループが承認済みシャットダウンを確認して終了する。
if (block.name == "shutdown_response"
        and block.input.get("approve")):
    should_exit = True
# ...
member["status"] = "shutdown" if should_exit else "idle"
  1. プラン承認も同一のパターンに従う。チームメイトがプランを提出し、request_idを生成する。
plan_requests = {}

if tool_name == "plan_approval":
    plan_text = args.get("plan", "")
    req_id = str(uuid.uuid4())[:8]
    plan_requests[req_id] = {
        "from": sender, "plan": plan_text,
        "status": "pending",
    }
    BUS.send(sender, "lead", plan_text,
             "plan_approval_request",
             {"request_id": req_id, "plan": plan_text})
    return f"Plan submitted (request_id={req_id})"
  1. リーダーがレビューし、同じrequest_idでレスポンスする。
def handle_plan_review(request_id, approve, feedback=""):
    req = plan_requests.get(request_id)
    if not req:
        return f"Error: Unknown request_id '{request_id}'"
    req["status"] = "approved" if approve else "rejected"
    BUS.send("lead", req["from"], feedback,
             "plan_approval_response",
             {"request_id": request_id,
              "approve": approve,
              "feedback": feedback})
    return f"Plan {req['status']} for '{req['from']}'"
  1. 両プロトコルとも同じplan_approvalツール名を2つのモードで使用する: チームメイトが提出(request_idなし)、リーダーがレビュー(request_idあり)。
# Lead tool dispatch:
"plan_approval": lambda **kw: handle_plan_review(
    kw["request_id"], kw["approve"],
    kw.get("feedback", "")),
# Teammate: submit mode (generate request_id)

主要コード

2つのプロトコルハンドラ(agents/s10_team_protocols.py):

shutdown_requests = {}
plan_requests = {}

# -- Shutdown --
def handle_shutdown_request(teammate):
    req_id = str(uuid.uuid4())[:8]
    shutdown_requests[req_id] = {
        "target": teammate, "status": "pending"
    }
    BUS.send("lead", teammate,
             "Please shut down gracefully.",
             "shutdown_request",
             {"request_id": req_id})

# -- Plan Approval --
def handle_plan_review(request_id, approve, feedback=""):
    req = plan_requests[request_id]
    req["status"] = "approved" if approve else "rejected"
    BUS.send("lead", req["from"], feedback,
             "plan_approval_response",
             {"request_id": request_id,
              "approve": approve})

# Both use the same FSM:
# pending -> approved | rejected
# Both correlate by request_id across async inboxes

s09からの変更点

Component Before (s09) After (s10)
Tools 9 12 (+shutdown_req/resp +plan)
Shutdown Natural exit only Request-response handshake
Plan gating None Submit/review with approval
Request tracking None Two tracker dicts
Correlation None request_id per request
FSM None pending -> approved/rejected

設計原理

request_id相関パターンは、任意の非同期インタラクションを追跡可能な有限状態マシンに変換する。同じ3状態マシン(pending -> approved/rejected)がシャットダウン、プラン承認、または将来の任意のプロトコルに適用される。1つのパターンが複数のプロトコルを処理できるのはこのためだ -- FSMは何を承認しているかを気にしない。request_idはメッセージが順不同で到着する可能性のある非同期インボックス間で相関を提供し、エージェント間のタイミング差異に対してパターンを堅牢にする。

試してみる

cd learn-claude-code
python agents/s10_team_protocols.py

試せるプロンプト例:

  1. Spawn alice as a coder. Then request her shutdown.
  2. List teammates to see alice's status after shutdown approval
  3. Spawn bob with a risky refactoring task. Review and reject his plan.
  4. Spawn charlie, have him submit a plan, then approve it.
  5. /teamと入力してステータスを監視する