Abstract / Overview

Agent communication protocols define how autonomous components exchange intent, facts, and results. A good protocol specifies message structure, speech acts, sequencing, error semantics, and security. This article gives a practical, implementation-first blueprint for designing and shipping a production-ready agent communication protocol for multi-agent systems, including LLM-driven agents. Assumption: JSON over HTTP or a message bus inside a trusted VPC, UTC timestamps, and stateless workers.

Conceptual Background

Minimal envelope fields

message_id , conversation_id , sender , receiver , timestamp , performative , content , reply_to , causality ( in_reply_to ), ttl , priority , schema_version , auth , signature .

Message lifecycle

Compose → Validate → Send → Route → Receive → Authorize → Handle → Reply or Conclude → Persist → Audit. Timeouts and cancellations are first-class.

Step-by-Step Walkthrough

1) Define roles and responsibilities

2) Choose performatives and map to transitions

3) Design the message schema

4) Specify conversations

5) Serialization and transport

6) Reliability, ordering, and idempotency

7) Security and governance

8) Error taxonomy

9) Observability

Code / JSON Snippets

A. Canonical message schema (JSON Schema)

Use this to validate messages at ingress.

 {
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AgentMessage",
  "type": "object",
  "required": [
    "message_id", "conversation_id", "sender", "receiver",
    "timestamp", "performative", "content", "schema_version"
  ],
  "properties": {
    "message_id": {"type": "string", "pattern": "^[a-f0-9-]{36}$"},
    "conversation_id": {"type": "string"},
    "sender": {"type": "string"},
    "receiver": {"type": "string"},
    "timestamp": {"type": "string", "format": "date-time"},
    "performative": {"type": "string"},
    "reply_to": {"type": "string"},
    "in_reply_to": {"type": "string"},
    "ttl": {"type": "integer", "minimum": 1},
    "priority": {"type": "integer", "minimum": 0, "maximum": 9},
    "schema_version": {"type": "string"},
    "idempotency_key": {"type": "string"},
    "auth": {
      "type": "object",
      "properties": {
        "type": {"type": "string"},
        "token": {"type": "string"}
      },
      "required": ["type", "token"]
    },
    "signature": {"type": "string"},
    "tool_calls": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "arguments"],
        "properties": {
          "name": {"type": "string"},
          "arguments": {"type": "object"}
        }
      }
    },
    "content": {"type": "object"},
    "attachments": {
      "type": "array",
      "items": {"type": "string", "format": "uri"}
    }
  },
  "additionalProperties": false
}

B. Example messages

Request to research agent

{
  "message_id": "7e1a6b3e-3a74-4c7a-8c6f-2e21b8f59f44",
  "conversation_id": "conv-2024-09-15-abc123",
  "sender": "planner@svc",
  "receiver": "researcher@svc",
  "timestamp": "2024-09-15T12:02:03Z",
  "performative": "request",
  "schema_version": "1.0.0",
  "ttl": 120,
  "idempotency_key": "task-4821-v1",
  "content": {
    "task": "market_scan",
    "query": "compare vector databases for hybrid search",
    "constraints": {"max_sources": 5, "timebox_min": 10}
  }
}

Inform with result

{
  "message_id": "2fbb2e8b-1c6a-4a53-a2b9-f90d15c7f9f7",
  "conversation_id": "conv-2024-09-15-abc123",
  "sender": "researcher@svc",
  "receiver": "planner@svc",
  "timestamp": "2024-09-15T12:12:30Z",
  "performative": "inform",
  "in_reply_to": "7e1a6b3e-3a74-4c7a-8c6f-2e21b8f59f44",
  "schema_version": "1.0.0",
  "content": {
    "status": "ok",
    "summary": "Three options exhibit strong hybrid support.",
    "artifacts": ["s3://reports/conv-2024-09-15-abc123/summary.md"]
  }
}

Error with retry hint

{
  "message_id": "6a7f7f60-459d-4908-8343-2a75be3b4a52",
  "conversation_id": "conv-2024-09-15-abc123",
  "sender": "researcher@svc",
  "receiver": "planner@svc",
  "timestamp": "2024-09-15T12:05:11Z",
  "performative": "error",
  "in_reply_to": "7e1a6b3e-3a74-4c7a-8c6f-2e21b8f59f44",
  "schema_version": "1.0.0",
  "content": {
    "code": "error.transient.rate_limit",
    "message": "Rate limit exceeded",
    "retry_after_seconds": 20
  }
}

C. Minimal Python reference implementation (HTTP + JSON)

A tiny sender and handler with validation and idempotency.

Sender.py

import json, uuid, time, requests, datetime

def now_iso():
    return datetime.datetime.utcnow().replace(microsecond=0).isoformat() + "Z"

def compose_request(conversation_id, sender, receiver, task, query):
    return {
        "message_id": str(uuid.uuid4()),
        "conversation_id": conversation_id,
        "sender": sender,
        "receiver": receiver,
        "timestamp": now_iso(),
        "performative": "request",
        "schema_version": "1.0.0",
        "ttl": 120,
        "idempotency_key": f"{conversation_id}:{task}",
        "content": {"task": task, "query": query}
    }

def send(url, msg):
    r = requests.post(url, json=msg, timeout=10)
    r.raise_for_status()
    return r.json()

if __name__ == "__main__":
    conv = "conv-{}".format(int(time.time()))
    msg = compose_request(conv, "planner@svc", "researcher@svc", "market_scan", "RAG evaluation best practices")
    print(send("http://localhost:8000/ingest", msg))
  

handler.py

from fastapi import FastAPI, Request
from pydantic import BaseModel, Field
from typing import Optional, List, Dict
import uvicorn, time, hashlib

app = FastAPI()
SEEN = set()

class ToolCall(BaseModel):
    name: str
    arguments: Dict[str, object]

class AgentMessage(BaseModel):
    message_id: str
    conversation_id: str
    sender: str
    receiver: str
    timestamp: str
    performative: str
    schema_version: str
    ttl: Optional[int] = 60
    idempotency_key: Optional[str] = None
    in_reply_to: Optional[str] = None
    reply_to: Optional[str] = None
    tool_calls: Optional[List[ToolCall]] = None
    content: Dict[str, object]

def dedupe(key: str) -> bool:
    if key in SEEN: 
        return True
    SEEN.add(key)
    return False

@app.post("/ingest")
async def ingest(msg: AgentMessage):
    # Idempotency
    if msg.idempotency_key and dedupe(msg.idempotency_key):
        return {"status": "duplicate", "message_id": msg.message_id}

    # TTL enforcement (simple)
    # In production, compare received_at - parsed timestamp.
    if msg.ttl and msg.ttl <= 0:
        return {"status": "error", "code": "error.timeout"}

    # Route by performative
    if msg.performative == "request" and msg.content.get("task") == "market_scan":
        # Simulate work
        time.sleep(0.1)
        return {
            "status": "ok",
            "reply": {
                "performative": "inform",
                "conversation_id": msg.conversation_id,
                "in_reply_to": msg.message_id,
                "summary": "Report ready"
            }
        }

    return {"status": "error", "code": "error.semantic.unknown_task"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

D. Sample workflow JSON (copy-paste)

A small, declarative orchestration of a research job. Each node sends a message and waits for a specific performative.

{
  "name": "market_research_workflow",
  "version": "1.0.0",
  "assumptions": ["HTTP JSON", "at-least-once delivery", "UTC timestamps"],
  "variables": {
    "conversation_id": "conv-${ISO_DATE}-${RAND}",
    "query": "vector database comparison for hybrid search"
  },
  "nodes": [
    {
      "id": "plan",
      "type": "emit",
      "message": {
        "performative": "request",
        "receiver": "researcher@svc",
        "content": {"task": "market_scan", "query": "${query}", "constraints": {"max_sources": 5}}
      },
      "out": {"on": "inform", "to": "summarize", "fallback": {"on": "error", "to": "retry"}}
    },
    {
      "id": "retry",
      "type": "wait_retry",
      "policy": {"max_retries": 3, "backoff_sec": 10},
      "out": {"to": "plan"}
    },
    {
      "id": "summarize",
      "type": "emit",
      "message": {
        "performative": "request",
        "receiver": "writer@svc",
        "content": {"task": "summarize", "source": "s3://reports/${conversation_id}/raw.json"}
      },
      "out": {"on": "inform", "to": "finish"}
    },
    {"id": "finish", "type": "end"}
  ]
}

Use Cases / Scenarios

Limitations / Considerations

Token budget model (set costs per your provider)

Let:

Per conversation cost:

Cost ≈ m * ( (t_in/1000)*C_in + (t_out/1000)*C_out )

Example:

Control cost by batching requests, tightening prompts, and using small models for routing.

Fixes (common pitfalls and solutions)

Diagram

  1. Sequence for Contract Net-style task allocation.

Untitled diagram _ Mermaid Chart-2025-08-25-115528
  1. Flowchart of request–response with error and retry

Untitled diagram _ Mermaid Chart-2025-08-25-120127

Conclusion

A durable agent communication protocol keeps teams productive and systems stable. Define performatives and conversations up front. Freeze the envelope, version the ontology, and validate every ingress. Prefer idempotency, causal ordering, and explicit error codes. Make observability and budgets visible. The result is a multi-agent system that remains interpretable, safe, and cost-aware as it scales.