10. Team Protocols

"Teammates need shared communication rules"

30 min read
πŸ’‘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

  1. 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")
  1. 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"]}
  1. 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

ComponentBefore (Agent Teams)After (Team Protocols)
ShutdownKill threadGraceful req/resp handshake
Plan reviewNonePlan request + lead approval
Message formatFree textTyped JSON with req_id
SafetyLowExplicit 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

The Request-Response Protocol
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_id
11 
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.
Step 1 of 4
πŸ§ͺ Try it yourself
πŸ”₯ Warm-up ~5 min

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.

πŸ”¨ Build ~20 min

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.

πŸš€ Stretch ~45 min

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'.

Found a mistake? Report it β†’