Agentic Systems

Agent-to-Agent (A2A) Protocol

A2A is an open protocol for inter-agent collaboration: independent, often opaque agents discover one another through Agent Cards, then exchange Messages and stateful Tasks over JSON-RPC 2.0 (with optional SSE streaming and webhook push notifications). This page covers Agent Card discovery, the Task lifecycle, Messages and Parts, Artifacts, the RPC method surface, and how A2A complements MCP.

The A2A (Agent2Agent) protocol is an open standard for how independent AI agents — built by different teams, vendors, and frameworks — discover one another, negotiate, and collaborate on tasks. Where the Model Context Protocol (see protocols-mcp.html) lets a single agent reach down to its own tools and data, A2A lets an agent reach across to a peer it does not control and cannot see inside. The remote agent is treated as an autonomous, opaque system that "reasons, plans, uses multiple tools, and maintains state over longer interactions," rather than a stateless function call.

A2A was introduced by Google in April 2025 and donated to the Linux Foundation, which launched the vendor-neutral Agent2Agent Protocol project on June 23, 2025 with founding support from Amazon Web Services, Cisco, Google, Microsoft, Salesforce, SAP, and ServiceNow. The specification is published under the Apache 2.0 license.

A2A is deliberately built on widely deployed web standards so it drops into existing infrastructure: communication occurs over HTTP(S); JSON-RPC 2.0 is the payload format for all requests and responses; and Server-Sent Events (SSE) carry streaming updates.

The three actors

A2A defines a client/server relationship that is itself between agents:

  • A2A Client (client agent) — an application, service, or agent acting on behalf of a user that initiates requests to a remote agent.
  • A2A Server (remote agent) — an agent or agentic system that exposes an HTTP endpoint implementing A2A.
  • User — the human or system whose goal is ultimately being served.

A single deployment is frequently both: an orchestrator agent is a server to its caller and a client to the specialist agents it delegates to.

Agent Card discovery

Before any work happens, a client must find a server and learn what it can do. A server advertises itself with an agent-card: a JSON metadata document describing the agent's identity, service endpoint, capabilities, skills, and how to authenticate. The canonical discovery mechanism is the well-known URI — the card is hosted at:

https://{agent-server-domain}/.well-known/agent-card.json

(This path was /.well-known/agent.json through v0.2.x and was renamed to agent-card.json in v0.3.0, released in late July 2025; the Agent Card examples on this page pin protocolVersion: "0.2.5" but adopt the newer path name.) Two other discovery mechanisms are defined: curated registries (a catalog service that holds many Agent Cards) and direct configuration (hardcoded URLs, config files, or environment variables).

A representative Agent Card. Field names below are spec-accurate:

{
  "protocolVersion": "0.2.5",      // A2A version the server implements
  "name": "Revenue Analyst Agent", // human-readable agent name
  "description": "Analyzes financial data and produces revenue summaries.",
  "url": "https://agent.example.com/a2a/v1", // the A2A service endpoint
  "preferredTransport": "JSONRPC", // transport at `url` (default JSON-RPC)
  "version": "1.4.0",              // the agent implementation's own version
  "provider": {                    // optional: who operates the agent
    "organization": "Example Corp",
    "url": "https://example.com"
  },
  "capabilities": {                // optional protocol features the agent supports
    "streaming": true,             // supports message/stream over SSE
    "pushNotifications": true,     // supports webhook push notifications
    "stateTransitionHistory": false
  },
  "defaultInputModes": ["text/plain", "application/json"],  // accepted media types
  "defaultOutputModes": ["text/plain", "application/json"], // produced media types
  "securitySchemes": {             // OpenAPI-style auth scheme declarations
    "bearer": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
  },
  "security": [{ "bearer": [] }],  // which scheme(s) apply
  "skills": [                      // discrete capabilities the agent offers
    {
      "id": "summarize-revenue",
      "name": "Summarize revenue",
      "description": "Summarize quarterly revenue from supplied figures.",
      "tags": ["finance", "summarization"],
      "inputModes": ["text/plain"],
      "outputModes": ["text/plain"]
    }
  ],
  "supportsAuthenticatedExtendedCard": true // a fuller card may exist behind auth
}

Authentication in A2A is transport-level, not a bespoke scheme: the card declares securitySchemes using OpenAPI security-scheme objects (e.g. HTTP bearer/JWT, API key, OAuth 2, OpenID Connect), and the client presents the corresponding credentials on its HTTP requests. Capability discovery is just reading the card: the capabilities, skills, and the defaultInputModes/defaultOutputModes fields tell a client what the agent can do and in what formats before it ever sends a request. If supportsAuthenticatedExtendedCard is true, a client can fetch a richer card after authenticating — but how depends on the protocol version. In the v0.2.5 spec this page pins its examples to, the extended card is an HTTP GET endpoint at {AgentCard.url}/../agent/authenticatedExtendedCard and is explicitly "not a JSON-RPC method." A dedicated JSON-RPC method, agent/getAuthenticatedExtendedCard, was only added in v0.3.0.

Messages and Parts

A a2a-message is a single turn of communication. Each message carries a role"user" for content from the client/user side, "agent" for content from the remote agent — and an array of parts. A2A makes the multi-modal unit of content explicit as a a2a-part, a discriminated union with a kind field:

  • TextPart{ "kind": "text", "text": "..." }
  • FilePart{ "kind": "file", "file": { ... } }, where the file is either a FileWithBytes (bytes, base64-encoded, plus optional name/mimeType) or a FileWithUri (uri plus optional name/mimeType).
  • DataPart{ "kind": "data", "data": { ... } } for structured JSON (forms, parameters, machine-readable results).

A message also has a required messageId, an optional taskId and contextId to bind it to an existing task and grouping, optional referenceTaskIds, and the literal discriminator "kind": "message".

Tasks and the lifecycle

When a request requires stateful, possibly long-running processing, the server creates a a2a-task: a unit of work with a unique id, a server-generated contextId (which logically groups related tasks), a status, and accumulated history (messages) and artifacts. A task carries the literal discriminator "kind": "task".

The task's status is a TaskStatus object holding a task-state (state), an optional message, and an optional timestamp. The spec defines exactly these TaskState string values:

State Meaning
submitted Acknowledged, not yet started
working Actively being processed
input-required Paused; needs more input from the client (interrupted, not terminal)
auth-required Paused; needs additional authentication/credentials
completed Finished successfully (terminal)
canceled Canceled by request (terminal)
failed Ended in error (terminal)
rejected Refused by the agent (terminal)
unknown State could not be determined

completed, canceled, failed, and rejected are terminal; input-required and auth-required are interrupted states that hand control back to the client mid-task without ending it.

Artifacts

An a2a-artifact is a tangible output the agent produces during a task — a document, image, or structured result. Like a message, an artifact is composed of parts, and it has a stable artifactId plus optional name, description, and metadata. Artifacts are distinct from messages: messages are conversational turns, while artifacts are the durable results accumulated on the task and can be streamed incrementally in chunks.

The RPC method surface

A2A's JSON-RPC binding defines a small, stable set of method strings:

Method Purpose
message/send Send a message; start or continue a task (request/response)
message/stream Send a message and subscribe to streamed updates over SSE
tasks/get Poll the current state of a task by id
tasks/cancel Request cancellation of a task
tasks/resubscribe Re-attach an SSE stream to an existing task (e.g. after a drop)
tasks/pushNotificationConfig/set Register a webhook for push notifications on a task
tasks/pushNotificationConfig/get Retrieve a task's push-notification config
tasks/pushNotificationConfig/list List configured push configs for a task
tasks/pushNotificationConfig/delete Remove a push-notification config

Fetching the authenticated extended Agent Card is not a JSON-RPC method in v0.2.5 (the version these examples pin to) — there it is an HTTP GET endpoint (see Agent Card discovery above). A JSON-RPC method for it, agent/getAuthenticatedExtendedCard, was added in v0.3.0.

A message/send request — the delegating agent speaks as the "user":

{
  "jsonrpc": "2.0",                 // JSON-RPC 2.0 envelope
  "id": 1,                          // request id; echoed in the response
  "method": "message/send",         // A2A method to hand off work
  "params": {
    "message": {
      "role": "user",               // content from the client side
      "parts": [
        { "kind": "text", "text": "tell me a joke" }  // a TextPart
      ],
      "messageId": "9229e770-767c-417b-a0b0-f0741243c589" // client-generated id
    },
    "metadata": {}
  }
}

If the work completes quickly, the server can answer the same request with a finished Task:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "363422be-b0f9-4692-a24d-278670e7c7f1",        // the task id
    "contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4", // groups related tasks
    "status": { "state": "completed" },                  // terminal state
    "artifacts": [
      {
        "artifactId": "9b6934dd-37e3-4eb1-8766-962efaab63a1",
        "name": "joke",
        "parts": [
          { "kind": "text",
            "text": "Why did the chicken cross the road? To get to the other side!" }
        ]
      }
    ],
    "kind": "task"                                       // literal discriminator
  }
}

A server may instead reply with a Message directly (a one-shot answer with no task to track), or with a Task in a non-terminal state that the client then polls via tasks/get.

Streaming with SSE

When the Agent Card advertises capabilities.streaming: true, a client can call message/stream instead of message/send. The HTTP response is a Server-Sent Events stream; each SSE data: line carries a JSON-RPC response whose result is one of:

  • the initial Task object,
  • a TaskStatusUpdateEventkind: "status-update" — carrying a new status, with a final boolean that is true on the last event,
  • a TaskArtifactUpdateEventkind: "artifact-update" — carrying an artifact chunk, with append (concatenate to a prior chunk vs. replace) and lastChunk flags.

The stream MUST begin with the Task and MUST close once the task reaches a terminal state. A status-update event mid-task:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "taskId": "363422be-b0f9-4692-a24d-278670e7c7f1",
    "contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4",
    "kind": "status-update",        // TaskStatusUpdateEvent discriminator
    "status": { "state": "working" },
    "final": false                  // not the last event in the stream
  }
}
sequenceDiagram
    participant C as Client Agent
    participant S as Remote Agent (A2A Server)
    C->>S: GET /.well-known/agent-card.json
    S-->>C: Agent Card (skills, capabilities, auth)
    C->>S: POST message/stream (role=user, parts)
    S-->>C: SSE: Task (state=submitted)
    S-->>C: SSE: status-update (state=working, final=false)
    S-->>C: SSE: artifact-update (artifact chunk, lastChunk=false)
    S-->>C: SSE: artifact-update (artifact chunk, lastChunk=true)
    S-->>C: SSE: status-update (state=completed, final=true)
    Note over C,S: Stream closes on terminal state

Push notifications

For long-running work where holding a connection open is impractical, A2A supports push-notifications: the client registers a webhook via tasks/pushNotificationConfig/set, and the server POSTs asynchronous updates to that URL as the task progresses. The PushNotificationConfig carries the target url, an optional token that the server includes so the client can validate the callback belongs to the right task/session, and an authentication object describing how the server should authenticate to the webhook. This lets a client disconnect entirely and be notified when state changes — the asynchronous counterpart to SSE.

How A2A complements MCP

A2A and the Model Context Protocol are not competitors; they operate at different layers and are explicitly designed to compose:

  • MCP is agent-to-tool. It connects an agent to tools and resources — "primitives with well-defined, structured inputs and outputs" that perform "specific, often stateless, functions." MCP focuses on agents using capabilities.
  • A2A is agent-to-agent. It lets "independent, often opaque" agents collaborate as peers — discovering each other, negotiating interactions, and managing shared, stateful tasks. A2A focuses on agents partnering on tasks.

The canonical framing in the spec: an agentic application "might primarily use A2A to communicate with other agents," while "each individual agent internally uses MCP to interact with its specific tools and resources." In practice an orchestrator delegates a task to a specialist agent over A2A, and that specialist reaches for a database or API over MCP — the two protocols stack rather than overlap. For a side-by-side of the protocol landscape, see protocols-comparison.html.