Engineering Notes

Engineering Notes

Thoughts and Ideas on AI by Muthukrishnan

Agent Communication Protocols and Message-Passing Patterns for Coordination

07 Nov 2025

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:

In multi-agent systems (MAS), these protocols are formalized specifications for message exchange that enable coordination without centralized control. Key components include:

  1. Message format: Structure and content encoding
  2. Speech acts: Performative types (inform, request, propose, etc.)
  3. Conversation protocols: Expected message sequences
  4. Commitment semantics: What obligations messages create
  5. 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.

Historical & Theoretical Context

Agent communication protocols emerged from three parallel traditions.

Speech Act Theory (1960s–1970s): Philosopher J.L. Austin and John Searle proposed that language doesn’t just describe reality: it performs actions. Saying “I promise to pay you” creates a commitment. This insight became foundational for agent communication.

Distributed AI (1980s): As researchers built systems with multiple problem-solving agents, they needed structured ways for agents to negotiate, share information, and coordinate plans. Early systems like DVMT (Distributed Vehicle Monitoring Testbed) and Contract Net Protocol pioneered message-passing approaches.

KQML and FIPA ACL (1990s): The Knowledge Query and Manipulation Language (KQML) and later FIPA ACL standardized agent communication. FIPA (Foundation for Intelligent Physical Agents), established in 1996, created the most widely adopted specifications.

Agent communication protocols rest on three principles:

  1. Autonomy: Agents decide independently how to respond to messages
  2. Intentionality: Messages express mental attitudes (beliefs, desires, intentions)
  3. Social conventions: Communication follows shared norms and creates commitments

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 ActMeaningExample
INFORMAssert a fact“The temperature is 72°F”
REQUESTAsk agent to perform action“Please schedule a meeting”
QUERY-IFAsk if proposition is true“Is the door locked?”
PROPOSESuggest an action“I can deliver by Tuesday for $50”
ACCEPT-PROPOSALAgree to a proposal“Deal! Deliver Tuesday”
REFUSEDecline to perform action“I cannot process that request”
CONFIRMVerify a belief“Yes, I received the data”
SUBSCRIBERequest 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:

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:

Open Problems

  1. Handling miscommunication: When agents misunderstand each other
  2. Protocol negotiation: Agents agreeing on which protocol to use
  3. Cross-platform communication: LangGraph ↔ AutoGen ↔ Custom agents
  4. 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:

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:

  1. Quality: Don’t send false information
  2. Quantity: Send enough information, not too much
  3. Relevance: Stay on topic
  4. Manner: Be clear and unambiguous

Agents that violate these principles confuse their partners.

From Economics: Communication Costs

Every message has costs:

Design protocols that minimize total cost while achieving coordination goals.

Daily Challenge: Build a Contract Net Protocol

Objective: Implement a simple task allocation system using Contract Net Protocol.

Scenario: You have 3 worker agents with different costs and capabilities. A manager agent needs to allocate a task.

Tasks (30 minutes):

  1. Extend the base Agent class to create:

    • ManagerAgent: Sends CFP, evaluates bids, awards contract
    • WorkerAgent: Receives CFP, decides whether to bid, generates bid
  2. Implement the protocol:

    Manager → Workers: CALL_FOR_PROPOSALS(task_description, deadline)
    Workers → Manager: PROPOSE(cost, estimated_time) | REFUSE
    Manager → Winner: ACCEPT_PROPOSAL
    Manager → Others: REJECT_PROPOSAL
    Winner → Manager: INFORM(result)
    
  3. Selection criteria: Lowest cost, but must meet deadline

  4. Test scenario:

    • Task: “Analyze 100 documents”
    • Worker1: Fast but expensive ($100, 1 hour)
    • Worker2: Slow but cheap ($30, 5 hours)
    • Worker3: Balanced ($60, 2 hours)
    • Deadline: 3 hours

Bonus: Handle a worker refusing after accepting (breach of commitment). How does the system recover?

Starter Code Pattern:

class ManagerAgent(Agent):
    def initiate_contract_net(self, task, workers, deadline):
        conversation_id = str(uuid.uuid4())
        # Send CFP to all workers
        # Collect proposals
        # Select winner
        # Award contract
        pass

class WorkerAgent(Agent):
    def __init__(self, agent_id, capabilities, cost, speed):
        super().__init__(agent_id)
        self.capabilities = capabilities
        self.cost = cost
        self.speed = speed
        self.register_handler(SpeechAct.CALL_FOR_PROPOSALS, self.handle_cfp)
        self.register_handler(SpeechAct.ACCEPT_PROPOSAL, self.handle_accept)

    def handle_cfp(self, message):
        # Decide whether to bid
        # Generate proposal or refuse
        pass

References & Further Reading

Foundational Papers

Modern Research

Practical Resources

Standards & Specifications

Open Source Projects


● Intelligence at Every Action

AI Native
Project Management

Stop using tools that bolt on AI as an afterthought. Jovis is built AI-first — smart routing, proactive monitoring, and intelligent workflows from the ground up.