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

name, description, version

Who the agent is. The description is what a calling agent’s model reads to decide whether to delegate.

supportedInterfaces

One entry per endpoint: url, protocolBinding (JSONRPC, GRPC or HTTP+JSON) and protocolVersion.

capabilities

Optional features: streaming, pushNotifications, extendedAgentCard (an authenticated, richer card).

skills

What the agent can do: each skill has an id, name, description, tags, optional examples and input/output modes.

defaultInputModes, defaultOutputModes

Media types the agent accepts and produces (for example text/plain, application/json).

securitySchemes, securityRequirements

How to authenticate (API key, HTTP auth, OAuth 2.0, OpenID Connect, mutual TLS), OpenAPI-style.

provider, documentationUrl, iconUrl

Who operates it and where to read more.

signatures

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:

The reviewer’s Agent Card
{
  "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_USER for the client, ROLE_AGENT for the remote agent), a messageId, 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, a contextId grouping related tasks and messages into one conversation, a status whose state moves through TASK_STATE_SUBMITTED, TASK_STATE_WORKING, and the interrupted states TASK_STATE_INPUT_REQUIRED and TASK_STATE_AUTH_REQUIRED to a terminal TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED or TASK_STATE_REJECTED, and a history.

  • An artifact is an output the task produced — a report, a file, structured data — with an artifactId, an optional name, 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

SendMessage

Send a message; returns a task or a message. Blocking by default; returnImmediately makes it return at once.

SendStreamingMessage

Same, but the response is a Server-Sent Events stream of task, status-update and artifact-update events.

GetTask, ListTasks, CancelTask

Poll, list and cancel tasks.

SubscribeToTask

Re-attach a stream to an existing, non-terminal task.

Push notification config methods

Create, get, list and delete a task’s webhook configuration.

GetExtendedAgentCard

Fetch the authenticated, extended card.

sequenceDiagram participant C as DocsAssistant (client agent) participant R as Reviewer (remote agent) C->>R: GET /.well-known/agent-card.json R-->>C: Agent Card (skills, interfaces, auth) C->>R: SendMessage (proposed fix, evidence) R-->>C: Task (TASK_STATE_WORKING) R->>R: review against sources R-->>C: Artifact "review" (verdict) R-->>C: Task (TASK_STATE_COMPLETED)

Streaming and push notifications

For long tasks, A2A offers two alternatives to polling with GetTask:

  • Streaming — SendStreamingMessage (or SubscribeToTask to re-attach) returns text/event-stream. The stream starts with the task, followed by statusUpdate and artifactUpdate events (artifacts can arrive in chunks), and closes when the task reaches a terminal state. The card must declare capabilities.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 /.well-known/agent-card.json, registries

Long-running work

The tasks extension (io.modelcontextprotocol/tasks) in the 2026-07-28 revision

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 docs_search

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).

The reviewer as an A2A server (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:

JSON-RPC request
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"}]
    }
  }
}
JSON-RPC response (abridged: IDs shortened)
{
  "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.

The remote reviewer as a LangChain4j agent
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, both org.springaicommunity, version 0.3.0) exposes a ChatClient as an A2A agent: provide AgentCard and AgentExecutor beans, and it serves the card at /.well-known/agent-card.json and 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-a2a lets a remote A2A agent act as a subagent of a Spring AI agent (see Spring AI subagents).

Spring AI community A2A server (protocol v0.3)
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 (A2AAgent to call a remote agent, A2AExecutor to expose one), alongside MCP.

CrewAI

A2A client and server configuration on agents (A2AConfig, A2AClientConfig, A2AServerConfig).

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.