Communication Between Agents: Message Formats, Protocols & Coordination Patterns

Michael BrenndoerferJuly 27, 202520 min read

Learn how AI agents exchange information and coordinate actions through structured messages, communication patterns like pub-sub and request-response, and protocols for task delegation and consensus building.

Communication Between Agents

In the previous chapter, you saw how multiple agents can work together by specializing in different tasks. But we glossed over a critical detail: how do agents actually talk to each other? When the guest agent finishes compiling dietary restrictions, how does it pass that information to the menu agent? When the research agent finds important data, how does it tell the writing agent?

Human teams communicate through language, gestures, emails, and meetings. AI agents need their own communication mechanisms. In this chapter, we'll explore how agents exchange information, coordinate their actions, and stay synchronized while working toward shared goals.

The Communication Challenge

Let's start with a concrete problem. Imagine you have two agents:

  • Agent A (Research): Finds the current price of Bitcoin
  • Agent B (Analysis): Determines if now is a good time to buy

Agent B needs the information Agent A discovers. But how does Agent A send that information? And how does Agent B know it's receiving price data rather than, say, historical trends or market sentiment?

Here's what makes agent communication tricky:

Different Contexts: Each agent has its own conversation history, system prompt, and state. When Agent A says "the price is $45,000," Agent B needs enough context to understand what that means.

Timing: Agent B might need to wait for Agent A to finish its research. How does it know when Agent A is done? What if Agent A encounters an error?

Format: Should Agent A send a simple number, a structured JSON object, or a natural language description? The format affects how easily Agent B can use the information.

Reliability: What if the message gets lost or corrupted? What if Agent A sends information that Agent B doesn't understand?

Let's see these challenges in action with a simple example:

In[3]:
Code
## Using Claude Sonnet 4.5 for agent communication
from anthropic import Anthropic
import json

client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

## Agent A: Research Agent
def research_agent(query):
    """
    Researches information and returns findings.
    """
    system_prompt = """You are a research specialist. 
    When asked to research something, provide factual information in a clear format.
    Always structure your response as JSON with 'topic', 'findings', and 'confidence' fields."""
    
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        system=system_prompt,
        messages=[{"role": "user", "content": query}]
    )
    
    return response.content[0].text

## Agent B: Analysis Agent
def analysis_agent(research_data):
    """
    Analyzes research data and provides recommendations.
    """
    system_prompt = """You are an analysis specialist.
    You receive research findings and provide actionable recommendations.
    Focus on practical insights."""
    
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        system=system_prompt,
        messages=[{"role": "user", "content": f"Analyze this research: {research_data}"}]
    )
    
    return response.content[0].text

## Simple communication flow
research_result = research_agent("What is the current sentiment around electric vehicles?")
print(f"Research Agent found:\n{research_result}\n")

analysis_result = analysis_agent(research_result)
print(f"Analysis Agent recommends:\n{analysis_result}")
Out[3]:
Console
Research Agent found:
```json
{
  "topic": "Electric Vehicle Sentiment Analysis",
  "findings": {
    "overall_sentiment": "Mixed but trending positive",
    "positive_factors": [
      "Environmental benefits and reduced emissions",
      "Lower operating costs compared to gasoline vehicles",
      "Improved technology and longer range capabilities",
      "Government incentives and tax credits in many regions",
      "Growing charging infrastructure",
      "Performance advantages (instant torque, quiet operation)"
    ],
    "concerns": [
      "Higher upfront purchase costs",
      "Charging time compared to gasoline refueling",
      "Range anxiety for long-distance travel",
      "Charging infrastructure gaps in rural areas",
      "Battery degradation over time",
      "Supply chain and raw material sourcing questions",
      "Grid capacity concerns with mass adoption"
    ],
    "market_trends": [
      "Sales growth continues globally despite economic headwinds",
      "Traditional automakers rapidly expanding EV lineups",
      "China leading in production and adoption",
      "Europe showing strong policy support",
      "U.S. market growing but still relatively smaller percentage of total sales"
    ],
    "demographic_variations": [
      "Younger consumers generally more enthusiastic",
      "Urban residents more positive than rural",
      "Early adopters satisfied, mainstream buyers more cautious",
      "Business/fleet operators increasingly interested in TCO benefits"
    ]
  },
  "confidence": "High - based on consistent market data, sales trends, and consumer surveys from 2023-2024"
}
```

Analysis Agent recommends:
# Electric Vehicle Sentiment Analysis: Strategic Recommendations

## Executive Summary
The research reveals a market at an inflection point—sentiment is positive and growing, but significant barriers remain for mainstream adoption. Success requires targeted strategies addressing specific customer segments and pain points.

---

## Priority Recommendations

### 1. **Address the Cost-Value Perception Gap**

**Actions:**
- **Educate on Total Cost of Ownership (TCO)**: Create calculators and comparison tools showing 5-year costs including fuel savings, maintenance, and incentives
- **Flexible financing options**: Partner with financial institutions for EV-specific loans with lower rates
- **Trade-in programs**: Offer premium valuations for gas vehicles to offset upfront cost concerns
- **Target fleet operators first**: Their TCO focus makes them ideal early mainstream adopters—build case studies for consumer marketing

**Why**: Higher upfront costs are the #1 barrier, but the math favors EVs over time. Make this transparent.

---

### 2. **Segment Your Market Strategy**

**Urban vs. Rural Approach:**

**Urban Markets (Priority 1):**
- Emphasize convenience of home/work charging
- Highlight performance and tech features
- Focus on environmental benefits
- Leverage existing infrastructure density

**Rural Markets (Secondary Phase):**
- Partner with utilities on charging infrastructure
- Emphasize range improvements prominently
- Consider plug-in hybrids as bridge products
- Use local dealerships as charging hubs

**Why**: Demographics show clear divides—allocate resources where adoption likelihood is highest first.

---

### 3. **Eliminate Range Anxiety Through Psychology & Infrastructure**

**Tactical Solutions:**
- **Real-world range guarantees**: Offer buy-back/exchange if customers experience genuine range limitations in first year
- **Free charging packages**: Include 2-3 years of public charging credits with purchase
- **Trip planning integration**: Built-in navigation showing charging stops (make it foolproof)
- **Strategic infrastructure partnerships**: Work with governments/businesses to fill rural gaps on major routes

**Communication Strategy:**
- Lead marketing with actual range numbers in varied conditions
- Show real customer testimonials about long-distance travel
- Emphasize that 95% of daily driving is within easy range

**Why**: Range anxiety is psychological more than practical

This works, but notice the limitations. The analysis agent receives raw text from the research agent. It has to parse that text and hope it contains the information it needs. There's no guarantee the format will be consistent, no way to verify the message was understood correctly, and no mechanism for the analysis agent to ask follow-up questions.

We can do better.

Message Formats: Speaking a Common Language

The first step to better communication is establishing a shared format. Just as humans might agree to communicate via email with specific subject lines and structure, agents benefit from standardized message formats.

Natural Language Messages

The simplest approach is natural language. Agent A sends a message like "I found that Bitcoin is currently trading at $45,000 with high volatility." Agent B reads this and responds accordingly.

Advantages:

  • Flexible and expressive
  • Easy to understand when debugging
  • Works well with language models' strengths

Disadvantages:

  • Ambiguous (what does "high volatility" mean exactly?)
  • Hard to parse programmatically
  • Prone to misinterpretation

Natural language works well for high-level coordination where precision isn't critical. For example, a coordinator agent might tell worker agents: "Focus on the Q4 data" or "Prioritize accuracy over speed."

Structured Data Messages

For precise information exchange, structured formats like JSON work better:

In[4]:
Code
## Agent A sends structured data
message = {
    "from": "research_agent",
    "to": "analysis_agent",
    "timestamp": "2025-11-10T14:30:00Z",
    "message_type": "research_findings",
    "data": {
        "topic": "Bitcoin price",
        "current_price": 45000,
        "currency": "USD",
        "volatility": 0.15,
        "confidence": 0.92
    }
}

Now Agent B knows exactly what it's receiving. The price is a number, not a string. The volatility is quantified. The confidence score indicates how reliable this information is. The timestamp shows when the data was collected.

Let's rebuild our example with structured communication:

In[5]:
Code
## Using Claude Sonnet 4.5 with structured message passing
from anthropic import Anthropic
import json
from datetime import datetime

client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

class AgentMessage:
    """
    Standard message format for agent communication.
    """
    def __init__(self, sender, recipient, message_type, data):
        self.sender = sender
        self.recipient = recipient
        self.message_type = message_type
        self.data = data
        self.timestamp = datetime.utcnow().isoformat()
    
    def to_json(self):
        return json.dumps({
            "from": self.sender,
            "to": self.recipient,
            "type": self.message_type,
            "timestamp": self.timestamp,
            "data": self.data
        }, indent=2)
    
    @staticmethod
    def from_json(json_str):
        obj = json.loads(json_str)
        msg = AgentMessage(
            obj["from"],
            obj["to"],
            obj["type"],
            obj["data"]
        )
        msg.timestamp = obj["timestamp"]
        return msg

def extract_json(text):
    """
    Extract JSON from text that might include markdown code blocks.
    """
    import re
    # Build the pattern for code fences dynamically to avoid backtick issues
    fence = chr(96) * 3  # Three backticks
    pattern = fence + r'(?:json)?\s*\n?([\s\S]*?)\n?' + fence
    code_block_match = re.search(pattern, text)
    if code_block_match:
        return code_block_match.group(1).strip()
    # Otherwise, try to find JSON object directly
    json_match = re.search(r'\{[\s\S]*\}', text)
    if json_match:
        return json_match.group(0)
    return text.strip()

def research_agent_v2(query):
    """
    Research agent that returns structured messages.
    """
    system_prompt = """You are a research specialist.
    Return findings as a valid JSON object with these exact fields:
    - topic: what you researched
    - findings: key information discovered (as a string or simple list)
    - sources: where the information came from (as a string or list)
    - confidence: 0-1 score of how confident you are
    
    Return ONLY the JSON object, no other text or markdown."""
    
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        system=system_prompt,
        messages=[{"role": "user", "content": query}]
    )
    
    # Parse the research findings (extract JSON from potential markdown)
    raw_text = response.content[0].text
    json_text = extract_json(raw_text)
    try:
        findings = json.loads(json_text)
    except json.JSONDecodeError:
        # Fallback: create a simple findings object from the raw text
        findings = {
            "topic": query,
            "findings": raw_text[:500],
            "sources": ["LLM response"],
            "confidence": 0.7
        }
    
    # Wrap in a standard message
    message = AgentMessage(
        sender="research_agent",
        recipient="analysis_agent",
        message_type="research_complete",
        data=findings
    )
    
    return message

def analysis_agent_v2(message):
    """
    Analysis agent that expects structured messages.
    """
    # Verify message type
    if message.message_type != "research_complete":
        return AgentMessage(
            sender="analysis_agent",
            recipient=message.sender,
            message_type="error",
            data={"error": f"Expected 'research_complete', got '{message.message_type}'"}
        )
    
    system_prompt = """You are an analysis specialist.
    You receive research findings as JSON and provide recommendations.
    Return your analysis as a valid JSON object with:
    - recommendation: your main recommendation (as a string)
    - reasoning: why you recommend this (as a string)
    - confidence: 0-1 score of confidence
    
    Return ONLY the JSON object, no other text or markdown."""
    
    # Extract research data
    research_data = json.dumps(message.data)
    
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        system=system_prompt,
        messages=[{"role": "user", "content": f"Analyze: {research_data}"}]
    )
    
    # Parse the analysis (extract JSON from potential markdown)
    raw_text = response.content[0].text
    json_text = extract_json(raw_text)
    try:
        analysis = json.loads(json_text)
    except json.JSONDecodeError:
        # Fallback: create a simple analysis object from the raw text
        analysis = {
            "recommendation": raw_text[:300],
            "reasoning": "Based on the research findings",
            "confidence": 0.7
        }
    
    # Return structured response
    return AgentMessage(
        sender="analysis_agent",
        recipient="research_agent",
        message_type="analysis_complete",
        data=analysis
    )

## Example usage
print("=== Structured Agent Communication ===\n")

## Research agent does its work
research_msg = research_agent_v2("Research the benefits of renewable energy")
print(f"Research Agent sent:\n{research_msg.to_json()}\n")

## Analysis agent receives and processes
analysis_msg = analysis_agent_v2(research_msg)
print(f"Analysis Agent sent:\n{analysis_msg.to_json()}")
Out[5]:
Console
=== Structured Agent Communication ===

Research Agent sent:
{
  "from": "research_agent",
  "to": "analysis_agent",
  "type": "research_complete",
  "timestamp": "2025-12-07T12:03:02.665175",
  "data": {
    "topic": "Benefits of renewable energy",
    "findings": [
      "Environmental benefits: Renewable energy sources produce little to no greenhouse gas emissions or air pollutants during operation, helping combat climate change and reduce air pollution-related health issues",
      "Energy independence: Reduces reliance on imported fossil fuels and enhances national energy security by utilizing domestic resources like sun, wind, and water",
      "Economic advantages: Creates jobs in manufacturing, installation, and maintenance sectors; solar and wind are now cost-competitive with or cheaper than fossil fuels in many regions",
      "Sustainability: Renewable sources are inexhaustible on human timescales, unlike finite fossil fuel reserves",
      "Price stability: Operating costs are more predictable as fuel costs are eliminated; insulates consumers from volatile fossil fuel price fluctuations",
      "Health benefits: Reduces respiratory diseases, cardiovascular problems, and premature deaths associated with fossil fuel pollution",
      "Grid resilience: Distributed renewable energy systems can improve grid reliability and reduce transmission losses",
      "Water conservation: Most renewable technologies use significantly less water than conventional thermal power plants"
    ],
    "sources": [
      "International Energy Agency (IEA) reports on renewable energy",
      "U.S. Department of Energy and Environmental Protection Agency studies",
      "International Renewable Energy Agency (IRENA) publications",
      "Academic research from environmental science journals",
      "National Renewable Energy Laboratory (NREL) data",
      "World Health Organization studies on air quality and health"
    ],
    "confidence": 0.95
  }
}

Analysis Agent sent:
{
  "from": "analysis_agent",
  "to": "research_agent",
  "type": "analysis_complete",
  "timestamp": "2025-12-07T12:03:10.502700",
  "data": {
    "recommendation": "Accelerate investment and policy support for renewable energy transition across all sectors, prioritizing solar and wind deployment while developing grid infrastructure and energy storage solutions to maximize environmental, economic, and health benefits",
    "reasoning": "The research demonstrates overwhelming evidence across multiple dimensions: (1) Environmental - significant reduction in greenhouse gases and air pollution; (2) Economic - cost competitiveness achieved, job creation potential, and price stability advantages; (3) Health - measurable reduction in pollution-related diseases and mortality; (4) Strategic - enhanced energy security and independence from volatile fossil fuel markets; (5) Sustainability - inexhaustible resources ensuring long-term viability. The high confidence score (0.95) and credible sources (IEA, IRENA, NREL, WHO) validate these findings. The convergence of economic viability with environmental necessity creates a rare policy alignment where financial and ecological interests coincide, making this an optimal time for aggressive renewable energy expansion",
    "confidence": 0.92
  }
}

This structured approach gives us several benefits:

Type Safety: The message_type field lets agents verify they're receiving the expected kind of message.

Traceability: Timestamps and sender/recipient fields create an audit trail of communication.

Error Handling: Agents can send error messages in the same format, making it easy to handle failures.

Extensibility: You can add new message types without breaking existing communication patterns.

Communication Patterns

Now that we have a message format, let's explore different ways agents can communicate.

Request-Response

The simplest pattern: Agent A sends a request, Agent B sends a response.

Agent A: "What's the weather in Paris?"
Agent B: "Sunny, 22°C"

This is synchronous. Agent A waits for Agent B's response before continuing. It's straightforward but can be inefficient if Agent B takes a long time.

Fire-and-Forget

Agent A sends a message but doesn't wait for a response. It continues with other work.

Agent A: "Log this event: user clicked button"
Logger Agent: [receives message, logs it]
Agent A: [already moved on to other tasks]

This is asynchronous and efficient, but Agent A has no way to know if the message was received or processed successfully.

Publish-Subscribe

Multiple agents can subscribe to specific types of messages. When Agent A publishes a message, all subscribed agents receive it.

Agent A: [publishes "new_data_available"]
Agent B: [subscribed to "new_data_available", receives notification]
Agent C: [subscribed to "new_data_available", receives notification]
Agent D: [not subscribed, doesn't receive anything]

This pattern works well for broadcasting information to multiple interested parties without Agent A needing to know who they are.

Here's a simple implementation:

In[6]:
Code
## Using Claude Sonnet 4.5 for pub-sub coordination
class MessageBroker:
    """
    Simple publish-subscribe message broker for agent communication.
    """
    def __init__(self):
        self.subscriptions = {}  # topic -> list of agents
        self.message_history = []
    
    def subscribe(self, topic, agent_name):
        """
        Agent subscribes to a topic.
        """
        if topic not in self.subscriptions:
            self.subscriptions[topic] = []
        if agent_name not in self.subscriptions[topic]:
            self.subscriptions[topic].append(agent_name)
            print(f"{agent_name} subscribed to '{topic}'")
    
    def publish(self, topic, message):
        """
        Publish a message to all subscribers of a topic.
        """
        self.message_history.append({
            "topic": topic,
            "message": message,
            "timestamp": datetime.utcnow().isoformat()
        })
        
        if topic in self.subscriptions:
            subscribers = self.subscriptions[topic]
            print(f"\nPublishing to '{topic}' ({len(subscribers)} subscribers)")
            return subscribers
        else:
            print(f"\nPublishing to '{topic}' (no subscribers)")
            return []
    
    def get_history(self, topic=None):
        """
        Get message history, optionally filtered by topic.
        """
        if topic:
            return [msg for msg in self.message_history if msg["topic"] == topic]
        return self.message_history

## Example: Multiple agents coordinating through pub-sub
broker = MessageBroker()

## Agents subscribe to topics they care about
broker.subscribe("market_data", "trading_agent")
broker.subscribe("market_data", "analysis_agent")
broker.subscribe("market_data", "reporting_agent")
broker.subscribe("user_actions", "logging_agent")
broker.subscribe("user_actions", "analytics_agent")

## When market data arrives, all interested agents are notified
market_subscribers = broker.publish("market_data", {
    "symbol": "AAPL",
    "price": 178.50,
    "change": 2.3
})
print(f"Notified: {', '.join(market_subscribers)}")

## When a user action occurs, different agents are notified
action_subscribers = broker.publish("user_actions", {
    "action": "purchase",
    "item": "premium_subscription"
})
print(f"Notified: {', '.join(action_subscribers)}")
Out[6]:
Console
trading_agent subscribed to 'market_data'
analysis_agent subscribed to 'market_data'
reporting_agent subscribed to 'market_data'
logging_agent subscribed to 'user_actions'
analytics_agent subscribed to 'user_actions'

Publishing to 'market_data' (3 subscribers)
Notified: trading_agent, analysis_agent, reporting_agent

Publishing to 'user_actions' (2 subscribers)
Notified: logging_agent, analytics_agent

Conversation Threads

For complex coordination, agents might need back-and-forth conversations:

Agent A: "I need to schedule a meeting with the team."
Agent B: "What time works for you?"
Agent A: "Tuesday afternoon."
Agent B: "Tuesday at 2pm is available. Shall I book it?"
Agent A: "Yes, please."
Agent B: "Done. Meeting scheduled."

This requires maintaining conversation context. Each message needs to reference the thread it belongs to:

In[7]:
Code
class ConversationThread:
    """
    Manages a conversation thread between agents.
    """
    def __init__(self, thread_id, participants):
        self.thread_id = thread_id
        self.participants = participants
        self.messages = []
    
    def add_message(self, sender, content):
        """
        Add a message to the thread.
        """
        if sender not in self.participants:
            raise ValueError(f"{sender} is not a participant in this thread")
        
        message = {
            "thread_id": self.thread_id,
            "sender": sender,
            "content": content,
            "timestamp": datetime.utcnow().isoformat(),
            "sequence": len(self.messages)
        }
        self.messages.append(message)
        return message
    
    def get_history(self):
        """
        Get the full conversation history.
        """
        return self.messages
    
    def get_context_for_agent(self, agent_name):
        """
        Get conversation context formatted for a specific agent.
        """
        context = f"Conversation thread {self.thread_id}\n"
        context += f"Participants: {', '.join(self.participants)}\n\n"
        for msg in self.messages:
            context += f"{msg['sender']}: {msg['content']}\n"
        return context

## Example: Two agents having a conversation
thread = ConversationThread(
    thread_id="meeting_planning_001",
    participants=["coordinator_agent", "calendar_agent"]
)

thread.add_message("coordinator_agent", "I need to schedule a team meeting for next week.")
thread.add_message("calendar_agent", "I can see Tuesday 2pm or Thursday 3pm are available. Which works better?")
thread.add_message("coordinator_agent", "Tuesday 2pm works. Please book it.")
thread.add_message("calendar_agent", "Meeting scheduled for Tuesday at 2pm. I've sent invites to all team members.")

print("=== Conversation Thread ===")
print(thread.get_context_for_agent("coordinator_agent"))
Out[7]:
Console
=== Conversation Thread ===
Conversation thread meeting_planning_001
Participants: coordinator_agent, calendar_agent

coordinator_agent: I need to schedule a team meeting for next week.
calendar_agent: I can see Tuesday 2pm or Thursday 3pm are available. Which works better?
coordinator_agent: Tuesday 2pm works. Please book it.
calendar_agent: Meeting scheduled for Tuesday at 2pm. I've sent invites to all team members.

Coordination Protocols

Message formats and patterns are building blocks. Coordination protocols define the rules for how agents work together on specific types of tasks.

Task Delegation Protocol

When one agent needs another agent to do work:

  1. Request: Coordinator sends a task with clear requirements
  2. Acknowledgment: Worker confirms it received the task
  3. Progress Updates: Worker sends periodic status updates
  4. Completion: Worker sends results or error message
  5. Confirmation: Coordinator acknowledges receipt
In[8]:
Code
## Using Claude Sonnet 4.5 for task delegation
class TaskDelegationProtocol:
    """
    Protocol for delegating tasks between agents.
    """
    def __init__(self):
        self.active_tasks = {}
    
    def request_task(self, task_id, from_agent, to_agent, task_description):
        """
        Coordinator requests a task from a worker.
        """
        task = {
            "task_id": task_id,
            "coordinator": from_agent,
            "worker": to_agent,
            "description": task_description,
            "status": "requested",
            "created_at": datetime.utcnow().isoformat(),
            "updates": []
        }
        self.active_tasks[task_id] = task
        
        return AgentMessage(
            sender=from_agent,
            recipient=to_agent,
            message_type="task_request",
            data=task
        )
    
    def acknowledge_task(self, task_id, worker_agent):
        """
        Worker acknowledges receiving the task.
        """
        if task_id not in self.active_tasks:
            raise ValueError(f"Unknown task: {task_id}")
        
        task = self.active_tasks[task_id]
        task["status"] = "in_progress"
        task["updates"].append({
            "timestamp": datetime.utcnow().isoformat(),
            "message": "Task acknowledged and started"
        })
        
        return AgentMessage(
            sender=worker_agent,
            recipient=task["coordinator"],
            message_type="task_acknowledged",
            data={"task_id": task_id}
        )
    
    def update_progress(self, task_id, worker_agent, progress_message):
        """
        Worker sends a progress update.
        """
        if task_id not in self.active_tasks:
            raise ValueError(f"Unknown task: {task_id}")
        
        task = self.active_tasks[task_id]
        task["updates"].append({
            "timestamp": datetime.utcnow().isoformat(),
            "message": progress_message
        })
        
        return AgentMessage(
            sender=worker_agent,
            recipient=task["coordinator"],
            message_type="task_progress",
            data={
                "task_id": task_id,
                "progress": progress_message
            }
        )
    
    def complete_task(self, task_id, worker_agent, results):
        """
        Worker completes the task and sends results.
        """
        if task_id not in self.active_tasks:
            raise ValueError(f"Unknown task: {task_id}")
        
        task = self.active_tasks[task_id]
        task["status"] = "completed"
        task["results"] = results
        task["completed_at"] = datetime.utcnow().isoformat()
        
        return AgentMessage(
            sender=worker_agent,
            recipient=task["coordinator"],
            message_type="task_complete",
            data={
                "task_id": task_id,
                "results": results
            }
        )
    
    def get_task_status(self, task_id):
        """
        Get the current status of a task.
        """
        return self.active_tasks.get(task_id)

## Example usage
protocol = TaskDelegationProtocol()

print("=== Task Delegation Protocol ===\n")

## Step 1: Coordinator requests a task
request_msg = protocol.request_task(
    task_id="research_001",
    from_agent="coordinator",
    to_agent="research_agent",
    task_description="Research the top 3 cloud providers and compare pricing"
)
print(f"1. Task Requested:\n{request_msg.to_json()}\n")

## Step 2: Worker acknowledges
ack_msg = protocol.acknowledge_task("research_001", "research_agent")
print(f"2. Task Acknowledged:\n{ack_msg.to_json()}\n")

## Step 3: Worker sends progress update
progress_msg = protocol.update_progress(
    "research_001",
    "research_agent",
    "Completed research on AWS, starting Azure"
)
print(f"3. Progress Update:\n{progress_msg.to_json()}\n")

## Step 4: Worker completes task
complete_msg = protocol.complete_task(
    "research_001",
    "research_agent",
    {
        "providers": ["AWS", "Azure", "Google Cloud"],
        "comparison": "Detailed pricing comparison...",
        "recommendation": "AWS offers best value for your use case"
    }
)
print(f"4. Task Complete:\n{complete_msg.to_json()}\n")

## Check final status
status = protocol.get_task_status("research_001")
print(f"Final Status: {status['status']}")
print(f"Updates: {len(status['updates'])} progress updates")
Out[8]:
Console
=== Task Delegation Protocol ===

1. Task Requested:
{
  "from": "coordinator",
  "to": "research_agent",
  "type": "task_request",
  "timestamp": "2025-12-07T12:03:10.535184",
  "data": {
    "task_id": "research_001",
    "coordinator": "coordinator",
    "worker": "research_agent",
    "description": "Research the top 3 cloud providers and compare pricing",
    "status": "requested",
    "created_at": "2025-12-07T12:03:10.535176",
    "updates": []
  }
}

2. Task Acknowledged:
{
  "from": "research_agent",
  "to": "coordinator",
  "type": "task_acknowledged",
  "timestamp": "2025-12-07T12:03:10.535241",
  "data": {
    "task_id": "research_001"
  }
}

3. Progress Update:
{
  "from": "research_agent",
  "to": "coordinator",
  "type": "task_progress",
  "timestamp": "2025-12-07T12:03:10.535274",
  "data": {
    "task_id": "research_001",
    "progress": "Completed research on AWS, starting Azure"
  }
}

4. Task Complete:
{
  "from": "research_agent",
  "to": "coordinator",
  "type": "task_complete",
  "timestamp": "2025-12-07T12:03:10.535312",
  "data": {
    "task_id": "research_001",
    "results": {
      "providers": [
        "AWS",
        "Azure",
        "Google Cloud"
      ],
      "comparison": "Detailed pricing comparison...",
      "recommendation": "AWS offers best value for your use case"
    }
  }
}

Final Status: completed
Updates: 2 progress updates

This protocol ensures both agents stay synchronized. The coordinator knows the task is being worked on, can track progress, and receives results in a predictable format. The worker has a clear structure for communicating its status.

Consensus Protocol

When multiple agents need to agree on something:

  1. Proposal: One agent proposes a decision
  2. Voting: All agents vote (agree, disagree, abstain)
  3. Tally: Votes are counted according to rules (majority, unanimous, weighted)
  4. Decision: Result is announced to all agents
In[9]:
Code
class ConsensusProtocol:
    """
    Simple consensus protocol for agent decision-making.
    """
    def __init__(self, participants, threshold=0.5):
        self.participants = participants
        self.threshold = threshold  # Fraction of votes needed to pass
        self.proposals = {}
    
    def propose(self, proposal_id, proposer, description):
        """
        An agent proposes something for the group to decide.
        """
        if proposer not in self.participants:
            raise ValueError(f"{proposer} is not a participant")
        
        self.proposals[proposal_id] = {
            "id": proposal_id,
            "proposer": proposer,
            "description": description,
            "votes": {},
            "status": "voting",
            "created_at": datetime.utcnow().isoformat()
        }
        
        print(f"\n{proposer} proposed: {description}")
        print(f"Proposal ID: {proposal_id}")
        print(f"Waiting for votes from: {', '.join(self.participants)}")
    
    def vote(self, proposal_id, voter, vote):
        """
        An agent votes on a proposal (True for agree, False for disagree).
        """
        if proposal_id not in self.proposals:
            raise ValueError(f"Unknown proposal: {proposal_id}")
        
        if voter not in self.participants:
            raise ValueError(f"{voter} is not a participant")
        
        proposal = self.proposals[proposal_id]
        proposal["votes"][voter] = vote
        
        print(f"{voter} voted: {'agree' if vote else 'disagree'}")
        
        # Check if all votes are in
        if len(proposal["votes"]) == len(self.participants):
            self._finalize_proposal(proposal_id)
    
    def _finalize_proposal(self, proposal_id):
        """
        Tally votes and finalize the proposal.
        """
        proposal = self.proposals[proposal_id]
        votes = proposal["votes"]
        
        agree_count = sum(1 for v in votes.values() if v)
        total_votes = len(votes)
        agreement_ratio = agree_count / total_votes
        
        if agreement_ratio >= self.threshold:
            proposal["status"] = "accepted"
            result = "ACCEPTED"
        else:
            proposal["status"] = "rejected"
            result = "REJECTED"
        
        proposal["finalized_at"] = datetime.utcnow().isoformat()
        proposal["agreement_ratio"] = agreement_ratio
        
        print(f"\nProposal {proposal_id}: {result}")
        print(f"Votes: {agree_count}/{total_votes} agreed ({agreement_ratio:.0%})")
        print(f"Threshold: {self.threshold:.0%}")
    
    def get_result(self, proposal_id):
        """
        Get the result of a proposal.
        """
        return self.proposals.get(proposal_id)

## Example: Agents reaching consensus
agents = ["agent_a", "agent_b", "agent_c", "agent_d"]
consensus = ConsensusProtocol(agents, threshold=0.75)

## Propose a decision
consensus.propose(
    "upgrade_001",
    "agent_a",
    "Upgrade to the new model version (higher cost but better performance)"
)

## Agents vote
consensus.vote("upgrade_001", "agent_a", True)   # Proposer agrees
consensus.vote("upgrade_001", "agent_b", True)   # Agent B agrees
consensus.vote("upgrade_001", "agent_c", True)   # Agent C agrees
consensus.vote("upgrade_001", "agent_d", False)  # Agent D disagrees

## Result is automatically finalized when all votes are in
result = consensus.get_result("upgrade_001")
print(f"\nFinal decision: {result['status']}")
Out[9]:
Console

agent_a proposed: Upgrade to the new model version (higher cost but better performance)
Proposal ID: upgrade_001
Waiting for votes from: agent_a, agent_b, agent_c, agent_d
agent_a voted: agree
agent_b voted: agree
agent_c voted: agree
agent_d voted: disagree

Proposal upgrade_001: ACCEPTED
Votes: 3/4 agreed (75%)
Threshold: 75%

Final decision: accepted

Handling Communication Failures

Communication between agents isn't always perfect. Networks fail, messages get lost, agents crash. Robust multi-agent systems need to handle these failures gracefully.

Timeouts

If Agent A sends a request to Agent B and doesn't get a response within a reasonable time, it should timeout and either retry or report an error:

In[10]:
Code
import time

def send_with_timeout(sender_func, timeout_seconds=5):
    """
    Send a message and wait for response with timeout.
    """
    start_time = time.time()
    
    try:
        # In a real system, this would be async
        response = sender_func()
        elapsed = time.time() - start_time
        
        if elapsed > timeout_seconds:
            return {
                "status": "timeout",
                "message": f"No response after {timeout_seconds} seconds"
            }
        
        return {
            "status": "success",
            "response": response
        }
    except Exception as e:
        return {
            "status": "error",
            "message": str(e)
        }

Retries

For transient failures, retrying can help:

In[11]:
Code
def send_with_retry(sender_func, max_retries=3):
    """
    Send a message with automatic retries on failure.
    """
    for attempt in range(max_retries):
        try:
            response = sender_func()
            return {
                "status": "success",
                "response": response,
                "attempts": attempt + 1
            }
        except Exception as e:
            if attempt == max_retries - 1:
                # Last attempt failed
                return {
                    "status": "failed",
                    "error": str(e),
                    "attempts": max_retries
                }
            # Wait before retrying (exponential backoff)
            time.sleep(2 ** attempt)

Acknowledgments

For critical messages, require acknowledgments:

In[12]:
Code
class ReliableMessaging:
    """
    Ensures messages are received by requiring acknowledgments.
    """
    def __init__(self):
        self.pending_acks = {}
    
    def send_reliable(self, message_id, sender, recipient, content):
        """
        Send a message that requires acknowledgment.
        """
        self.pending_acks[message_id] = {
            "sender": sender,
            "recipient": recipient,
            "content": content,
            "sent_at": datetime.utcnow().isoformat(),
            "acknowledged": False
        }
        
        return AgentMessage(
            sender=sender,
            recipient=recipient,
            message_type="reliable_message",
            data={
                "message_id": message_id,
                "content": content,
                "requires_ack": True
            }
        )
    
    def acknowledge(self, message_id, recipient):
        """
        Recipient acknowledges receiving the message.
        """
        if message_id not in self.pending_acks:
            raise ValueError(f"Unknown message: {message_id}")
        
        self.pending_acks[message_id]["acknowledged"] = True
        self.pending_acks[message_id]["acked_at"] = datetime.utcnow().isoformat()
        
        msg = self.pending_acks[message_id]
        return AgentMessage(
            sender=recipient,
            recipient=msg["sender"],
            message_type="acknowledgment",
            data={"message_id": message_id}
        )
    
    def check_pending(self):
        """
        Check for messages that haven't been acknowledged.
        """
        return {
            msg_id: msg 
            for msg_id, msg in self.pending_acks.items() 
            if not msg["acknowledged"]
        }

Real-World Communication: The A2A Protocol

Earlier we discussed the Agent-to-Agent (A2A) Protocol as a framework for multi-agent systems. Now let's look specifically at how A2A handles communication.

A2A defines standard message formats and communication patterns that work across different platforms. When agents use A2A, they can communicate even if one is built with Claude, another with GPT-5, and a third with Gemini.

Key A2A Communication Features:

Agent Discovery: Agents can find other agents by querying a registry. "Are there any research agents available?" returns a list of agents with their capabilities.

Capability Negotiation: Before communicating, agents can check if they speak compatible "languages." Agent A can ask Agent B: "Do you accept JSON input?" or "Can you return image data?"

Standardized Message Envelope: All A2A messages include metadata (sender, recipient, timestamp, message type) in a consistent format, similar to our AgentMessage class but with additional fields for authentication and routing.

Task Lifecycle Management: A2A includes built-in protocols for task delegation, progress tracking, and completion, similar to our TaskDelegationProtocol but standardized across all A2A-compatible agents.

Error Handling: A2A defines standard error codes and formats, so agents can understand what went wrong even across different implementations.

While we won't implement the full A2A protocol here (it's complex and still evolving), the patterns we've explored in this chapter align with A2A's design principles. When you build multi-agent systems, following similar patterns will make your agents more interoperable and maintainable.

Practical Considerations

As you design communication between your agents, keep these principles in mind:

Keep Messages Small: Large messages slow down communication and increase the chance of errors. If you need to share a big dataset, send a reference (like a file path or database query) rather than the data itself.

Be Explicit: Don't assume the receiving agent has context. Include enough information in each message for it to be understood independently.

Version Your Protocols: As your system evolves, message formats might change. Include version numbers so agents can handle different formats gracefully.

Log Everything: Communication logs are invaluable for debugging. When something goes wrong, being able to trace the message flow helps you find the problem quickly.

Design for Failure: Assume messages will get lost, delayed, or corrupted. Build in timeouts, retries, and error handling from the start.

Test Communication Separately: Before building complex multi-agent workflows, test that your agents can reliably exchange simple messages. Get the communication layer working first, then add sophisticated behaviors.

Looking Ahead

You now understand how agents communicate: through structured messages, following specific patterns (request-response, pub-sub, conversations), using coordination protocols (task delegation, consensus), and handling failures gracefully.

In the next chapter, we'll explore the benefits and challenges of multi-agent systems in more depth. You'll learn when the added complexity of multiple agents is worth it, what problems can arise, and how to design multi-agent systems that are robust, efficient, and maintainable.

The key insight from this chapter is that communication is the foundation of collaboration. Just as human teams need clear communication channels and protocols, AI agents need well-designed message formats and coordination mechanisms. Get the communication right, and your agents can accomplish remarkable things together.

Glossary

Acknowledgment: A message sent by a recipient to confirm that it received and understood a previous message, used to ensure reliable communication.

Asynchronous Communication: A pattern where the sender doesn't wait for a response and continues with other work, useful for fire-and-forget messages and parallel processing.

Consensus Protocol: A set of rules for how multiple agents reach agreement on decisions through proposals, voting, and tallying results according to defined thresholds.

Conversation Thread: A sequence of related messages between agents, maintained with a thread ID and message history to preserve context across multiple exchanges.

Message Broker: A component that manages publish-subscribe communication, routing messages from publishers to all subscribed agents based on topics.

Message Envelope: The wrapper around message content that includes metadata like sender, recipient, timestamp, and message type, enabling proper routing and handling.

Publish-Subscribe: A communication pattern where agents subscribe to topics of interest and receive all messages published to those topics, enabling one-to-many communication.

Request-Response: A synchronous communication pattern where one agent sends a request and waits for another agent to send back a response before continuing.

Structured Data: Information formatted in a standardized way (like JSON) with defined fields and types, making it easier for agents to parse and use reliably.

Task Delegation Protocol: A coordination protocol that defines how one agent requests work from another, including acknowledgment, progress updates, completion, and error handling.

Timeout: A mechanism that limits how long an agent waits for a response before concluding the message was lost or the recipient is unavailable.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about agent communication patterns and protocols.

Loading component...

Reference

BIBTEXAcademic
@misc{communicationbetweenagentsmessageformatsprotocolscoordinationpatterns, author = {Michael Brenndoerfer}, title = {Communication Between Agents: Message Formats, Protocols & Coordination Patterns}, year = {2025}, url = {https://mbrenndoerfer.com/writing/communication-between-agents}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-12-25} }
APAAcademic
Michael Brenndoerfer (2025). Communication Between Agents: Message Formats, Protocols & Coordination Patterns. Retrieved from https://mbrenndoerfer.com/writing/communication-between-agents
MLAAcademic
Michael Brenndoerfer. "Communication Between Agents: Message Formats, Protocols & Coordination Patterns." 2025. Web. 12/25/2025. <https://mbrenndoerfer.com/writing/communication-between-agents>.
CHICAGOAcademic
Michael Brenndoerfer. "Communication Between Agents: Message Formats, Protocols & Coordination Patterns." Accessed 12/25/2025. https://mbrenndoerfer.com/writing/communication-between-agents.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Communication Between Agents: Message Formats, Protocols & Coordination Patterns'. Available at: https://mbrenndoerfer.com/writing/communication-between-agents (Accessed: 12/25/2025).
SimpleBasic
Michael Brenndoerfer (2025). Communication Between Agents: Message Formats, Protocols & Coordination Patterns. https://mbrenndoerfer.com/writing/communication-between-agents