ARTICLE · 14 MIN READ · MARCH 02, 2026
Chapter 15: Inter-Agent Communication (A2A)
MCP connects agents to tools. A2A connects agents to agents. The Agent2Agent protocol is the missing standard that lets any AI agent — regardless of framework — collaborate with any other.
The Missing Link in Multi-Agent Systems
Interoperability: The ability of different systems — built by different companies, using different technologies — to work together without custom translation layers. USB is an interoperable standard. HTTP is an interoperable standard. A2A aims to be the interoperable standard for agent communication.
Protocol: A set of agreed-upon rules for communication. (Covered in Chapter 10 — MCP.) A2A is a protocol specifically for agent-to-agent communication over HTTP.
JSON-RPC 2.0: A protocol for calling functions remotely using JSON messages. Format: {"jsonrpc":"2.0","method":"sendTask","params":{...},"id":1}. A2A uses JSON-RPC 2.0 as its message format over HTTP/HTTPS.
Server-Sent Events (SSE): A web standard where a server maintains a persistent HTTP connection and pushes data to the client over time, without the client needing to make repeated requests. Used in A2A for streaming updates from agent to agent.
mTLS (Mutual TLS): A security protocol where BOTH sides of a connection authenticate themselves — not just the server proving its identity to the client, but the client also proving its identity to the server. Prevents unauthorized agents from connecting to your A2A server.
Opaque system: A system where the internal implementation is hidden from the caller. An A2A server is "opaque" to its client — the client knows what the server can do (from the Agent Card) but not how it does it. This is essential for framework independence.
Webhook: A URL that a server can call to notify another system of an event. In A2A, a client can register a webhook URL and the server will POST to it when a long-running task completes — no need to poll.
In Chapter 7, we built multi-agent systems where multiple agents collaborate. In Chapter 10, we standardized how agents connect to tools via MCP. But there’s a gap that neither pattern addresses:
What happens when an agent built in Google ADK needs to collaborate with an agent built in LangGraph, which needs to call an agent built in CrewAI?
Without a standard, each pair of collaborating agents needs a custom integration — agent A knows how to call agent B using B’s specific API format, agent B knows how to call C using C’s format, and so on. This is the same integration explosion problem that MCP solved for tools — but at the agent layer.
The table below shows why the problem gets serious fast:
| Agents | Framework pairs | Custom integrations needed (without A2A) |
|---|---|---|
| 5 | 10 pairs | 10 integrations |
| 10 | 45 pairs | 45 integrations |
| 50 | 1,225 pairs | 1,225 integrations |
Agent2Agent (A2A) is Google’s open protocol, launched in April 2025, that solves this. It defines a universal communication standard for agents — any agent that speaks A2A can work with any other A2A-compliant agent, regardless of whether they’re built with ADK, LangGraph, CrewAI, Azure AI Foundry, or any other framework.
A2A vs MCP: Two Complementary Protocols
Before going deeper into A2A, the most important conceptual clarification:
The key insight: MCP and A2A are complementary. A single agent might use MCP to connect to its tools (databases, APIs, file systems) AND use A2A to collaborate with other agents. They solve different problems at different levels of the stack.
The Three Core Actors
Every A2A interaction involves three roles:
Why “opaque”? The client agent doesn’t know whether the server agent uses GPT-4, Gemini, or Llama. It doesn’t know if the server uses LangGraph, CrewAI, or a custom implementation. All it knows is the Agent Card — what tasks the server can handle, what inputs it accepts, and what outputs it produces. This opaqueness is the key that makes framework independence possible.
The Agent Card: A Digital Identity
The Agent Card is the cornerstone of A2A. It’s a JSON file that describes everything another agent needs to know to interact with yours — without you needing to share your internal implementation.
{
"name": "WeatherBot",
"description": "Provides weather forecasts",
"url": "http://weather.example.com/a2a",
"version": "1.0.0",
"capabilities": {
"streaming": true,
"pushNotifications": false,
"stateTransitionHistory": true
},
"authentication": {
"schemes": ["apiKey"]
},
"skills": [
{
"id": "get_current_weather",
"name": "Get Current Weather",
"description": "Real-time weather for any location",
"inputModes": ["text"],
"outputModes": ["text"],
"examples": ["What's the weather in Paris?"],
"tags": ["weather", "current", "real-time"]
}
]
} Where is the Agent Card served? By convention, agents serve their Agent Card at /.well-known/agent.json on their domain. This is the Well-Known URI discovery method — any A2A client can find your agent’s capabilities by fetching https://your-agent-domain.com/.well-known/agent.json. No prior coordination needed.
The Four Interaction Mechanisms
A2A supports four distinct ways for agents to communicate, each suited to different latency and complexity requirements:
The Task Lifecycle
Every piece of work in A2A is a Task — an asynchronous unit of work with a unique ID and a defined state machine:
graph LR
SUB([submitted]) --> WRK[working]
WRK -->|needs more info| INP[input-required]
INP -->|client provides info| WRK
WRK --> COMP([completed])
WRK --> FAIL([failed])
WRK --> CANC([cancelled])
style SUB fill:#141b2d,stroke:#2698ba,color:#e0e0e0
style WRK fill:#141b2d,stroke:#e6a817,color:#e0e0e0
style INP fill:#141b2d,stroke:#c97af2,color:#e0e0e0
style COMP fill:#141b2d,stroke:#4fc97e,color:#e0e0e0
style FAIL fill:#141b2d,stroke:#ff6b6b,color:#e0e0e0
style CANC fill:#141b2d,stroke:#4a5a6a,color:#e0e0e0
The input-required state is particularly important for multi-turn interactions. A remote agent might partially complete a task, determine it needs more information from the client, and pause — waiting for the client to provide the missing data before continuing. This enables rich back-and-forth collaboration between agents, not just one-shot requests.
The Code: Building an A2A Calendar Agent
The A2A samples repository provides a complete example of a calendar agent built with ADK that exposes itself as an A2A server. Let’s walk through the key components.
Part 1: The ADK Agent with Calendar Tools
import datetime
from google.adk.agents import LlmAgent
from google.adk.tools.google_api_tool import CalendarToolset
async def create_agent(client_id, client_secret) -> LlmAgent:
"""Constructs the ADK calendar agent with Google Calendar access."""
# Initialize the Google Calendar tool with OAuth credentials
toolset = CalendarToolset(client_id=client_id, client_secret=client_secret)
return LlmAgent(
model = 'gemini-2.0-flash-001',
name = 'calendar_agent',
description = "An agent that can help manage a user's calendar",
instruction = f"""
You are an agent that can help manage a user's calendar.
Users will request information about their calendar or ask to make changes.
Use the provided tools for interacting with the Calendar API.
If not specified, assume the user wants their 'primary' calendar.
When using Calendar API tools, use well-formed RFC3339 timestamps.
Today is {datetime.datetime.now()}.
""",
tools = await toolset.get_tools(),
)
CalendarToolset: A pre-built ADK toolset that wraps the Google Calendar API as MCP-compatible tools. It handles OAuth 2.0 authentication flow, token management, and API call formatting.toolset.get_tools()returns a list of tool objects that the agent can call to read/write calendar data.
f"Today is {datetime.datetime.now()}"in the instruction: This is dynamic instruction injection — the current date is embedded at agent creation time. Without it, the LLM might calculate “tomorrow” relative to its training cutoff date rather than today. This small detail ensures all date calculations are anchored to real-world time.
Why
async def create_agent()? Thetoolset.get_tools()call requires async execution (it may need to make network calls to fetch the tool schema from the Calendar API). The entire function is markedasyncto enable this. Theawaitkeyword pauses execution untilget_tools()completes before constructing the agent.
Part 2: Defining the Agent Card and Starting the A2A Server
from a2a.types import AgentSkill, AgentCard, AgentCapabilities
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from google.adk.runners import Runner
Why these imports? A2A provides a Python SDK (
a2apackage) that handles the protocol details — JSON-RPC framing, task state management, SSE streaming, etc. Without it, you’d need to implement the full A2A protocol specification manually. The ADKRunnermanages the agent’s execution lifecycle, session management, and tool call routing.
def main(host: str, port: int):
# 1. Define the agent's skill (what it can do)
skill = AgentSkill(
id = 'check_availability',
name = 'Check Availability',
description = "Checks a user's availability for a time using their Google Calendar",
tags = ['calendar'],
examples = ['Am I free from 10am to 11am tomorrow?'],
)
AgentSkillis the fine-grained capability descriptor. While theAgentCarddescribes the agent overall, eachAgentSkilldescribes a specific capability. This enables precise routing: if an orchestrating agent needs to check calendar availability (and not, say, create events), it can specifically request thecheck_availabilityskill. This granularity makes multi-agent systems more precise.
# 2. Define the Agent Card (the agent's public identity)
agent_card = AgentCard(
name = 'Calendar Agent',
description = "An agent that can manage a user's calendar",
url = f'http://{host}:{port}/',
version = '1.0.0',
defaultInputModes = ['text'],
defaultOutputModes = ['text'],
capabilities = AgentCapabilities(streaming=True),
skills = [skill],
)
AgentCardis the agent’s published identity. This is what gets served at/.well-known/agent.json. Any A2A client — regardless of framework — can fetch this URL and immediately understand: what this agent does, where to send requests, what authentication is required, whether streaming is supported, and which specific skills are available. The Agent Card makes the agent self-describing and discoverable without any prior coordination.
# 3. Create the ADK agent with credentials from environment
adk_agent = asyncio.run(create_agent(
client_id = os.getenv('GOOGLE_CLIENT_ID'),
client_secret = os.getenv('GOOGLE_CLIENT_SECRET'),
))
Why
os.getenv()for credentials? Never hardcode API keys or OAuth secrets in source code — they’d be committed to version control and exposed. Environment variables are the standard way to pass secrets to running processes without embedding them in code. In production, these would be set via secrets management services (AWS Secrets Manager, Google Secret Manager, etc.).
# 4. Wire the ADK agent to the A2A execution infrastructure
runner = Runner(
app_name = agent_card.name,
agent = adk_agent,
artifact_service = InMemoryArtifactService(), # stores file outputs
session_service = InMemorySessionService(), # tracks conversation state
memory_service = InMemoryMemoryService(), # long-term memory
)
agent_executor = ADKAgentExecutor(runner, agent_card)
ADKAgentExecutoris the bridge between the A2A protocol layer and the ADK agent. When an A2A task request arrives (a JSON-RPCsendTaskmessage), the executor: (1) extracts the message content, (2) callsrunner.run()with the message, (3) collects the ADK agent’s responses and tool calls, and (4) formats them as A2A protocol responses. This adapter pattern means the ADK agent doesn’t need to know anything about the A2A protocol — the executor handles the translation.
# 5. Create the A2A web application
request_handler = DefaultRequestHandler(
agent_executor = agent_executor,
task_store = InMemoryTaskStore() # tracks task states
)
a2a_app = A2AStarletteApplication(
agent_card = agent_card,
http_handler = request_handler
)
# 6. Add OAuth callback route (needed for Google Calendar auth)
routes = a2a_app.routes()
routes.append(Route(
path = '/authenticate',
methods = ['GET'],
endpoint = handle_auth,
))
# 7. Start the HTTP server
app = Starlette(routes=routes)
uvicorn.run(app, host=host, port=port)
A2AStarletteApplication: Creates a Starlette web application (a Python async web framework) that: (1) serves the Agent Card at/.well-known/agent.json, (2) handles incoming A2A task requests, (3) manages task state, (4) sends streaming responses via SSE whenstreaming: true. The entire A2A HTTP server is encapsulated in this single object.
InMemoryTaskStore: Stores the state of all active tasks (submitted, working, completed, failed) in memory. In production, you’d replace this with a persistent store (database) so task state survives server restarts. For development, in-memory is sufficient.
uvicorn.run(): Starts the ASGI server that will serve your A2A agent over HTTP. Uvicorn is a fast Python async HTTP server. Your agent is now accessible athttp://host:port/— any A2A-compliant client can discover it via/.well-known/agent.jsonand send tasks to/.
The Complete A2A Flow Visualized
graph TD
U([User: "Plan a birthday party for March 15"]) --> CA[Client Agent — Orchestrator]
CA -->|GET /.well-known/agent.json| DISC[Agent Discovery]
DISC -->|Agent Card: capabilities, skills| CA
CA -->|POST /a2a sendTask| CALSERV[Calendar Agent A2A Server]
CALSERV --> ADK[ADK Agent Executor]
ADK -->|tool call| GCAL[Google Calendar API]
GCAL -->|availability data| ADK
ADK -->|SSE streaming| CALSERV
CALSERV -->|incremental results| CA
CA --> RESP([Final response to user])
style CA fill:#141b2d,stroke:#2698ba,color:#e0e0e0
style CALSERV fill:#141b2d,stroke:#4fc97e,color:#e0e0e0
style ADK fill:#141b2d,stroke:#c97af2,color:#e0e0e0
style GCAL fill:#141b2d,stroke:#e6a817,color:#e0e0e0
Security: How A2A Stays Safe
Security is built into A2A at multiple levels:
Mutual TLS (mTLS)
Both the A2A client and server authenticate themselves with TLS certificates. This prevents unauthorized agents from connecting and ensures both sides can trust the other's identity. Essential for enterprise deployments where you don't want random clients calling your agent.
Credential Handling
Authentication credentials (API keys, OAuth tokens) are passed via HTTP Authorization headers — never in the URL or request body. The Agent Card declares which authentication scheme is required, so clients know what to send before making any requests.
Audit Logs
All inter-agent communications are logged — which agent sent what to which agent, when, and what was returned. This creates an immutable audit trail for accountability, debugging, security analysis, and compliance with regulations that require traceability of AI decisions.
Agent Card Security
Even though Agent Cards contain capability descriptions (not secrets), they should be access-controlled. In enterprise environments, not everyone should be able to discover which agents exist and what they can do. Secure discovery endpoints with network restrictions or access tokens.
Practical Applications
Multi-Framework Collaboration
An ADK orchestrator delegates research to a LangGraph specialist, analysis to a CrewAI analyst, and report generation to another ADK agent — all communicating through A2A regardless of framework differences.
Enterprise Workflow Automation
A master agent decomposes a complex business process: collect data → delegate to analyst agent → delegate to compliance agent → delegate to report agent. Each agent operates independently and communicates via A2A tasks.
Dynamic Capability Discovery
An orchestrator agent discovers available specialist agents at runtime by querying a registry of Agent Cards, selects the most appropriate one for each sub-task, and delegates — without any hardcoded agent references.
Cross-Organization Collaboration
Your company's AI agent collaborates with a partner company's AI agent via A2A. Neither side needs to expose internal implementation details — just the Agent Card interface. Inter-company AI integration becomes as easy as an API call.
At a Glance
An open, HTTP-based standard for agent-to-agent communication. A2A defines how agents discover each other (Agent Cards), how they exchange tasks (JSON-RPC 2.0), and how results are returned (sync, polling, streaming, webhooks) — regardless of the framework each agent uses.
Without A2A, integrating N agents from M frameworks requires N×M custom integrations. A2A reduces this to N+M standard implementations. It's the USB standard for agent collaboration — write once, work with any compliant agent.
MCP = how an agent connects to tools and external resources. A2A = how agents connect to each other. Both are needed in production systems: MCP gives individual agents their capabilities; A2A lets capable agents collaborate.
Key Takeaways
-
A2A is the missing standard between MCP and multi-agent systems. MCP connects agents to tools. A2A connects agents to each other. Together, they form a complete ecosystem: agents with tool access (MCP) collaborating with other agents (A2A).
-
The Agent Card is the protocol’s foundation. It makes agents self-describing and discoverable — any A2A client can learn an agent’s capabilities, endpoint, authentication requirements, and skills just by fetching its Agent Card JSON. This is what makes true interoperability possible.
-
Four interaction patterns cover every latency profile. Synchronous for fast tasks, polling for medium tasks, streaming SSE for incremental results, webhooks for overnight tasks. The Agent Card declares which patterns are supported — clients choose accordingly.
-
The
input-requiredstate enables multi-turn agent conversations. A remote agent can pause mid-task, request more information from the client, and resume once the information is provided. This supports complex collaborative workflows, not just one-shot requests. -
A2A is opaque by design. The client agent doesn’t know or care what framework the server agent uses, what LLM it calls, or how it processes requests. This opacity is what makes framework independence real — you can replace the server-side implementation entirely without changing any client.
-
The A2A Python SDK encapsulates all protocol complexity.
A2AStarletteApplication,ADKAgentExecutor,DefaultRequestHandler, andInMemoryTaskStorehandle the JSON-RPC framing, task state machine, SSE streaming, and HTTP routing. You implement the agent logic; the SDK handles the protocol. -
Security must be explicit. Declare authentication requirements in the Agent Card. Use mTLS for production. Log all inter-agent communications. Control who can discover your Agent Card. These aren’t optional features — they’re prerequisites for deploying A2A in enterprise environments.
-
Industry adoption is accelerating. Atlassian, Box, LangChain, MongoDB, Salesforce, SAP, ServiceNow, Microsoft (Azure AI Foundry), Auth0 — the A2A ecosystem is growing fast. Agents you build today to the A2A standard will be composable with agents from any of these platforms.
Enjoy Reading This Article?
Here are some more articles you might like to read next: