Agent-to-Agent Protocol (A2A)
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page describes the A2A specification at version 1.0
(released 2026-03-12; the current patch release, 1.0.1, is dated 2026-05-26), and its examples target Python 3.12
with the official a2a-sdk 1.1.5 and FastAPI, Java 21 with LangChain4j 1.20.2 (langchain4j-agentic-a2a
1.20.2-beta30, built on the A2A Java SDK 1.3.1), and Spring Boot 4.1 with Spring AI 2.0.1 plus the community
spring-ai-a2a-server 0.3.0, as checked on 2026-09-28 (see the AI Agents baseline).
The Agent2Agent protocol (A2A) lets independent agents — built by different teams, in different frameworks, running in different places — discover each other, delegate tasks and exchange results, without exposing their internal state, prompts or tools to each other. Each agent stays opaque: the caller sees what it can do (its Agent Card) and what it returns (messages and artifacts), never how. A2A was developed by Google, donated to the Linux Foundation, and is maintained by a Technical Steering Committee drawn from several vendors.
Agent Cards and discovery
An Agent Card is a JSON document in which an agent describes itself: identity, where and how to reach it, what it can do, and how to authenticate. The spec’s primary discovery mechanism is a well-known URL:
https://{server_domain}/.well-known/agent-card.json
Registries and direct configuration are the other two. The main fields, all in camelCase JSON:
| Field | Meaning |
|---|---|
|
Who the agent is. The description is what a calling agent’s model reads to decide whether to delegate. |
|
One entry per endpoint: |
|
Optional features: |
|
What the agent can do: each skill has an |
|
Media types the agent accepts and produces (for example |
|
How to authenticate (API key, HTTP auth, OAuth 2.0, OpenID Connect, mutual TLS), OpenAPI-style. |
|
Who operates it and where to read more. |
|
Optional JWS signatures over the card (see Signed Agent Cards below). |
This is the Agent Card DocsAssistant’s reviewer serves, exactly as the Python server below renders it:
{
"name": "DocsAssistant Reviewer",
"description": "Reviews a proposed documentation fix for factual accuracy.",
"supportedInterfaces": [
{"url": "http://localhost:9999/a2a", "protocolBinding": "JSONRPC", "protocolVersion": "1.0"}
],
"version": "1.0.0",
"capabilities": {"streaming": false, "pushNotifications": false},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{
"id": "review-doc-fix",
"name": "Review a documentation fix",
"description": "Checks a proposed change to a docs page against the page's sources.",
"tags": ["docs", "review"]
}
]
}
A production card would declare securitySchemes and securityRequirements (the reviewer should not accept
anonymous tasks) and be served over HTTPS. Clients should cache cards using ordinary HTTP caching, as the spec
recommends. See A2A specification (Agent Discovery: The Agent
Card).
Tasks, messages and artifacts
-
A message is one turn of communication, with a
role(ROLE_USERfor the client,ROLE_AGENTfor the remote agent), amessageId, and a list of parts: text, a file (inline or by URL), or structured data. -
A task is the unit of work a message can create. It has an
id, acontextIdgrouping related tasks and messages into one conversation, astatuswhosestatemoves throughTASK_STATE_SUBMITTED,TASK_STATE_WORKING, and the interrupted statesTASK_STATE_INPUT_REQUIREDandTASK_STATE_AUTH_REQUIREDto a terminalTASK_STATE_COMPLETED,TASK_STATE_FAILED,TASK_STATE_CANCELEDorTASK_STATE_REJECTED, and a history. -
An artifact is an output the task produced — a report, a file, structured data — with an
artifactId, an optionalname, and parts. Artifacts are the deliverables; messages are the conversation around them.
A remote agent may answer a quick request with a plain message and no task; anything that takes time or may need
more input becomes a task. Multi-turn interaction reuses the same task: an agent that needs more information sets
TASK_STATE_INPUT_REQUIRED with a question in the status message, and the client replies with a new message
carrying the same taskId.
The core operations, identical across the JSON-RPC, gRPC and HTTP+JSON bindings (the JSON-RPC method names are shown):
| Operation | Purpose |
|---|---|
|
Send a message; returns a task or a message. Blocking by default; |
|
Same, but the response is a Server-Sent Events stream of task, status-update and artifact-update events. |
|
Poll, list and cancel tasks. |
|
Re-attach a stream to an existing, non-terminal task. |
Push notification config methods |
Create, get, list and delete a task’s webhook configuration. |
|
Fetch the authenticated, extended card. |
Streaming and push notifications
For long tasks, A2A offers two alternatives to polling with GetTask:
-
Streaming —
SendStreamingMessage(orSubscribeToTaskto re-attach) returnstext/event-stream. The stream starts with the task, followed bystatusUpdateandartifactUpdateevents (artifacts can arrive in chunks), and closes when the task reaches a terminal state. The card must declarecapabilities.streaming. -
Push notifications — for tasks that run for minutes or hours, or clients that cannot hold a connection open, the client registers a webhook (a URL plus the authentication the server must present) in the request’s configuration or with the push-notification config methods; the server POSTs task updates to it. The card must declare
capabilities.pushNotifications, and the client must validate that incoming notifications really come from the server — a webhook is an open door otherwise.
Signed Agent Cards
A card fetched over the network is only as trustworthy as the channel, so A2A 1.0 lets an agent sign its card
with JSON Web Signatures (JWS, RFC 7515). The signer canonicalises the card with the JSON Canonicalization Scheme
(JCS, RFC 8785) — with the signatures field itself excluded — signs it, and publishes one or more {protected,
signature} entries in signatures. A client that knows the provider’s public keys verifies the signature before
trusting the card’s endpoints and security requirements, which defends against a tampered or spoofed card
redirecting delegated tasks (and their data) to an attacker’s endpoint. Signing does not make the agent
trustworthy — that still requires authentication, authorisation and the guardrails on
Agent Guardrails & Safety.
MCP and A2A
The two protocols are complementary, not competing: MCP connects an agent to its tools and context; A2A connects an agent to other agents.
| MCP (agent ↔ tool) | A2A (agent ↔ agent) | |
|---|---|---|
What sits on the other side |
A tool, resource or prompt server with no goals of its own |
An opaque, autonomous agent that plans and uses its own tools |
Unit of interaction |
A tool call with typed arguments and a result |
A task with a lifecycle, multi-turn messages and artifacts |
Discovery |
The client lists the server’s tools, resources and prompts |
Agent Card at |
Long-running work |
The tasks extension ( |
Built in: task states, streaming, push notifications |
Transport |
stdio or Streamable HTTP; JSON-RPC 2.0, stateless since 2026-07-28 |
JSON-RPC 2.0, gRPC or HTTP+JSON over HTTPS |
Typical use |
DocsAssistant calls |
DocsAssistant asks another team’s reviewer agent to check a fix |
A single agent often speaks both: MCP downwards to its tools, A2A sideways to peers. Both are governed under the Linux Foundation, which formed the Agentic AI Foundation (AAIF) as a neutral home for open agent standards, starting with MCP; A2A is a Linux Foundation project with its own Technical Steering Committee. The MCP revision referred to here is the 2026-07-28 specification (stateless requests without the initialisation handshake or session header, Multi Round-Trip Requests, and tasks moved into an official extension). Building MCP servers is the subject of the Model Context Protocol (MCP) sub-section of this AI section.
A Python A2A server
With the official a2a-sdk, a server is an AgentExecutor — the agent’s logic — plus a card and a request
handler, mounted on an ASGI app. The executor publishes events to a queue: the task, status changes, artifacts.
This one exposes DocsAssistant’s reviewer; review_page stands in for the reviewer’s own LLM loop (for example the
reviewer agent on Multi-Agent Systems).
a2a_server.py)from fastapi import FastAPI
from a2a.helpers import get_message_text, new_task_from_user_message, new_text_part
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import (
add_a2a_routes_to_fastapi,
create_agent_card_routes,
create_jsonrpc_routes,
)
from a2a.server.tasks import InMemoryTaskStore, TaskUpdater
from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill
def review_page(text: str) -> str:
"""Stand-in for the reviewer agent's own LLM loop."""
return f"Reviewed: {text[:60]}... -- no factual issues found."
class ReviewerExecutor(AgentExecutor):
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
task = context.current_task or new_task_from_user_message(context.message)
await event_queue.enqueue_event(task)
updater = TaskUpdater(event_queue, task.id, task.context_id)
await updater.start_work()
verdict = review_page(get_message_text(context.message))
await updater.add_artifact([new_text_part(verdict)], name="review")
await updater.complete()
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
raise NotImplementedError("reviews are short; cancellation is not supported")
card = AgentCard(
name="DocsAssistant Reviewer",
description="Reviews a proposed documentation fix for factual accuracy.",
version="1.0.0",
supported_interfaces=[
AgentInterface(
url="http://localhost:9999/a2a",
protocol_binding="JSONRPC",
protocol_version="1.0",
)
],
capabilities=AgentCapabilities(streaming=False, push_notifications=False),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="review-doc-fix",
name="Review a documentation fix",
description="Checks a proposed change to a docs page against the page's sources.",
tags=["docs", "review"],
)
],
)
handler = DefaultRequestHandler(
agent_executor=ReviewerExecutor(),
task_store=InMemoryTaskStore(),
agent_card=card,
)
app = FastAPI()
add_a2a_routes_to_fastapi(
app,
agent_card_routes=create_agent_card_routes(card),
jsonrpc_routes=create_jsonrpc_routes(handler, rpc_url="/a2a"),
)
Run it with uvicorn a2a_server:app --port 9999. create_agent_card_routes serves the card at
/.well-known/agent-card.json by default, and create_jsonrpc_routes exposes the JSON-RPC binding at /a2a. The
SDK’s types are Protocol Buffer messages, so fields are snake_case in Python and camelCase on the wire.
InMemoryTaskStore is for development; the SDK also ships a database-backed task store. See
a2a-python and
A2A specification.
The wire shape
Any HTTP client can call the server; this is the JSON-RPC exchange, including the A2A-Version header that tells
the server which protocol version the client speaks:
POST /a2a HTTP/1.1
Content-Type: application/json
A2A-Version: 1.0
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"role": "ROLE_USER",
"messageId": "m-1",
"parts": [{"text": "Please review the proposed fix to ai/agents/memory.adoc"}]
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"task": {
"id": "f6321202-...",
"contextId": "55f2dd01-...",
"status": {"state": "TASK_STATE_COMPLETED", "timestamp": "2026-09-28T19:27:46.096471Z"},
"artifacts": [{
"artifactId": "48497d0d-...",
"name": "review",
"parts": [{"text": "Reviewed: Please review the proposed fix to ai/agents/memory.adoc... -- no factual issues found."}]
}],
"history": [{
"messageId": "m-1", "contextId": "55f2dd01-...", "taskId": "f6321202-...",
"role": "ROLE_USER", "parts": [{"text": "Please review the proposed fix to ai/agents/memory.adoc"}]
}]
}
}
}
Because the call blocked (the default), the result is the completed task with its artifact. See A2A specification (JSON-RPC Protocol Binding).
Calling A2A agents from LangChain4j
LangChain4j’s langchain4j-agentic-a2a module turns a remote A2A agent into an ordinary sub-agent: it fetches the
card, sends the mapped inputs as a message, and writes the result to the AgenticScope, so a remote reviewer can
be dropped into a sequence or a supervisor next to local agents.
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.service.V;
public interface RemoteReviewer {
@Agent("Reviews a proposed documentation fix against official sources")
String review(@V("proposedFix") String proposedFix);
}
RemoteReviewer reviewer = AgenticServices
.a2aBuilder("http://localhost:9999", RemoteReviewer.class)
.outputKey("review")
.build();
UntypedAgent draftThenReview = AgenticServices.sequenceBuilder()
.subAgents(fixDrafter, reviewer) // fixDrafter writes "proposedFix"
.outputKey("review")
.build();
See LangChain4j — Agents and Agentic AI (A2A integration), which
also covers multi-turn and human-in-the-loop A2A agents that reply with TASK_STATE_INPUT_REQUIRED.
Spring AI and A2A
Spring AI’s core (2.0.1) has no A2A support of its own. Two community projects fill the gap, and the version caveat matters:
-
Server side —
spring-ai-a2a-server(with its auto-configuration module, bothorg.springaicommunity, version 0.3.0) exposes aChatClientas an A2A agent: provideAgentCardandAgentExecutorbeans, and it serves the card at/.well-known/agent-card.jsonand the message and task endpoints. Version 0.3.0 is built on the A2A Java SDK 0.3.3, so it implements the pre-1.0 (v0.3) protocol, not v1.0 — check its release notes before depending on it for v1.0 clients. For a v1.0 server in Java today, the official A2A Java SDK (org.a2aproject.sdk, 1.4.0.Final at the time of writing) can be used directly from a Spring Boot application. -
Client side —
spring-ai-agent-utils-a2alets a remote A2A agent act as a subagent of a Spring AI agent (see Spring AI subagents).
import io.a2a.server.agentexecution.AgentExecutor;
import io.a2a.spec.AgentCapabilities;
import io.a2a.spec.AgentCard;
import io.a2a.spec.AgentSkill;
import org.springaicommunity.a2a.server.executor.DefaultAgentExecutor;
import org.springframework.ai.chat.client.ChatClient;
@Configuration
class ReviewerA2aConfig {
@Bean
AgentCard agentCard(@Value("${server.port:8080}") int port) {
return new AgentCard.Builder()
.name("DocsAssistant Reviewer")
.description("Reviews a proposed documentation fix for factual accuracy.")
.url("http://localhost:" + port + "/a2a/")
.version("1.0.0")
.capabilities(new AgentCapabilities.Builder().streaming(false).build())
.defaultInputModes(List.of("text"))
.defaultOutputModes(List.of("text"))
.skills(List.of(new AgentSkill.Builder()
.id("review-doc-fix").name("Review a documentation fix")
.description("Checks a proposed change to a docs page against its sources.")
.tags(List.of("docs", "review")).build()))
.protocolVersion("0.3.0")
.build();
}
@Bean
AgentExecutor agentExecutor(ChatClient.Builder builder, DocsTools docsTools) {
ChatClient reviewer = builder.clone()
.defaultSystem("Review the proposed documentation fix against the official sources. "
+ "Reply APPROVE or REJECT with one line per problem.")
.defaultTools(docsTools)
.build();
return new DefaultAgentExecutor(reviewer, (chat, ctx) -> chat.prompt()
.user(DefaultAgentExecutor.extractTextFromMessage(ctx.getMessage()))
.call()
.content());
}
}
DefaultAgentExecutor bridges the A2A request to the ChatClient and publishes the reply; with
spring.ai.a2a.server.enabled=true and a servlet context path of /a2a, the agent is reachable at
http://localhost:8080/a2a. See spring-ai-a2a.
In other frameworks
| Framework | A2A support |
|---|---|
OpenAI Agents SDK |
No built-in A2A client or server; an A2A agent is wrapped as a function tool, or the agent is exposed through the A2A SDKs. |
Claude Agent SDK |
No built-in A2A; an A2A agent can be called through a custom tool or an MCP server that fronts it. |
Microsoft Agent Framework |
A2A integration ( |
CrewAI |
A2A client and server configuration on agents ( |
Google’s ADK also has first-class A2A support (a RemoteA2aAgent and a to_a2a helper). See
OpenAI Agents SDK,
Claude Agent SDK,
Microsoft Agent Framework, CrewAI and
Google ADK.