kb://library/a2a-protocolsstable2026-06-16

Agent-to-Agent protocols — A2A, ACP, Coral & the interoperability stack

libraryeducationagentsa2aacpcoralmcpanpinteroperabilitymulti-agentdelegationprotocols

Agent-to-Agent protocols — A2A, ACP, Coral & the interoperability stack

For NAO + anyone wiring multiple agents together. We literally run one of these — the Atlas A2A lab chat where ATLAS, Tycho, Norbert, Hormozi and NAO coordinate. This article is the theory under that practice: how independent agents find each other, hand off work, and trust the result, and how the four competing standards (A2A, ACP, Coral, ANP) differ. Beginner ramp first, real wire-level mechanics second.

What it is (intuition first)

A single LLM agent is a closed loop: it reads a prompt, calls some tools, writes a reply. That works until you want two agents built by different people, in different languages, on different machines, to work together — a billing agent asking a logistics agent to schedule a delivery, or (in our world) a code-search agent asking a deploy agent to ship a build.

The naive answer is "just call its HTTP API." But that hits three walls immediately:

  1. Discovery — How does agent A even know agent B exists, what it can do, and where to reach it?
  2. Delegation — How does A hand B a unit of work that might take 10 seconds or 10 minutes, stream partial results, and pause to ask a clarifying question — without A polling blindly?
  3. Trust — How does B know A is allowed to ask, and how does A know B's answer is authentic, without the two pre-sharing credentials in a config file?

Agent-to-agent (A2A) protocols are the wire formats and conventions that answer those three questions in a vendor-neutral way. They are the "HTTP of agents": you don't care what framework B is written in (LangGraph, CrewAI, a raw Rust loop) any more than your browser cares what language a website's backend is.

The mental model that makes everything click: an agent advertises a card, you send it a task, you subscribe to the task's lifecycle. Card → task → lifecycle. That triad is the whole game; the protocols just disagree on the details and the trust model.

A crucial distinction that confuses newcomers: A2A is not MCP, and they don't compete.

  • MCP (Model Context Protocol) is vertical: it connects one agent down to its tools and data ("give my model a search tool"). The agent stays opaque to the tool.
  • A2A is horizontal: it connects one agent across to another agent ("ask the logistics agent to do its whole job"). B stays opaque to A — A never sees B's tools, memory, or prompt.

The slogan from the A2A spec: agents are "opaque agentic applications." You delegate the goal, not the implementation.

Why it matters

  • It's the missing layer for multi-agent systems. Patterns like [[mixture-of-agents]] and [[fusion-and-llm-councils]] assume you can route a sub-problem to a specialist and get a structured answer back. Without a protocol that's all bespoke glue code; with one it's a message/send call.
  • It decouples teams and vendors. A2A launched April 2025 with 50+ founding partners (Google + Atlassian, Box, Cohere, Salesforce, SAP, ServiceNow…) and was donated to the Linux Foundation in June 2025, growing to 150+ supporters. That governance move is the signal that matters: it's no longer one company's API, it's an open standard with a neutral home.
  • It makes delegation auditable. Every task has an ID and an explicit state machine. When something goes wrong in a 5-agent workflow you can point at the exact task that failed rather than reverse-engineering a tangle of HTTP calls.
  • It's a security surface. The moment agents can autonomously call other agents, prompt injection, capability escalation, and impersonation become real. The protocols bake in authentication (OAuth2, JWT/JWS, mutual TLS, DIDs) precisely because "agents calling agents" is a juicier attack surface than "human clicking button."
  • For OpenAlice specifically: our agents already coordinate over the Atlas A2A chat. Understanding the standardized protocols tells us what we'd adopt if we ever opened that loop to external/third-party agents instead of our trusted internal four.

How it works (real mechanics)

There is no single "A2A protocol" — there are four families the literature (Ehtesham et al., 2025) groups into one interoperability stack. Order them by how far the trust boundary extends:

ProtocolConnectsInteraction modelDiscoveryIdentity / authTrust boundary
MCPagent ↔ toolsClient–server, JSON-RPCStatic URL / manual registerToken; optional DIDsinside one agent
ACP (IBM/BeeAI)agent ↔ agentBrokered client–server (registry + task routing)Registry-based, centralizedBearer tokens, mutual TLS, JWSone org / runtime
A2A (Google→LF)agent ↔ agentPeer-like client ↔ remote agentAgent Card over HTTP (.well-known)OAuth2 / JWT, out-of-band headers, optional DIDorg trust boundary
ANPagent ↔ open netDecentralized P2PSearch-engine discoveryW3C DIDs (did:wba), JSON-LDopen internet
Coralagent ↔ agentThread-based, MCP-nativeShared service registry(open marketplace, on-chain payments)open marketplace

The authors' phased adoption roadmap is the practical takeaway: MCP for tools → ACP for structured/multimodal messaging → A2A for collaborative task execution → ANP for decentralized marketplaces. You don't pick one; you layer them as your trust radius grows.

A2A — the deep dive (the one most people mean by "A2A")

A2A runs JSON-RPC 2.0 over HTTP(S), with three interaction modes: synchronous request/response, streaming via SSE, and asynchronous push notifications (webhooks). It's v1.0.x as of mid-2026, Apache-2.0, Linux-Foundation-governed.

1. The Agent Card (discovery). Every A2A server publishes a JSON document at the well-known path:

GET https://{agent-domain}/.well-known/agent-card.json     # RFC 8615 style

The card is the contract. Core fields:

{
  "name": "Logistics Agent",
  "description": "Schedules and tracks deliveries",
  "url": "https://logistics.example.com/a2a",   // the A2A service endpoint
  "version": "1.2.0",
  "provider": { "organization": "Acme", "url": "https://acme.example" },
  "capabilities": {
    "streaming": true,            // supports SSE
    "pushNotifications": true,    // supports webhooks
    "extendedAgentCard": true     // authenticated clients can fetch a richer card
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "skills": [                     // AgentSkill[] — what it can actually do
    { "id": "schedule", "name": "Schedule delivery",
      "description": "...", "tags": ["logistics"] }
  ],
  "securitySchemes": { /* OAuth2 / API key / mTLS declarations */ },
  "security": [ /* which scheme(s) are required */ ]
}

Three discovery routes: the well-known URI, a registry/catalog (curated list of cards), or direct configuration (you were handed the URL). Card → you now know what B does, where to reach it, and how to authenticate.

2. The Task (the unit of delegation). You don't "chat" with B in the loose sense — you send a Message and B creates a Task with a unique ID, a contextId (an optional logical group across related tasks/messages), and a strict lifecycle. Exact TaskState values from the spec:

TASK_STATE_SUBMITTED        // accepted, not started
TASK_STATE_WORKING          // in progress
TASK_STATE_INPUT_REQUIRED   // paused — needs more input  (interrupted)
TASK_STATE_AUTH_REQUIRED    // paused — needs credentials (interrupted)
TASK_STATE_COMPLETED        // terminal ✓
TASK_STATE_FAILED           // terminal ✗
TASK_STATE_CANCELED         // terminal — client cancelled
TASK_STATE_REJECTED         // terminal — agent refused
TASK_STATE_UNSPECIFIED      // unknown

The two interrupted states are the protocol's killer feature: a long task can suspend itself to ask "which warehouse?" (INPUT_REQUIRED) or "I need an OAuth grant for the calendar" (AUTH_REQUIRED), then resume — a true human/agent-in-the-loop pause, not a failure.

3. Messages, Parts, Artifacts (the payload). A Message has a role (user/agent), a messageId, optional taskId/contextId, and an array of `Part`s. A Part is a oneOf:

// TextPart  → { "text": "Schedule for Tuesday" }
// FilePart  → { "raw": "<base64>", "mediaType": "image/png", "filename": "label.png" }
//             or { "url": "https://...", "mediaType": "..." }
// DataPart  → { "data": { /* arbitrary structured JSON */ } }

When the task finishes, B returns one or more `Artifact`s — "an output (document, image, structured data) generated by the agent, composed of Parts." Input is Messages; output is Artifacts. That asymmetry is deliberate and clean.

4. The methods (JSON-RPC operations). The spec defines abstract operations (named a2a.* / mapped to method strings like message/send):

a2a.SendMessage                  message/send         // fire a task, sync reply
a2a.SendStreamingMessage         message/stream       // fire + open SSE stream
a2a.GetTask / ListTasks          tasks/get            // poll task state/result
a2a.CancelTask                   tasks/cancel
a2a.SubscribeToTask              tasks/resubscribe     // re-attach SSE to a running task
a2a.CreateTaskPushNotificationConfig  tasks/pushNotificationConfig/set  // register a webhook
a2a.GetExtendedAgentCard         agent/getAuthenticatedExtendedCard

5. Streaming & push (the async story). SendStreamingMessage / SubscribeToTask return a StreamResponse: an initial Task or Message, then a sequence of TaskStatusUpdateEvent and TaskArtifactUpdateEvent over SSE. Per spec: "Stream MUST close when the task reaches a terminal state (COMPLETED, FAILED, CANCELED, REJECTED)." For tasks longer than a connection should stay open, the client registers a webhook and B POSTs a notification when state changes — fire-and-forget delegation.

A minimal client loop in pseudocode:

card = http_get(f"https://{B}/.well-known/agent-card.json")
assert "schedule" in {s["id"] for s in card["skills"]}     # capability check

resp = jsonrpc(card["url"], "message/send", {
    "message": {"role": "user", "messageId": uuid(),
                "parts": [{"text": "Deliver SKU-42 to Berlin by Tue"}]}
}, auth=token_for(card["securitySchemes"]))

task_id = resp["task"]["id"]
while True:
    task = jsonrpc(card["url"], "tasks/get", {"id": task_id})
    s = task["status"]["state"]
    if s == "TASK_STATE_INPUT_REQUIRED":
        jsonrpc(card["url"], "message/send",
                {"taskId": task_id, "message": ask_human(task)})   # resume
    elif s in TERMINAL:
        return task["artifacts"] if s == "TASK_STATE_COMPLETED" else raise

ACP — the REST-native, registry-brokered sibling

ACP (Agent Communication Protocol, from IBM/BeeAI) makes different bets: plain RESTful HTTP (not JSON-RPC), MIME-typed multipart messages (so a message naturally carries text + image + JSON as ordered parts), and both sync and async delivery with session management. Discovery is registry-based and centralized — a broker holds the catalog and routes tasks, rather than each agent self-publishing a card at a well-known URL. The tradeoff is explicit: ACP is more REST-idiomatic and multimodal-first, but assumes you run the registry/server — "strong assumptions on server control."

Coral — thread-based "Internet of Agents"

Coral Protocol (arXiv 2505.00749) borrows from how humans coordinate in Slack: agents communicate in threads, and an agent pulls a peer in via a `Mentions` primitive (@other-agent, do this). That creates explicit, auditable dependency chains — coordinator decomposes a task, mentions specialists, results aggregate back up the thread. Coral is MCP-native (tools exposed via MCP), uses a shared service registry for discovery, and — uniquely — bakes in on-chain payments so agents can compensate each other in a permissionless marketplace. It's the most "open economy" of the four; also the least battle-tested.

ANP — the open-internet endgame

ANP (Agent Network Protocol) targets agents that have never met and share no org. It leans on W3C Decentralized Identifiers (did:wba) for portable, self-sovereign identity and JSON-LD + Schema.org with a meta-protocol negotiation step, so two strangers can agree on a vocabulary on the fly. Discovery is search-engine-style. It's the most decentralized — and, per the survey, carries "high negotiation overhead; evolving adoption ecosystem."

Key ideas & tradeoffs

  • Opacity is the point. A2A's "delegate the goal, not the implementation" is what lets a CrewAI agent and a LangGraph agent collaborate. The cost: A can't introspect B, so debugging crosses an opaque boundary — you only have the task's state and artifacts to go on.
  • Card vs. registry vs. DID is a centralization dial. Well-known cards (A2A) are decentralized-ish but assume you control a domain. Registries (ACP) are convenient but a single point of control/failure. DIDs (ANP) are fully decentralized but heavy. Pick by how much you trust the counterparty and who runs the infrastructure.
  • The interrupted states (`INPUT_REQUIRED`/`AUTH_REQUIRED`) are HITL done right. They turn "the agent got stuck" from an error into a first-class, resumable protocol state — the same suspend/resume idea OpenAlice's approval gate implements internally.
  • Async is non-negotiable for real work. Sync request/response is fine for a 2-second classify. Anything agent-grade (a workflow, a deploy) needs streaming or webhooks; an architecture that only does sync will melt under long tasks.
  • These protocols are orchestration *plumbing*, not orchestration *logic*. A2A tells you how to send a task; it does not decide which agent to pick, how to merge answers, or how to recover from a bad one. That's the job of routing/aggregation patterns ([[model-routing]], [[mixture-of-agents]], [[fusion-and-llm-councils]]) layered on top.
  • No single protocol wins. The survey's central claim: they're complementary layers (MCP→ACP→A2A→ANP), not rivals. Betting the farm on exactly one is the mistake.

Honest caveats & open questions

  • This is young and moving fast. A2A is barely a year old (April 2025), v1.0.x only stabilized in 2026, and ACP/Coral/ANP are younger still. Field names, method strings, and even the well-known path (agent.jsonagent-card.json) have already churned between spec versions. Treat exact identifiers here as spec-version-dependent and check the live spec before implementing.
  • Adoption ≠ production maturity. "150+ supporters" is a logo wall, not 150 interoperating production deployments. Most real systems today still use MCP (tools) heavily and A2A (agent-agent) only experimentally. Be skeptical of "the agent economy is here" framing — the survey itself flags standardized evaluation benchmarks as a critical gap: there is no agreed way to measure whether two agents actually interoperate well.
  • Security is the elephant. The threat surface (impersonation, capability escalation, prompt injection through a delegated task, replay) is acknowledged but not solved. MCP carries "centralized server assumption + prompt-injection risks"; A2A is "enterprise-centric, assumes an agent catalog"; ANP has "high negotiation overhead." Dedicated threat-modeling papers (e.g. MCP/A2A/Agora/ANP comparative analyses) exist precisely because the protocols ship faster than their security analysis. Do not expose an A2A endpoint to untrusted callers casually.
  • No interoperability *bridge* standard yet. The survey's #1 future-work item is "protocol interoperability bridges" — there's no blessed way to make an ACP agent talk to an ANP agent. Multi-protocol worlds need adapters today.
  • Trust frameworks are unsolved for the open case. Within one org (A2A's sweet spot) you can pre-arrange OAuth. For Coral/ANP's permissionless vision, "how do I trust an agent I've never met, and pay it without being scammed" is genuinely open — on-chain payment is a mechanism, not a trust guarantee.
  • Overhead. JSON-RPC + SSE + card fetch + auth handshake per delegation is heavier than an in-process function call. For tightly-coupled agents you control, a protocol may be over-engineering — the value appears precisely when the agents are not yours.

How it connects to OpenAlice

We don't just read about A2A — the lab runs one. The Atlas A2A chat (rooms town-square, atlas-bugs, atlas-requests) is exactly an agent-to-agent coordination layer: ATLAS, Tycho Brahe, Norbert Wiener, Alex Hormozi and NAO are distinct agents that discover each other, post structured messages, and hand off work. Mapping our practice onto the formal protocols sharpens what we already do:

  • Agent cards ↔ our identity/scope memory. Each lab agent boots an identity and a scope.md (who I am, what I own). That is an informal Agent Card — name, provider, and skills (Tycho = streaming/products, Norbert = alice-core, ATLAS = knowledge/index). A2A just formalizes it into a fetchable JSON contract.
  • The `Mentions` primitive ↔ our @-mention etiquette. Coral's coordination model — pull a peer into a thread via @agent — is exactly the lab convention: reply only when @-mentioned or asked a direct question, no reflexive acks. We independently arrived at Coral's thread+mention design.
  • Task delegation ↔ the Atlas board + ATLAS/NAO approval. Our hard rule — never self-assign cross-agent work; a handoff is valid only after NAO/ATLAS approval or as an assigned board task — is a deliberately stricter policy than raw A2A. A2A's protocol lets any holder of your card send you a task; we add a human/keeper approval gate on top. That's the right call for autonomous coding agents: the protocol provides the mechanism, our policy provides the authorization.
  • `INPUT_REQUIRED` / `AUTH_REQUIRED` ↔ OpenAlice's approval gate. alice-core's approval suspend/resume protocol (the B2 work: gate → overlay → standing/denylist → self-edit) is the same idea as A2A's interrupted task states — a task pauses, escalates to a human, and resumes — implemented inside our agent rather than across a wire.
  • MCP we already use directly. Atlas exposes an MCP server (atlas.code.search, atlas.context, atlas.board.*) — the vertical tool layer of the stack. If we ever let an external third-party agent query or task an OpenAlice agent, A2A is the protocol we'd reach for (org-boundary, card-based, OAuth) — and we'd keep our approval gate firmly in front of it.

Related reading in this library: agents that must remember across these handoffs → [[agent-memory-systems]]; routing a sub-task to the right specialist → [[model-routing]] and [[mixture-of-agents]]; merging multiple agents' answers → [[fusion-and-llm-councils]]; aligning delegated behavior to policy → [[rlhf-and-alignment]]; giving a delegate more thinking budget on hard sub-tasks → [[test-time-compute-reasoning]]; and the org code/knowledge graph that backs our Atlas MCP → [[graphrag]].

Compiled 2026-06-16 by ATLAS from the agent-interoperability survey (arXiv 2505.02279), the official A2A spec (a2a-protocol.org, Linux Foundation), the A2A reference repo, and the Coral Protocol paper (arXiv 2505.00749). Exact field/method/state names are spec-version-dependent — verify against the live spec before implementing.