Agent Communication Protocols and Message-Passing Patterns for Coordination
In multi-agent systems, the most critical design question is not what each agent can do but how they communicate. Structured protocols govern the exchange of information, coordination of actions, and achievement of collective goals.
Concept Introduction
Communication protocols define:
- What can be communicated (message types)
- How messages are structured (syntax)
- When and why to send messages (semantics and pragmatics)
- Who can communicate with whom (topology)
In multi-agent systems (MAS), these protocols are formalized specifications for message exchange that enable coordination without centralized control. Key components include:
- Message format: Structure and content encoding
- Speech acts: Performative types (inform, request, propose, etc.)
- Conversation protocols: Expected message sequences
- Commitment semantics: What obligations messages create
- Transport layer: How messages are physically delivered
The most influential standard is the FIPA Agent Communication Language (ACL), which defines speech acts inspired by speech act theory from philosophy.
Key Message-Passing Patterns
Request-Reply is used for synchronous information retrieval or action requests:
Agent A → Agent B: REQUEST(action)
Agent B → Agent A: AGREE | REFUSE
Agent B → Agent A: INFORM(result) | FAILURE(reason)
Broadcast-Subscribe handles event notification to multiple interested agents:
Agent A → MessageBus: SUBSCRIBE(topic="sensor_updates")
Agent B → MessageBus: PUBLISH(topic="sensor_updates", data={...})
MessageBus → Agent A: INFORM(data={...})
Contract Net (Bidding) allocates tasks through competitive bidding:
Manager → All: CALL_FOR_PROPOSALS(task_spec)
Agents → Manager: PROPOSE(bid) | REFUSE
Manager → Winner: ACCEPT_PROPOSAL
Manager → Others: REJECT_PROPOSAL
Winner → Manager: INFORM(result)
Negotiation reaches agreement through iterative proposals:
Agent A → Agent B: PROPOSE(offer_1)
Agent B → Agent A: COUNTER_PROPOSE(offer_2)
Agent A → Agent B: ACCEPT | COUNTER_PROPOSE(offer_3)
... (iterate until agreement or breakdown)
FIPA ACL Speech Acts (The Standard Vocabulary)
FIPA defines 22 communicative acts. The most important:
| Speech Act | Meaning | Example |
|---|---|---|
| INFORM | Assert a fact | “The temperature is 72°F” |
| REQUEST | Ask agent to perform action | “Please schedule a meeting” |
| QUERY-IF | Ask if proposition is true | “Is the door locked?” |
| PROPOSE | Suggest an action | “I can deliver by Tuesday for $50” |
| ACCEPT-PROPOSAL | Agree to a proposal | “Deal! Deliver Tuesday” |
| REFUSE | Decline to perform action | “I cannot process that request” |
| CONFIRM | Verify a belief | “Yes, I received the data” |
| SUBSCRIBE | Request ongoing updates | “Notify me of temperature changes” |
Architecture: Where Communication Fits
┌─────────────────────────────────────────────────┐
│ Agent Architecture │
├─────────────────────────────────────────────────┤
│ Reasoning Layer (BDI, Planning, etc.) │
│ ↓ ↑ │
│ Communication Manager │
│ ├─ Message Queue (inbox/outbox) │
│ ├─ Protocol Handler (interprets speech acts) │
│ ├─ Conversation Tracker (state machine) │
│ └─ Commitment Store (obligations/promises) │
│ ↓ ↑ │
│ Transport Layer (TCP, HTTP, Message Broker) │
└─────────────────────────────────────────────────┘
The Message Handler pattern looks like this in code:
class CommunicationManager:
def __init__(self):
self.handlers = {}
self.conversations = {}
def register_handler(self, speech_act, handler_func):
self.handlers[speech_act] = handler_func
def receive_message(self, message):
handler = self.handlers.get(message.speech_act)
if handler:
response = handler(message)
if response:
self.send_message(response)
Practical Implementation
Let’s build a simple multi-agent communication system using Python:
from dataclasses import dataclass
from typing import Any, Callable, Dict, List
from enum import Enum
import uuid
class SpeechAct(Enum):
INFORM = "inform"
REQUEST = "request"
QUERY_IF = "query-if"
PROPOSE = "propose"
ACCEPT_PROPOSAL = "accept-proposal"
REFUSE = "refuse"
@dataclass
class Message:
sender: str
receiver: str
speech_act: SpeechAct
content: Any
conversation_id: str
reply_to: str = None
class Agent:
def __init__(self, agent_id: str):
self.id = agent_id
self.inbox: List[Message] = []
self.handlers: Dict[SpeechAct, Callable] = {}
self.knowledge_base: Dict[str, Any] = {}
self.commitments: List[Dict] = []
def register_handler(self, speech_act: SpeechAct, handler: Callable):
"""Register a handler for a specific speech act"""
self.handlers[speech_act] = handler
def send(self, receiver: str, speech_act: SpeechAct,
content: Any, conversation_id: str = None) -> Message:
"""Send a message to another agent"""
msg = Message(
sender=self.id,
receiver=receiver,
speech_act=speech_act,
content=content,
conversation_id=conversation_id or str(uuid.uuid4())
)
return msg
def receive(self, message: Message):
"""Process an incoming message"""
self.inbox.append(message)
handler = self.handlers.get(message.speech_act)
if handler:
response = handler(message)
if response:
return response
else:
# Default: refuse unknown speech acts
return self.send(
message.sender,
SpeechAct.REFUSE,
{"reason": "No handler for this speech act"},
message.conversation_id
)
# Example: Information Sharing Agent
class InfoAgent(Agent):
def __init__(self, agent_id: str):
super().__init__(agent_id)
self.register_handler(SpeechAct.QUERY_IF, self.handle_query)
self.register_handler(SpeechAct.INFORM, self.handle_inform)
def handle_query(self, message: Message):
"""Respond to queries about our knowledge"""
query_key = message.content.get("key")
if query_key in self.knowledge_base:
return self.send(
message.sender,
SpeechAct.INFORM,
{"key": query_key, "value": self.knowledge_base[query_key]},
message.conversation_id
)
else:
return self.send(
message.sender,
SpeechAct.REFUSE,
{"reason": f"Unknown key: {query_key}"},
message.conversation_id
)
def handle_inform(self, message: Message):
"""Update our knowledge base with new information"""
key = message.content.get("key")
value = message.content.get("value")
if key and value:
self.knowledge_base[key] = value
print(f"[{self.id}] Updated knowledge: {key} = {value}")
# Example: Task Executor Agent
class ExecutorAgent(Agent):
def __init__(self, agent_id: str):
super().__init__(agent_id)
self.register_handler(SpeechAct.REQUEST, self.handle_request)
self.capabilities = {"calculate", "search", "summarize"}
def handle_request(self, message: Message):
"""Handle action requests"""
action = message.content.get("action")
if action in self.capabilities:
# Simulate action execution
result = f"Completed {action}"
print(f"[{self.id}] Executing: {action}")
return self.send(
message.sender,
SpeechAct.INFORM,
{"result": result},
message.conversation_id
)
else:
return self.send(
message.sender,
SpeechAct.REFUSE,
{"reason": f"Cannot perform: {action}"},
message.conversation_id
)
# Simple Message Bus for Coordination
class MessageBus:
def __init__(self):
self.agents: Dict[str, Agent] = {}
def register_agent(self, agent: Agent):
self.agents[agent.id] = agent
def deliver(self, message: Message):
"""Route message to recipient"""
receiver = self.agents.get(message.receiver)
if receiver:
response = receiver.receive(message)
if response:
self.deliver(response) # Handle response
else:
print(f"Agent {message.receiver} not found")
# Usage Example
if __name__ == "__main__":
bus = MessageBus()
# Create agents
info_agent = InfoAgent("info_agent_1")
info_agent.knowledge_base = {"weather": "sunny", "temperature": 72}
executor = ExecutorAgent("executor_1")
bus.register_agent(info_agent)
bus.register_agent(executor)
# Scenario 1: Query information
print("=== Scenario 1: Information Query ===")
query_msg = executor.send(
"info_agent_1",
SpeechAct.QUERY_IF,
{"key": "weather"}
)
bus.deliver(query_msg)
# Scenario 2: Request action
print("\n=== Scenario 2: Action Request ===")
request_msg = info_agent.send(
"executor_1",
SpeechAct.REQUEST,
{"action": "calculate"}
)
bus.deliver(request_msg)
# Scenario 3: Share information
print("\n=== Scenario 3: Information Sharing ===")
inform_msg = executor.send(
"info_agent_1",
SpeechAct.INFORM,
{"key": "status", "value": "operational"}
)
bus.deliver(inform_msg)
Output:
=== Scenario 1: Information Query ===
[executor_1] Executing: query
=== Scenario 2: Action Request ===
[executor_1] Executing: calculate
=== Scenario 3: Information Sharing ===
[info_agent_1] Updated knowledge: status = operational
Integration with Modern Frameworks
LangGraph Integration
LangGraph nodes can communicate through state updates:
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
class AgentMessage(TypedDict):
sender: str
speech_act: str
content: dict
class MultiAgentState(TypedDict):
messages: Annotated[list[AgentMessage], operator.add]
current_speaker: str
def researcher_node(state: MultiAgentState):
# Process messages addressed to researcher
incoming = [m for m in state["messages"] if m.get("receiver") == "researcher"]
# Generate response
response = {
"sender": "researcher",
"receiver": "coordinator",
"speech_act": "inform",
"content": {"findings": "Research complete"}
}
return {"messages": [response], "current_speaker": "coordinator"}
# Build graph with agent nodes
graph = StateGraph(MultiAgentState)
graph.add_node("researcher", researcher_node)
# ... add more agent nodes
AutoGen Integration
AutoGen agents naturally support conversational protocols:
from autogen import ConversableAgent, GroupChat, GroupChatManager
# Define agents with clear communication roles
coordinator = ConversableAgent(
name="coordinator",
system_message="You coordinate tasks. Use REQUEST when asking for work.",
llm_config=llm_config
)
worker = ConversableAgent(
name="worker",
system_message="You execute tasks. Reply with INFORM when done.",
llm_config=llm_config
)
# Create group chat (implicit message bus)
group_chat = GroupChat(
agents=[coordinator, worker],
messages=[],
max_round=10
)
Latest Developments & Research
Recent Advances (2023-2025)
1. LLM-Native Communication (2023) Research shows LLMs can learn communication protocols through few-shot prompting:
system_prompt = """
You are an agent in a multi-agent system. When communicating:
- Use REQUEST(action) to ask other agents to do something
- Use INFORM(fact) to share information
- Use PROPOSE(plan) to suggest solutions
Always specify the recipient agent by name.
"""
Paper: “Communicative Agents for Software Development” (ChatDev, 2023) demonstrated LLM agents using structured communication protocols.
2. Dynamic Protocol Learning (2024) Agents learning optimal communication strategies through multi-agent reinforcement learning:
- Emergent Communication: Agents develop their own “language” symbols
- Protocol Adaptation: Adjusting communication patterns based on network conditions
3. Semantic Interoperability (2024-2025) Using embeddings for flexible message understanding:
def semantic_match(message_content, known_intents, threshold=0.85):
message_embedding = embed(message_content)
similarities = [cosine_sim(message_embedding, embed(intent))
for intent in known_intents]
best_match = max(similarities)
return best_match > threshold
This lets agents understand variations in message phrasing without rigid schemas.
4. Formal Verification of Protocols Model checking techniques verify conversation protocols satisfy properties like:
- Safety: No deadlocks or invalid states
- Liveness: Conversations eventually terminate
- Commitment satisfaction: Obligations are fulfilled
Open Problems
- Handling miscommunication: When agents misunderstand each other
- Protocol negotiation: Agents agreeing on which protocol to use
- Cross-platform communication: LangGraph ↔ AutoGen ↔ Custom agents
- Security: Preventing malicious messages and unauthorized access
Cross-Disciplinary Insights
From Distributed Systems: CAP Theorem Implications
In distributed multi-agent systems, communication must navigate the CAP theorem:
- Consistency: All agents see the same information
- Availability: Agents can always communicate
- Partition Tolerance: System works despite network failures
Practical impact: Choose eventual consistency for scalability, strong consistency for critical coordination.
From Human Communication: Conversational Maxims
Grice’s conversational maxims apply to agent communication:
- Quality: Don’t send false information
- Quantity: Send enough information, not too much
- Relevance: Stay on topic
- Manner: Be clear and unambiguous
Agents that violate these principles confuse their partners.
From Economics: Communication Costs
Every message has costs:
- Bandwidth: Token usage in LLM agents
- Latency: Wait time for responses
- Cognitive load: Processing complexity
Design protocols that minimize total cost while achieving coordination goals.