Communication Patterns
How agents talk to each other — the protocols that make or break multi-agent systems.
What You'll Learn
- The three fundamental agent communication patterns
- How to design message formats agents actually understand
- Synchronous vs. asynchronous agent communication
- Common pitfalls that cause agent miscommunication
Your Agents Will Break at the Handoffs
Think about the worst group project you've ever been in. One person does the research and writes a 10-page essay. The next person expected bullet points. The third person doesn't know what happened and starts over. Nobody agrees on the format, the deadlines, or who's responsible for what.
That's what happens when AI agents pass information without a communication protocol. Agent A outputs a free-form paragraph. Agent B expects structured JSON. The result? Garbled data, lost context, and cascading failures downstream.
Communication patterns solve this by establishing contracts between agents — agreed-upon formats, protocols, and expectations for how information flows through the system. Get this right and your agents coordinate like a surgery team. Get it wrong and you have an expensive game of telephone.
Direct Messaging
The simplest pattern: Agent A sends output directly to Agent B. Like a relay race — the baton passes from hand to hand. This works well for linear pipelines where each agent's output is the next agent's input.
Best for: Sequential workflows, simple handoffs, two-agent systems.
Watch out for: Format mismatches. Always define the exact structure of what gets passed.
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from env
def run_agent(role: str, system_prompt: str, user_input: str) -> str:
# Each agent is just a Claude call with a specialized system prompt
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": user_input}]
)
return response.content[0].text
# Agent A: Research agent finds information
research_result = run_agent(
role="researcher",
system_prompt="You are a research agent. Return findings as JSON with keys: topic, findings (list), confidence (0-1).",
user_input="Research the top 3 competitors in AI education pricing."
)
# Agent B: Analysis agent receives research output DIRECTLY
analysis_result = run_agent(
role="analyst",
system_prompt="You are an analysis agent. You receive research data and produce strategic recommendations.",
user_input=f"Analyze this research and recommend a pricing strategy:\n{research_result}"
)
print(analysis_result)
# "Based on the competitor analysis, I recommend a freemium model...
# Tier 1: Free (52 courses). Tier 2: $29/mo (premium features)..."
Broadcast (Pub/Sub)
One agent publishes a message. Multiple agents receive it and decide independently whether to act. Like a team Slack channel — everyone sees the message, but only relevant agents respond.
Best for: Parallel processing, event-driven systems, agents that need to react to the same trigger differently.
Watch out for: Race conditions and duplicate work. Two agents might both try to handle the same task.
import asyncio
# Define specialist agents that all react to the same event
AGENTS = {
"sentiment": "Analyze the sentiment of this customer message. Return: positive/negative/neutral + confidence.",
"category": "Categorize this support ticket. Return: billing/technical/feature-request/other.",
"urgency": "Rate the urgency of this message: low/medium/high/critical. Return urgency + reasoning.",
}
async def broadcast(message: str) -> dict:
# All agents process the SAME message in parallel
# Each agent sees the same input, but does something different with it
tasks = {
name: run_agent_async(name, prompt, message)
for name, prompt in AGENTS.items()
}
results = await asyncio.gather(*tasks.values())
return dict(zip(tasks.keys(), results))
# One customer message → three specialist analyses in parallel
ticket = "I've been charged twice and my account is locked. This is urgent!"
results = asyncio.run(broadcast(ticket))
# → {"sentiment": "negative (0.95)", "category": "billing", "urgency": "critical"}
Blackboard (Shared Space)
All agents read from and write to a shared workspace. No direct messages — agents check the blackboard, see what's new, contribute their piece, and move on. Like a collaborative document where everyone adds their section.
Best for: Complex problems where agents need full context, iterative refinement, consensus building.
Watch out for: Stale reads and write conflicts. Two agents editing the same section simultaneously creates chaos.
# The blackboard is shared state that all agents can read and write
# In production, this would be a database (Supabase, Redis, etc.)
blackboard = {
"task": "Write a blog post about AI agents",
"status": "planning",
"sections": {}, # agents write their contributions here
"feedback": [], # review comments from other agents
}
def research_agent(board: dict) -> dict:
# Read the task from the blackboard
topic = board["task"]
# Do research, then write findings BACK to the blackboard
board["sections"]["research"] = run_agent("researcher", RESEARCH_PROMPT, topic)
board["status"] = "research_complete"
return board
def writer_agent(board: dict) -> dict:
# Read research from the blackboard (written by research agent)
research = board["sections"]["research"]
# Write the draft, add it to the blackboard
board["sections"]["draft"] = run_agent("writer", WRITER_PROMPT, research)
board["status"] = "draft_complete"
return board
def editor_agent(board: dict) -> dict:
# Read BOTH research and draft — full context from the blackboard
context = f"Research: {board['sections']['research']}\nDraft: {board['sections']['draft']}"
board["feedback"].append(run_agent("editor", EDITOR_PROMPT, context))
board["status"] = "review_complete"
return board
# Orchestrate: each agent reads/writes the same blackboard
blackboard = research_agent(blackboard) # writes research
blackboard = writer_agent(blackboard) # reads research, writes draft
blackboard = editor_agent(blackboard) # reads both, writes feedback
Structured Message Contracts
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
@dataclass
class AgentMessage:
# Who sent it and who should receive it
sender: str # "research-agent"
recipient: str # "analysis-agent"
message_type: str # "research_complete"
# The actual data being passed
payload: dict[str, Any] # {"topic": "...", "findings": [...]}
# Metadata for debugging and tracing
confidence: float = 0.0 # How reliable is this data? (0-1)
tokens_used: int = 0 # Cost tracking
timestamp: str = field( # When was this created?
default_factory=lambda: datetime.now().isoformat()
)
# Usage: research agent sends findings to analysis agent
msg = AgentMessage(
sender="research-agent",
recipient="analysis-agent",
message_type="research_complete",
payload={
"topic": "competitor pricing Q4",
"findings": ["Competitor A: $29/mo", "Competitor B: $49/mo"],
"sources_checked": 12,
},
confidence=0.87, # High confidence — 12 sources verified
tokens_used=4200,
)
# Structured, typed, traceable. The receiving agent knows
# exactly what it got, who sent it, and how reliable it is.
Design a Message Contract
Take your agent team from Lesson 2. Define the message format for each handoff. What fields does the receiving agent need? What metadata helps with debugging?
Handoff: [Agent A] → [Agent B]
Message type: [name]
Required fields: [list them]
Optional fields: [list them]
Format: JSON / structured text / markdown
Communication pattern strengths and risks.
Three Ways Agent Communication Breaks
Agent A returns a paragraph. Agent B tries to parse it as JSON. The system crashes — or worse, silently produces garbage. Fix: Always force structured output with explicit instructions in the system prompt: "Return ONLY valid JSON with these exact keys."
In direct messaging, each agent only sees the previous agent's output. By agent #5 in a chain, the original intent is lost — like a game of telephone. Fix: Pass the original request alongside each handoff, or use a blackboard so every agent has full context.
Agent A fails silently and returns empty data. Agent B processes the empty data without checking. Agent C presents garbage to the user. Fix: Include a status field in every message — "success", "partial", or "failed" — and teach downstream agents to check it first.
Good Communication Is the Whole Game
Multi-agent systems fail at the seams — the handoff points between agents. Invest heavily in communication contracts. Define message formats explicitly. Include metadata for debugging. When your agents communicate cleanly, the whole system becomes predictable, debuggable, and reliable.
The code you've seen in this lesson — direct messaging, broadcast, and blackboard — are the three building blocks. Every multi-agent system you build will use one or a combination of these patterns. Master them and you've mastered the hardest part of agent orchestration.