The Agent2Agent (A2A) Protocol



Table of Contents
The Agent2Agent (A2A) Protocol
Modern AI systems increasingly consist of multiple specialized agents rather than one monolithic model. A trip-planning assistant might delegate flight search to one agent, hotel negotiation to another, and itinerary formatting to a third. Each of these agents could be built by a different team, on a different framework (LangGraph, Google ADK, CrewAI, a custom stack), and hosted on a different server. For these agents to actually cooperate, they need a shared way to find each other, describe what they can do, exchange tasks, and report progress and results.
The Agent2Agent (A2A) protocol is an open standard built for exactly this problem. It was initiated by Google in 2025 and has since been contributed to the Linux Foundation (now under the Agentic AI Foundation), with backing from Microsoft, Anthropic, AWS, and dozens of other companies. Rather than defining how an agent uses a tool, A2A defines how one agent talks to another agent as a peer, treating the remote agent as an opaque black box that exposes a well-defined interface.
The Problem A2A Solves
Before A2A, connecting two independently built agents meant writing custom, brittle integration code for every pair: bespoke REST endpoints, ad hoc JSON payloads, and no consistent way to know what the other side could actually do or how long a request might take. This breaks down badly at scale because:
- Agents are stateful and long-running. A single request to an agent might take seconds or hours (think "research this topic and write a report"), unlike a typical stateless API call.
- Agents need to negotiate capabilities. One agent might only accept text, another might handle images, structured data, or files, and the client needs a standard way to discover this before sending a request.
- Agents are often opaque by design. A company exposing an agent externally does not want to reveal its internal prompts, tool calls, or model choice. The protocol needs to work across a trust boundary without requiring internal visibility.
- Multi-turn collaboration is the norm, not the exception. An agent frequently needs to ask a clarifying question mid-task, or request human authorization before proceeding, and the conversation needs to resume from where it left off.
A2A's guiding principles reflect these constraints directly: reuse existing, well-understood standards (HTTP, JSON-RPC 2.0, Server-Sent Events, gRPC) instead of inventing new transport mechanics; design async-first for long-running and human-in-the-loop work; stay modality-agnostic (text, files, structured data); and preserve "opaque execution," meaning agents collaborate purely through declared capabilities and exchanged messages, never through shared internal state.
A2A vs MCP: Different Layers of the Same Stack
A2A is frequently discussed alongside the Model Context Protocol (MCP), and the two are complementary rather than competing.
| MCP | A2A | |
|---|---|---|
| Purpose | Connects a model or agent to tools, APIs, and data sources | Connects independent agents to each other |
| Relationship | Vertical: agent to tool | Horizontal: agent to agent (peer to peer) |
| Typical unit of work | A function call with structured input/output | A long-running, stateful Task with a lifecycle |
| Opacity | Tool internals are typically fully described (schemas, parameters) | Remote agent's internal reasoning and tools stay hidden |
A single system commonly uses both: an A2A client agent delegates a task to a remote A2A server agent, and that server agent internally uses MCP to call out to search APIs, databases, or other tools while doing the work. A2A is the "how agents delegate to each other" layer, MCP is the "how an agent uses a specific capability" layer underneath it.
Core Concepts
A2A's data model is defined canonically using Protocol Buffers, with JSON-RPC, gRPC, and HTTP+JSON/REST as concrete transport bindings on top of it. A handful of objects show up everywhere in the protocol:
- A2A Client: the application or agent that initiates a request.
- A2A Server (remote agent): the agent exposing an A2A-compliant endpoint.
- Agent Card: a JSON document describing an agent's identity, skills, supported transports, and security requirements.
- Task: the fundamental, stateful unit of work, identified by a unique ID, with a defined lifecycle.
- Message: one turn of communication (from either the client or the agent), made up of one or more Parts.
- Part: the smallest content unit inside a Message or Artifact, one of text, raw bytes (a file), a URL reference, or structured data.
- Artifact: an output produced by the agent as a result of a task (a document, an image, a JSON blob), also composed of Parts.
- Context: an optional identifier that groups related tasks and messages into a single logical conversation.
The Agent Card
The Agent Card is how a client discovers what an agent can do before ever sending it a task. Servers publish it at a well-known location, typically:
GET /.well-known/agent-card.json HTTP/1.1 Host: agent.example.com
A trimmed example of what comes back:
{
"name": "GeoSpatial Route Planner Agent",
"description": "Provides route planning, traffic analysis, and custom map generation.",
"supportedInterfaces": [
{ "url": "https://georoute-agent.example.com/a2a/v1", "protocolBinding": "JSONRPC", "protocolVersion": "1.0" },
{ "url": "https://georoute-agent.example.com/a2a/grpc", "protocolBinding": "GRPC", "protocolVersion": "1.0" }
],
"version": "1.2.0",
"capabilities": {
"streaming": true,
"pushNotifications": true,
"extendedAgentCard": true
},
"securitySchemes": {
"google": {
"openIdConnectSecurityScheme": {
"openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration"
}
}
},
"defaultInputModes": ["application/json", "text/plain"],
"defaultOutputModes": ["application/json", "image/png"],
"skills": [
{
"id": "route-optimizer-traffic",
"name": "Traffic-Aware Route Optimizer",
"description": "Calculates the optimal driving route considering real-time traffic.",
"tags": ["maps", "routing", "traffic"],
"examples": ["Plan a route from downtown to the airport avoiding tolls."]
}
]
}
A few details matter here. supportedInterfaces lists every transport the agent exposes, in preference order, so a client can pick whichever binding it prefers. capabilities tells the client upfront whether streaming or push notifications are even worth attempting, so it can fail fast or adjust its approach instead of guessing. securitySchemes describes exactly how to authenticate, using the same vocabulary as OpenAPI 3 (API key, HTTP auth, OAuth2, OpenID Connect, or mutual TLS). And skills is what actually lets a client (or a routing layer in a multi-agent system) decide whether this agent is even the right one to call for a given request.
Agent Cards can also be cryptographically signed using JSON Web Signatures, so a client can verify the card was not tampered with in transit and genuinely originates from the claimed provider.
The Task Lifecycle
Every non-trivial piece of work in A2A is represented as a Task, and a task moves through a well-defined set of states:
| State | Meaning |
|---|---|
| SUBMITTED | Task has been acknowledged but processing has not started |
| WORKING | Agent is actively processing |
| INPUT_REQUIRED | Agent needs more information from the client to continue (interrupted, not terminal) |
| AUTH_REQUIRED | Agent needs the client to resolve an authorization requirement (interrupted, not terminal) |
| COMPLETED | Finished successfully (terminal) |
| FAILED | Finished with an error (terminal) |
| CANCELED | Canceled before completion (terminal) |
| REJECTED | Agent decided not to perform the task at all (terminal) |
The distinction between "terminal" and "interrupted" states matters a lot in practice. A task in a terminal state can never accept another message, it is done. A task in INPUT_REQUIRED or AUTH_REQUIRED, on the other hand, is paused and waiting, and the client is expected to send a follow-up message on the same task to unblock it. This is what makes multi-turn conversations, clarifying questions, and human-in-the-loop approval steps all first-class citizens of the protocol instead of something bolted on.
Every Task carries a contextId that groups it with other related tasks and messages, so an agent can maintain conversational continuity across multiple separate task objects that all belong to the same broader interaction.
Sending a Message and Getting a Task Back
The primary operation in A2A is SendMessage (called message:send on the HTTP+JSON/REST binding). A client sends a Message and the agent responds with either a Task (if it needs to track ongoing work) or a direct Message (for a trivial, immediate reply that doesn't warrant task tracking).
POST /message:send HTTP/1.1
Host: agent.example.com
Content-Type: application/a2a+json
Authorization: Bearer token
{
"message": {
"role": "ROLE_USER",
"parts": [{ "text": "What is the weather today?" }],
"messageId": "msg-uuid"
}
}
HTTP/1.1 200 OK
Content-Type: application/a2a+json
{
"task": {
"id": "task-uuid",
"contextId": "context-uuid",
"status": { "state": "TASK_STATE_COMPLETED" },
"artifacts": [{
"artifactId": "artifact-uuid",
"parts": [{ "text": "Today will be sunny with a high of 75F" }]
}]
}
}
Notice that the actual answer comes back as an Artifact, not as a Message. This separation is intentional: Messages are for conversational turns (status updates, clarifying questions, acknowledgments), while Artifacts carry the actual output of the task. Mixing the two would make it hard for a client to know which parts of a response are "the result" versus "commentary about the result."
Multi-turn interaction
When an agent needs more information, it returns the task in INPUT_REQUIRED with a clarifying message attached to the status:
{
"task": {
"id": "task-uuid",
"status": {
"state": "TASK_STATE_INPUT_REQUIRED",
"message": {
"role": "ROLE_AGENT",
"parts": [{ "text": "Where would you like to fly from and to?" }]
}
}
}
}
The client responds by sending another message referencing the same taskId:
{
"message": {
"taskId": "task-uuid",
"role": "ROLE_USER",
"parts": [{ "text": "From San Francisco to New York" }],
"messageId": "msg-2"
}
}
Streaming, Polling, and Push Notifications
Long-running tasks need a way to report progress, and A2A supports three complementary delivery mechanisms, each suited to a different kind of client:
- Polling (
GetTask): the client periodically asks for the current task state. Simple, works everywhere, but has latency and can generate unnecessary load. - Streaming (
SendStreamingMessage/SubscribeToTask): the agent pushesTaskStatusUpdateEventandTaskArtifactUpdateEventobjects over a live connection (Server-Sent Events for the JSON-RPC and HTTP bindings, native server streaming for gRPC) as soon as they happen. Requires the agent to declarecapabilities.streaming: truein its Agent Card. - Push notifications: the agent calls a client-registered webhook via plain HTTP POST whenever the task's state changes. This is the right choice when the client cannot hold a persistent connection open, for example a server-to-server integration where the requesting process may not even be running when the task eventually finishes.
A streaming response looks like this over SSE:
POST /message:stream HTTP/1.1
Content-Type: application/a2a+json
{ "message": { "role": "ROLE_USER", "parts": [{"text": "Write a report on climate change"}], "messageId": "msg-uuid" } }
HTTP/1.1 200 OK
Content-Type: text/event-stream
data: {"task": {"id": "task-uuid", "status": {"state": "TASK_STATE_WORKING"}}}
data: {"artifactUpdate": {"taskId": "task-uuid", "artifact": {"parts": [{"text": "# Climate Change Report\n\n"}]}}}
data: {"statusUpdate": {"taskId": "task-uuid", "status": {"state": "TASK_STATE_COMPLETED"}}}
Registering a webhook for push notifications is a separate call before or during task submission:
{
"message": { "role": "ROLE_USER", "parts": [{"text": "Generate the Q1 sales report"}], "messageId": "msg-1" },
"configuration": {
"taskPushNotificationConfig": {
"url": "https://client.example.com/webhook/a2a-notifications",
"authentication": { "scheme": "Bearer", "credentials": "secure-client-token" }
}
}
}
When the task finishes, the agent later POSTs a StreamResponse-shaped payload to that URL. Because a webhook receiver is effectively an open HTTP endpoint, the spec is explicit that agents must guard against SSRF when validating client-supplied webhook URLs (rejecting private IP ranges and localhost), and clients must authenticate the incoming call and treat delivery as at-least-once (duplicates are possible, so handlers should be idempotent).
Transport Bindings
A2A separates its abstract operations and data model from how they are physically transmitted. Three standard bindings exist today, and all of them must be functionally equivalent, meaning a client should get the same behavior no matter which one it picks:
| Binding | Transport | Method style | Streaming mechanism |
|---|---|---|---|
| JSON-RPC | HTTP(S), JSON-RPC 2.0 | SendMessage, GetTask, CancelTask, ... | Server-Sent Events |
| gRPC | HTTP/2, Protocol Buffers | A2AService RPCs, matching method names | Native gRPC server streaming |
| HTTP+JSON/REST | HTTP(S), plain JSON | Resource-oriented, e.g. POST /message:send, GET /tasks/{id} | Server-Sent Events |
An agent declares every binding it supports in its Agent Card's supportedInterfaces list, in preference order, and a client is free to choose whichever one it can implement. This is what lets a Python agent built on gRPC interoperate cleanly with a Node.js agent that only speaks JSON-RPC, as long as both sides implement at least one shared binding.
Security Model
A2A deliberately does not invent its own authentication scheme. Instead it leans entirely on standard web security: TLS for transport, and one of API key, HTTP auth (Bearer, Basic), OAuth 2.0, OpenID Connect, or mutual TLS for identity, all declared explicitly in the Agent Card's securitySchemes field. A client discovers what's required, obtains credentials out of band (an OAuth flow, an API key issuance process, whatever the scheme calls for), and attaches them to every request.
One interesting wrinkle specific to agent-to-agent workflows is the AUTH_REQUIRED task state. If an agent discovers mid-task that it needs authorization it doesn't have (an OAuth token to call a downstream API, or human sign-off before a destructive action), it transitions the task into AUTH_REQUIRED and attaches a message explaining what's needed. The client can then satisfy that requirement itself, forward the request to a human, or, if the client is itself an agent working on behalf of another caller, delegate further up the chain by putting its own task into AUTH_REQUIRED. This lets authorization requests propagate cleanly through a whole chain of delegating agents instead of forcing every agent in the chain to somehow pre-negotiate every possible credential up front.
Beyond authentication, the spec calls out authorization scoping as a hard requirement: agents must scope every task list and lookup to what the authenticated caller is actually allowed to see, even when no filter is specified, and must not distinguish between "does not exist" and "not authorized" in error responses to avoid leaking information about resources outside a caller's access.
Extensibility
Because A2A is meant to be a durable base layer, it includes a formal extension mechanism rather than expecting every possible feature to live in the core spec. An agent declares supported extensions (identified by URI) in its Agent Card, optionally marking them required, and a client opts in per-request by listing the extension URIs it wants to use. Extensions attach structured metadata to Messages or Artifacts under a key matching the extension's URI, which is how domain-specific behavior (citation formatting, geolocation context, custom business metadata) gets layered on top of the protocol without forking it.
Example: A Minimal End-to-End Flow
Putting the pieces together, a typical interaction looks like this:
- Client fetches
GET /.well-known/agent-card.jsonfrom the target agent and checkscapabilitiesandskillsto confirm the agent supports streaming and can handle the request. - Client calls
SendStreamingMessagewith the user's request. - Agent creates a
Task, returns it inWORKINGstate as the first stream event, and starts processing. - Agent emits
TaskArtifactUpdateEventchunks as output is produced, and a finalTaskStatusUpdateEventmoving the task toCOMPLETED. - If the agent instead needs clarification partway through, it emits a
TASK_STATE_INPUT_REQUIREDstatus with a message, the client answers on the sametaskId, and processing resumes. - If the client's connection drops, it can reconnect using
SubscribeToTask(which replays the current task snapshot before resuming the live event stream) or fall back to polling withGetTask.
Where A2A Fits and Where It Doesn't
A2A is not trying to replace function calling, tool use, or MCP. It operates one layer up: once you already have agents capable of doing useful work (via whatever internal tools or model calls they use), A2A is the wire format for letting those agents discover each other and hand off tasks as peers, with proper support for the things that make agent-to-agent work different from typical API calls: long-running execution, mid-task clarification, human-in-the-loop authorization, and streaming partial results. If you are building a single agent that just needs to call a weather API or query a vector database, MCP (or a plain function call) is the right tool. A2A becomes relevant the moment you have more than one independently deployed agent that need to cooperate.
Read. Learn. Build. Repeat.