Learn Claude Code
s10

Team Protocols

Collaboration

Shared Communication Rules

440 LOC13 toolsrequest_id correlation for two protocols
One request-response pattern drives all team negotiation

s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12

"Teammates need shared communication rules" -- one request-response pattern drives all negotiation.

Harness layer: Protocols -- structured handshakes between models.

Problem

In s09, teammates work and communicate but lack structured coordination:

Shutdown: Killing an async loop leaves files half-written and config.json stale. You need a handshake: the lead requests, the teammate approves (finish and exit) or rejects (keep working).

Plan approval: When the lead says "refactor the auth module," the teammate starts immediately. For high-risk changes, the lead should review the plan first.

Both share the same structure: one side sends a request with a unique ID, the other responds referencing that ID.

Solution

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}  |

Shared FSM:
  [pending] --approve--> [approved]
  [pending] --reject---> [rejected]

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

How It Works

  1. The lead initiates shutdown by generating a request_id and sending through the inbox.
const shutdownRequests: Record<string, { target?: string; status: string }> = {};

function handleShutdownRequest(teammate: string): string {
  const reqId = randomUUID().slice(0, 8);
  shutdownRequests[reqId] = { target: teammate, status: "pending" };
  BUS.send("lead", teammate, "Please shut down gracefully.",
           "shutdown_request", { request_id: reqId });
  return `Shutdown request ${reqId} sent (status: pending)`;
}
  1. The teammate receives the request and responds with approve/reject.
if (toolName === "shutdown_response") {
  const reqId = args.request_id as string;
  const approve = args.approve as boolean;
  if (reqId in shutdownRequests) {
    shutdownRequests[reqId].status = approve ? "approved" : "rejected";
  }
  BUS.send(sender, "lead", (args.reason as string) || "",
           "shutdown_response",
           { request_id: reqId, approve });
}
  1. Plan approval follows the identical pattern. The teammate submits a plan (generating a request_id), the lead reviews (referencing the same request_id).
const planRequests: Record<string, { from: string; plan: string; status: string }> = {};

function handlePlanReview(requestId: string, approve: boolean, feedback = ""): string {
  const req = planRequests[requestId];
  req.status = approve ? "approved" : "rejected";
  BUS.send("lead", req.from, feedback,
           "plan_approval_response",
           { request_id: requestId, approve });
  return `Plan ${req.status} for '${req.from}'`;
}

One FSM, two applications. The same pending -> approved | rejected state machine handles any request-response protocol.

What Changed From s09

ComponentBefore (s09)After (s10)
Tools912 (+shutdown_req/resp +plan)
ShutdownNatural exit onlyRequest-response handshake
Plan gatingNoneSubmit/review with approval
CorrelationNonerequest_id per request
FSMNonepending -> approved/rejected

Try It

cd learn-claude-code-ts
bun run agents/s10_team_protocols.ts
  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. Type /team to monitor statuses