s16: Team Protocols — Teammates Need Agreements
s01 → ... → s14 → s15 → s16 → s17 → s18 → s19 → s20
"Teammates need agreements" — request-response pattern drives all negotiation.
Harness Layer: Protocols — Structured handshakes between agents.
The Problem
s15's teammates can work, but coordination is loose: Lead sends a message, teammate replies, no structured protocol. Two scenarios expose the gap:
Shutdown: Lead wants Alice to shut down. Killing the thread outright leaves half-written files on disk. A handshake is needed: Lead sends a request, Alice confirms after wrapping up.
Plan approval: Bob wants to refactor the auth module, a high-risk operation. Lead should review Bob's plan first, approve before Bob proceeds.
Both scenarios share the same structure: one side sends a request, the other replies, both linked by the same ID. A state machine tracks: pending → approved / rejected.
The Solution
)
Teaching code continues the agent capability arc from earlier chapters and adds structured protocols on top of S15's team communication. To stay focused on the protocol mechanism, it omits full error recovery, memory, and skill systems. Added: ProtocolState (request state tracking), dispatch_message (routes incoming messages by type to handlers), match_response (correlates response to request via request_id, with type validation).
Two protocols, one mechanism:
Teaching version demonstrates the request-response message flow for plan approval, but does not implement execution gating (intercepting bash/write_file when not approved). Real CC has a permission gating mechanism for teammates.
How It Works
ProtocolState: Request State
Each protocol request creates a state record tracking who sent it, to whom, current status, and payload:
A record is created when sending a request, found via request_id when receiving a response, and its status updated.
Four-Step Protocol Flow
Using shutdown as an example, the full chain:
request_id is the correlation key across the entire chain: the request carries it out, the response carries it back.
dispatch_message: Route by Type
A teammate's inbox receives both plain messages and protocol messages. handle_inbox_message dispatches by message type:
Adding a new protocol type means adding a new if branch.
match_response: Type Validation
match_response doesn't just find state by request_id, it also validates that the response type matches the request type:
A shutdown_response cannot accidentally approve a plan_approval request.
Unified Inbox Consumer: consume_lead_inbox
Both the check_inbox tool and the main loop call the same consume_lead_inbox() function, routing protocol messages before returning remaining content. This prevents messages from being consumed without protocol state updates:
The main loop also injects inbox messages into history so the LLM can see and react to them.
Teammate Idle Loop: Wait Instead of Exit
s15's teammates exit after 10 rounds. s16's teammates enter idle waiting after the LLM returns a non-tool_use response: poll inbox, respond to shutdown_request and exit, or continue working on new messages.
Teaching version omits idle_notification to Lead. Real CC sends idle_notification when idle, so Lead knows the teammate is free for new tasks.
Putting It Together
Shutdown handshake complete: request → confirm → shutdown. Every step tracked by request_id.
Changes from s15
Try It
Try these prompts:
Spawn alice as a backend dev. Ask her to create a file. Then request her shutdown.Spawn bob with a refactoring task. Have him submit a plan first. Then review and approve it.
What to observe: Is the shutdown handshake complete (request → confirm → shutdown)? Does pending_requests state transition correctly? Is request_id consistent between request and response? Can the idle teammate receive shutdown_request?
What's Next
In s15-s16, Lead must assign tasks to each teammate. "Alice does this, Bob does that." With 10 unclaimed tasks on the board, Lead has to manually assign each one.
What if teammates could check the board and claim tasks themselves? Lead only needs to create tasks; teammates discover, claim, and complete them on their own.
s17 Autonomous Agents → Self-organizing teammates, no leader assignment needed.
Deep Dive into CC Source
CC's team protocol implementation (teammateMailbox.ts, 1184 lines) shares the same core structure as the teaching version: request_id + approve/reject request-response pattern. Differences:
Shutdown protocol: CC's shutdown is three-way communication (teammateMailbox.ts:720-763, SendMessageTool.ts:268-430). Lead sends shutdown_request, teammate replies shutdown_approved (or shutdown_rejected with reason), system sends teammate_terminated to notify all parties. After confirmation, system cleans up pane (tmux/iTerm2), unassigns tasks, removes member from team config (useInboxPoller.ts:677-800). Teaching version uses shutdown_response as a unified name; real source splits into shutdown_approved and shutdown_rejected as two separate message types.
Plan approval: In the real source, plan approval request is generated by ExitPlanModeV2Tool.ts:263-312 when a plan-mode-required teammate exits plan mode. useInboxPoller.ts:599-661 currently auto-writes approval and passes the request to Lead as context (regular message). SendMessageTool.ts:434-518 retains explicit approve/reject response capability — approval can simultaneously set permissionMode (e.g. "approved but run in plan mode"), response can include feedback string for teammate to revise and resubmit. Not a simple "Lead manually uses review_plan tool" flow.
Message format: CC's protocol messages are structured JSON (with Zod schema validation), teaching version uses simple type + metadata dict. Field names are also inconsistent: permission uses request_id (teammateMailbox.ts:453-462), shutdown and plan approval use requestId (teammateMailbox.ts:684-763).
Execution gating: CC's teammates have full permission gating. Unapproved high-risk operations are intercepted, not optional. Teaching version only demonstrates the message flow without execution interception.
Generality: Teaching version's single FSM (pending → approved | rejected) maps to two protocols. This simplification is correct. CC's protocol messages all share the same request id correlation mechanism.
