The a2a Agent to Agent Communication Example That Actually Works in Production
We spent four months in 2025 building a multi-agent system for a logistics client in Rotterdam. The architecture looked great on paper. Three specialized agents, each with its own model, its own tools, its own memory. They were supposed to talk to each other, hand off tasks, and resolve disputes about shipping delays.
The system collapsed in the first hour of load testing. The agents were talking, but they were speaking different dialects. One agent expected JSON with shipment_id, the other wanted orderReference. One used function calling, the other just dumped text. What I learned from that disaster is that agent-to-agent communication isn't a technical problem. It's a protocol problem.
A2A (Agent-to-Agent) communication is a standardized protocol that lets autonomous AI agents discover each other, exchange capabilities, and delegate tasks without a human in the middle. Think of it as HTTP for AI agents. Not the content — the contract.
This article walks through a concrete a2a agent to agent communication example, shows you how to wire it up with MCP (Model Context Protocol), and explains why agent discovery is fundamentally different from tool discovery. By the end, you'll know exactly what to build and what to avoid.
Why A2A Matters Right Now (August 2026, and It's Messy)
Here's the state of things. Every vendor has an agent. OpenAI has Operator. Anthropic has Claude with computer use. Google has Gemini agents. Microsoft has Copilot agents. And none of them talk to each other.
The Agentic Web isn't a metaphor anymore. It's a pile of APIs that don't interoperate. I talked to a fintech CTO in July who said their "agent strategy" was building a custom router that translates between vendors. That's the XML SOAP problem all over again, but with LLMs.
Google launched the A2A protocol in April 2025, then donated it to the Linux Foundation in June 2025 Google Developers Blog. Since then, there's been real momentum. But adoption is still early. Most teams I meet are either building single-agent apps (which don't need A2A) or multi-agent systems where all agents share the same codebase (which also don't need A2A). The pain shows up when agents are built by different teams, run on different infrastructure, or use different model vendors.
That's the gap this article fills.
What A2A Actually Defines (and What It Doesn't)
A2A is a specification, not a framework. The Linux Foundation's A2A spec defines four core concepts:
- Agent Card — a JSON file that advertises an agent's identity, capabilities, and endpoint. This is the agent's public profile.
- Task — a unit of work. An agent sends a task to another agent, gets back a task ID, then polls or subscribes for status updates.
- Message — the content exchanged during a task (prompts, file parts, structured data).
- Artifact — the outputs produced when a task completes.
The protocol handles authentication, streaming, and push notifications via Webhooks. It doesn't define how the agent thinks, what tools it uses, or what model powers it. That's deliberate. A2A is a boundary protocol. It sits between agents, not inside them.
Here's what a minimal Agent Card looks like:
json
{
"name": "Invoice Extractor",
"description": "Extracts structured invoice data from PDF files",
"url": "https://agents.sivaro.com/invoice-extractor",
"version": "2.3.0",
"capabilities": {
"streaming": true,
"pushNotifications": true
},
"skills": [
{
"id": "extract_invoice",
"name": "Extract Invoice Fields",
"description": "Parses an invoice PDF and returns vendor, amount, and line items"
}
],
"security": {
"authentication": "bearer-token",
"oauthScopes": ["tasks:write", "tasks:read"]
}
}
That card gets published at /.well-known/agent.json on the agent's host. Other agents fetch it, parse the skills, and decide whether to delegate work.
Now, the part that confuses everyone.
A2A and MCP Integration with LLM Agents: They're Complementary, Not Competing
There's a lot of nonsense online about A2A versus MCP. People ask which one wins. They're wrong. They answer different questions.
MCP answers: "How does an agent use a tool?"
A2A answers: "How does an agent delegate to another agent?"
Anthropic open-sourced MCP in November 2024 Anthropic News. It grew fast. By mid-2026, it's the de facto standard for connecting LLMs to data sources and APIs. Every major framework supports it. We use it at SIVARO for every agent we ship.
But MCP has a limitation. A tool is a function. It has inputs, outputs, and it's deterministic (or should be). An agent is a reasoning system. It can make judgment calls, handle ambiguity, and loop until it gets context. That's not a tool.
A2A is the layer above MCP. Your agent uses MCP to access internal tools (databases, APIs, files). It uses A2A to talk to other agents that have their own MCP stacks. The integration pattern looks like this:
┌─────────────────────────────────────────────┐
│ Orchestrator Agent │
│ │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ LLM Core │◄──►│ A2A Client │ │
│ └──────────┘ └──────────────────────┘ │
│ │ │
│ ┌─────▼──────┐ │
│ │ MCP Client │ │
│ └────────────┘ │
└─────────────────────────────────────────────┘
│ │
│ A2A │ A2A
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Specialist A │ │ Specialist B │
│ (own MCP) │ │ (own MCP) │
└───────────────┘ └───────────────┘
In practice, the orchestrator agent runs on a framework like LangGraph or CrewAI. It has an MCP client for internal tools (search, database, filesystem). It also has an A2A client that can send tasks to specialist agents. Each specialist agent runs its own MCP server stack — it's a fully independent agent.
We built a working example of this pattern for a healthcare claims processing system. The orchestrator handles patient intake. When it hits a claim that needs adjudication, it sends an A2A task to the "Claims Adjudicator" agent. That agent uses its own MCP tools to check policy documents and prior authorization records. Then it sends the result back.
Here's how the orchestrator sends that task:
python
from a2a import A2AClient, Task, Message, TextPart
async def delegate_claims_adjudication(claim_data: dict) -> dict:
client = A2AClient("https://agents.healthcare-demo.com/claims-adjudicator")
task = Task(
message=Message(
parts=[
TextPart(text=f"Adjudicate this claim: {json.dumps(claim_data)}")
]
)
)
# Send the task and wait for completion
result = await client.send_task(task)
while result.status == "working":
await asyncio.sleep(2)
result = await client.get_task(result.id)
if result.status == "completed":
# Artifacts contain the structured adjudication output
return json.loads(result.artifacts[0].content)
else:
raise Exception(f"Task failed: {result.status.message}")
That's the a2a and mcp integration with llm agents pattern. One agent, many capabilities. Internal tools through MCP. External delegation through A2A.
The Confusion: A2A Agent Discovery vs MCP Tool Discovery
Let me clear up a distinction that trips up every team I've consulted with.
MCP tool discovery is about finding functions. Your agent queries an MCP server, gets a list of tools with schemas, and decides which to call. The discovery is scoped to one server. It's like walking into a kitchen and reading the menu.
A2A agent discovery is about finding capabilities across a network of agents. Your agent fetches an Agent Card, reads the skills, and decides whether this agent can handle a complex task. The discovery is scoped to the open web (or your internal agent network). It's like Yelp for agents — you're reading reviews and choosing a restaurant based on what it can do, not just what's on the menu.
The critical difference: MCP discovery happens per-session, per-needed-tool. A2A discovery happens once, and then you maintain a relationship. You might check an agent's card weekly to see if its capabilities changed.
Here's a practical example that makes it concrete.
Say you're building a customer support system. You have an MCP server that exposes a get_order_status() tool. Your main agent calls that — instant, deterministic, done.
Now your main agent encounters a refund request. It doesn't have a refund tool. But there's an agent on your network called "Refund Processor" that handles refunds end-to-end, including checking compliance rules and triggering bank transfers. Your agent needs to discover that this agent exists, understand what it can do, and delegate.
You can't do that with MCP tool discovery, because the refund agent isn't a tool. It's an autonomous system with its own decision-making. If you tried to shoehorn it into MCP, you'd have to expose every possible input and output as a function schema. That's brittle and it fails the moment the refund agent changes its internal workflow.
A2A agent discovery vs MCP tool discovery isn't a competition. It's a division of labor. MCP for functions. A2A for agents.
A Complete Walkthrough: The Booking Negotiation Example
Let me give you a real a2a agent to agent communication example that I've actually built. This is from a travel tech client we worked with in Q1 2026. The use case: a corporate travel booking system where a "Travel Coordinator" agent negotiates with multiple "Hotel Agent" instances.
The problem was that each hotel chain had its own API, its own booking flow, and its own cancellation logic. Building one MCP tool per hotel was a maintenance nightmare — the chains changed their APIs quarterly. Instead, we gave each hotel chain an A2A-compliant agent wrapper. The wrapper handled the proprietary API internally and exposed a standard A2A interface.
The coordinator agent discovers available hotels via their Agent Cards, then negotiates pricing and availability sequentially.
Here's the discovery and negotiation flow:
python
import httpx
import json
async def find_hotel_agents(location: str) -> list[dict]:
"""Discover A2A agents that can handle hotel booking for a location."""
# In production, you'd have an agent registry.
# This is a simplified registry lookup.
registry_url = "https://agent-registry.internal/agents"
async with httpx.AsyncClient() as client:
response = await client.post(registry_url, json={
"query": f"hotel booking {location}",
"required_skill": "book_hotel",
"max_results": 5
})
agents = response.json()["agents"]
# Fetch each agent's card to verify capabilities
verified = []
for agent in agents:
card = await client.get(f"{agent['url']}/.well-known/agent.json")
skills = card.json()["skills"]
if any(s["id"] == "book_hotel" for s in skills):
verified.append(agent)
return verified
The negotiation itself is interesting. Each hotel agent exposes a negotiate_price skill. The coordinator sends tasks back and forth, each one adjusting the context. This is where A2A's message protocol shines — it's not just request/response. You can have a multi-turn conversation where each turn is a task update.
python
async def negotiate_with_hotel(hotel_agent: dict, requirements: dict) -> dict:
client = A2AClient(hotel_agent["url"])
# Initial ask
negotiation_task = Task(
message=Message(parts=[
TextPart(text=f"Propose best available rate for: {json.dumps(requirements)}")
])
)
result = await client.send_task(negotiation_task)
# Loop through counter-offers
for round_num in range(3): # Max 3 rounds of negotiation
response_text = result.artifacts[0].content
if "accepted" in response_text or "final_offer" in response_text:
break
# Send a counter-offer
counter = Task(
message=Message(parts=[
TextPart(text=f"Counter-offer: We'll accept if you reduce by 12%")
]),
parent_task_id=negotiation_task.id # Threading the conversation
)
result = await client.send_task(counter)
return json.loads(result.artifacts[0].content)
The parent_task_id field is crucial. It lets agents maintain conversational context across turns, even when the underlying LLMs are stateless. This is something I don't see covered in most A2A tutorials. Without threading, each negotiation round feels like a fresh conversation to the hotel agent, and you lose negotiation leverage.
We measured the results of this system. The coordinator agent successfully negotiated rates 31% below the quoted public rate, compared to the 9% discount a human travel manager typically achieved. The system processed 2,400 bookings in its first week of production without a single protocol failure. The failures happened when a hotel's Agent Card was out of sync with its actual capabilities — which brings me to the operational lessons.
What Breaks in Real A2A Deployments (and How to Fix It)
Version skew. We hit this one hard. The Rotterdam logistics system failed because one agent had v1.2 of the protocol and another had v1.4. They sent incompatible message formats. The fix wasn't technical — it was organizational. We required every agent to publish its protocol version in its Agent Card, and we wrote a compatibility checker that ran in CI/CD. If you're deploying agents that live longer than two weeks, you need this.
Agent Card rot. If an agent's skills change but its card doesn't, you get silent failures. The negotiating agent in our travel system called a hotel agent's book_hotel skill only to find it had been renamed to create_reservation. We solved this with a weekly card re-validation job. Every Friday, the orchestrator re-fetched all known Agent Cards and alerted on any diffs.
Authentication sprawl. Every agent has a different auth scheme. Some use bearer tokens, some use mTLS, some use OAuth2. Your A2A client needs to handle all of them. We built a middleware layer that abstracts auth, but it's a lot of work. The A2A spec covers authentication but the implementation burden is on you.
Latency blindness. A2A is chatty. Each task turn involves HTTP round trips. If your agent does 10 rounds of negotiation, that's 10 sequential network calls. Our travel system had an end-to-end booking latency of 8-14 seconds. Users noticed. We added timeouts and reduced negotiation rounds to 3 maximum.
State management. A2A tasks are meant to be long-running. But if your agent crashes mid-task, there's no built-in recovery. We added idempotency keys to every task and made the agents resume from the last checkpoint. This isn't in the spec — you have to build it yourself.
Building Your First A2A Agent: The Practical Path
If you want to build an A2A agent today, here's the fastest path I know.
Start with an MCP server. Get your agent talking to your tools first. A2A without MCP is just a fancy REST API. The protocol's value is letting agents combine their internal tools with external delegation.
Then install the A2A SDK. Google maintains official SDKs in Python, JavaScript, and Java. The Python SDK is the most mature.
Here's a minimal A2A server using the Python SDK:
python
from a2a import A2AServer, Agent, Task, Artifact
from a2a.types import TaskStatus
app = A2AServer()
@app.agent()
class InvoiceAnalyzer(Agent):
name = "Invoice Analyzer"
description = "Analyzes invoice PDFs and returns structured data"
async def handle_task(self, task: Task) -> Task:
# Parse the incoming message for the invoice text or file
invoice_text = task.message.parts[0].text
# Run your existing MCP-backed analysis here
analysis = await self.mcp_call("analyze_invoice", invoice_text)
return Task(
status=TaskStatus.COMPLETED,
artifacts=[Artifact(content=json.dumps(analysis))]
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8001)
That's it. That's a functional A2A agent. It accepts tasks, does work, returns artifacts. The server automatically exposes the Agent Card at /.well-known/agent.json.
The harder part is building the orchestrator client that discovers and manages multiple such agents. I'd suggest starting with the A2A Samples repository — it has working multi-agent examples. We used those as our starting point and then customized for production concerns like retries and circuit breakers.
When NOT to Use A2A
I need to be honest about the trade-offs. Most people using A2A right now shouldn't be.
Single-agent applications. If you have one LLM with tools, you don't need A2A. MCP is enough.
Monolithic multi-agent systems. If all your agents live in the same codebase, use the same framework, and are deployed together, internal function calls are simpler than A2A. We wrote a knowledge base search system in June where all agents share a Postgres database. We use direct function calls, not A2A.
High-frequency, low-context calls. A2A has protocol overhead. Each task is an HTTP request, a status check, and an artifact fetch. If your "task" takes 50 milliseconds, A2A will add 90 milliseconds of overhead. Not worth it.
A2A earns its keep when you have agents that are independently developed, deployed, and versioned. When different teams own different agents. When you need to swap out a specialist agent for a new one without rewriting the orchestrator. That's the sweet spot.
The Road Ahead for A2A
The Linux Foundation's stewardship is a good sign. Standardization is moving. But the coordination problem remains — for A2A to reach its full potential, you need agents from different vendors to interoperate. That means OpenAI agents talking to Google agents, which means underlying models with different safety policies and different terms of service.
We're not there yet. In the meantime, the protocol is genuinely useful inside an organization. Our internal agent mesh at SIVARO uses A2A across 12 different agents. It's given us the ability to redesign one agent without breaking the system. That's the real promise.
The last piece of advice: start small. Pick one workflow where a single specialist agent makes sense. Wrap it in A2A. Get it talking to your existing orchestrator. Measure the latency, measure the failure rate, measure the maintainability. Then expand. Agent-to-agent is a marathon, not a sprint, and most teams fail by trying to build the whole mesh in week one.
FAQ: A2A Agent Communication, Answered Directly
Q: What is an a2a agent to agent communication example in simple terms?
A: Imagine your main booking agent needs to check hotel availability. Instead of calling a hotel API directly (which couples your agent to that hotel), it sends a task to a hotel-specific agent via HTTP. That hotel agent figures out the API, checks availability, and sends back a structured result. That task exchange is A2A.
Q: Is A2A better than MCP?
A: No. They fill different gaps. MCP connects an agent to tools and data. A2A connects agents to other agents. Use both. The strongest systems I've seen use MCP for internal tooling and A2A for cross-agent delegation.
Q: Can A2A agents use different LLM providers?
A: Yes, that's the whole point. The protocol doesn't care what model powers the agent. Your orchestrator could use GPT-5o while the specialist agent uses Claude Opus or a smaller custom model. As long as they both speak A2A, they interoperate.
Q: How does A2A handle authentication between agents?
A: The spec supports multiple auth methods including bearer tokens and OAuth2. You can also use mTLS for service-to-service auth. In practice, you'll need an auth middleware to normalize across agents, since different agents will use different schemes.
Q: What's the difference between agent discovery and tool discovery?
A: Tool discovery (MCP) finds functions with known schemas within a single server. Agent discovery (A2A) finds entire agents with complex capabilities across a network. Tool discovery is like checking if a function exists. Agent discovery is like checking if a partner can handle a project.
Q: What language should I use to build an A2A agent?
A: Python for server-side agents. JavaScript for edge or lightweight agents. The official SDKs are solid. We use Python for all our production agents because the MCP ecosystem is more mature there.
Q: How should I handle timeouts for long-running A2A tasks?
A: Don't use a single HTTP timeout. The protocol supports task status polling and webhook notifications. Set a reasonable timeout per HTTP call (2-5 seconds) and let the task run longer (minutes to hours) by polling or subscribing via webhook.
Q: Does A2A work with legacy systems that don't have agent wrappers?
A: Yes. You'll need to build a wrapper or adapter that exposes the legacy system as an A2A agent. We've done this for COBOL-based claims systems. The adapter handles the protocol and translates inbound A2A tasks into legacy API calls.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.