10. Team Protocols
"Teammates need shared communication rules"
New to this?
What is a protocol in agent communication?
A structured handshake β one side sends a request with a unique ID, the other responds referencing that ID. Protocols prevent misunderstandings: 'Are you done with task X?' / 'Yes, here is result X.' Rather than free-form messages.
What is the shutdown protocol?
A two-step handshake where the lead sends a shutdown_req (with a unique ID), and the teammate either approves (finishes current work and exits) or rejects (still busy, try later). This prevents killing a teammate mid-task and leaving files corrupted.
What is plan approval?
Before a teammate starts a high-risk task, it sends its plan to the lead for review. The lead can approve (proceed) or reject (revise the plan). This creates a human-in-the-loop checkpoint for dangerous operations.
The Problem
In the Agent Teams session, teammates work and communicate but lack structured coordination:
Shutdown: Killing a thread 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.
The 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", |
| approved:true} | | approved:false,|
| | | feedback:"..."}|
How It Works
- Request IDs are short UUIDs. Both request and response carry the same ID.
import uuid
def new_req_id() -> str:
return uuid.uuid4().hex[:8]
def send_shutdown_request(teammate_name: str) -> str:
req_id = new_req_id()
send_message(teammate_name, "lead", json.dumps({
"type": "shutdown_req",
"req_id": req_id,
}))
return req_id # caller stores this to match the response
def handle_shutdown_request(msg: dict, name: str) -> None:
req_id = msg["req_id"]
# Teammate decides: approve if idle, reject if mid-task
currently_working = get_status(name) == "WORKING"
send_message("lead", name, json.dumps({
"type": "shutdown_resp",
"req_id": req_id,
"approved": not currently_working,
"reason": "finishing current task" if currently_working else "ready to shutdown",
}))
if not currently_working:
update_status(name, "SHUTDOWN")
- Plan approval uses the same pattern, extended with structured plan content.
def request_plan_approval(plan: str, task_id: int) -> str:
req_id = new_req_id()
send_message("lead", "self", json.dumps({
"type": "plan_req",
"req_id": req_id,
"task_id": task_id,
"plan": plan,
}))
return req_id
def handle_plan_request(msg: dict) -> dict:
"""Lead reviews and approves/rejects the plan."""
# Lead's agent loop sees this in its inbox and decides
req_id = msg["req_id"]
teammate = msg.get("from")
# The LLM reviews the plan and calls approve_plan or reject_plan tool
return {"req_id": req_id, "teammate": teammate, "plan": msg["plan"]}
- The teammateβs loop checks message types before acting.
def process_inbox(name: str) -> None:
messages = drain_inbox(name)
for msg_raw in messages:
try:
msg = json.loads(msg_raw["content"])
msg_type = msg.get("type", "plain")
except (json.JSONDecodeError, KeyError):
msg_type = "plain"
msg = msg_raw
if msg_type == "shutdown_req":
handle_shutdown_request(msg, name)
elif msg_type == "plan_resp":
handle_plan_response(msg, name)
else:
# Plain task assignment β start working
start_task(msg_raw["content"], name)
What Changed From Agent Teams
| Component | Before (Agent Teams) | After (Team Protocols) |
|---|---|---|
| Shutdown | Kill thread | Graceful req/resp handshake |
| Plan review | None | Plan request + lead approval |
| Message format | Free text | Typed JSON with req_id |
| Safety | Low | Explicit checkpoints for risk |
Key Takeaway
Protocols transform free-form communication into structured negotiation. The req_id pattern is the key β it lets you match a response to the request that caused it, even when messages arrive out of order or with delays. The same two-step pattern (send request, await response with matching ID) works for any coordination need.
Interactive Code Walkthrough
1def new_req_id() -> str:2 return uuid.uuid4().hex[:8]3 4def send_shutdown_request(teammate_name: str) -> str:5 req_id = new_req_id()6 send_message(teammate_name, "lead", json.dumps({7 "type": "shutdown_req",8 "req_id": req_id,9 }))10 return req_id11 12def handle_shutdown_request(msg: dict, name: str) -> None:13 req_id = msg["req_id"]14 currently_working = get_status(name) == "WORKING"15 send_message("lead", name, json.dumps({16 "type": "shutdown_resp",17 "req_id": req_id,18 "approved": not currently_working,19 "reason": "finishing current task" if currently_working else "ready to shutdown",20 }))21 if not currently_working:22 update_status(name, "SHUTDOWN")23 24def process_inbox(name: str) -> None:25 messages = drain_inbox(name)26 for msg_raw in messages:27 try:28 msg = json.loads(msg_raw["content"])29 msg_type = msg.get("type", "plain")30 except (json.JSONDecodeError, KeyError):31 msg_type = "plain"32 if msg_type == "shutdown_req":33 handle_shutdown_request(msg, name)34 else:35 start_task(msg_raw["content"], name)36 new_req_id() generates an 8-character hex string from a UUID. Short enough to include in messages without bloating them, unique enough to avoid collisions across concurrent agents.Why do protocol messages need unique IDs? What would break if two requests had the same ID?
Hint
The lead agent wouldn't be able to match responses to the correct requests β results would get mixed up.
Implement the request-response protocol with unique IDs. Send 3 tasks to a teammate and verify each response matches its request.
Hint
Use uuid4() for IDs and include the request_id in the response message.
Add a plan approval handshake: before a teammate starts a complex task, it sends a proposed plan to the lead for approval. The lead can approve, modify, or reject.
Hint
Define message types: 'plan_proposal', 'plan_approved', 'plan_rejected', 'plan_modified'.