The Agent2Agent protocol (A2A) lets AI agents built by different teams, on different models, hand work to each other. If you have ever integrated with someone else's API, most of it will feel familiar. You need to find the service, learn what it can do, follow long-running work and handle errors. Only now the service on the other end is an agent, which decides how to do the job and can ask you a question before it finishes.
To understand it properly, I built a bike ride planner. You type one sentence:
50 km loop around Lake Wigry on Saturday, start 9:00, coffee halfway.Four agents work together and give you a ride plan plus a GPX file for Garmin, Strava or Komoot. Each agent is a separate process on its own port, and they use real data from OpenStreetMap (routes and stops) and Open-Meteo (weather).
The Planner is the only agent you talk to. It sends work to three specialists. The Router plans the loop, Weather checks the forecast and the wind along the route, and the Guide finds coffee and water stops.
The code is on GitHub.
#What A2A is
A2A is an open protocol for communication between AI agents. Google announced it in April 2025 and two months later handed it over to the Linux Foundation, so no single company owns it.
Every agent is a black box to the others. You only see what an agent offers to do and the results it sends back. You don't see its prompt, its model, its tools or its data, and you don't need to. The Router could be rewritten in Python on a different model tomorrow, and the Planner would never notice.
A2A doesn't help you build an agent. LangGraph, CrewAI or a plain loop do that. A2A only defines how agents talk to each other, no matter how they were built.
People often mix up A2A and MCP (Model Context Protocol), but the two cover different relationships:
- MCP connects an agent to tools and data. The agent uses something.
- A2A connects an agent to another agent. The agent asks someone.
#How agents talk
There is no new transport here. The spec defines three bindings (JSON-RPC 2.0 over HTTP, gRPC and plain HTTP + JSON), and each agent picks the ones it supports. Long-running work is streamed with Server-Sent Events (SSE), so the agent can push updates as they happen.
This is roughly what the Planner sends to the Router:
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendStreamingMessage",
"params": {
"message": {
"role": "ROLE_USER",
"messageId": "9f1c2a7e",
"parts": [{ "text": "50 km loop around Lake Wigry on Saturday" }]
}
}
}The role field says who wrote the message. ROLE_USER is the caller, the side that hands out the task. ROLE_AGENT is the agent that replies. "User" doesn't have to be a human. When the Planner calls the Router, the Planner has the user role.
#Agent Cards
Before agents can talk, they have to find each other. Every agent publishes an Agent Card, a small JSON file at a fixed address:
http://localhost:9101/.well-known/agent-card.jsonHere is the Router's card, trimmed:
{
"name": "Router",
"description": "Plans bike loops in Poland.",
"supportedInterfaces": [
{ "url": "http://localhost:9101/", "protocolBinding": "JSONRPC" }
],
"capabilities": { "streaming": true },
"skills": [
{
"id": "plan-route",
"name": "Plan a bike route",
"description": "Returns the route as a DataPart and a GPX file. Asks back if the distance or bike type is missing."
}
]
}The Planner only knows the specialists' URLs. It fetches their cards and learns the rest.
The card doesn't list any tools. The Router uses a geocoder and a routing engine, but nobody else needs to know that. Instead, the card lists skills, written in plain language so a model can read them. A tool is a function an agent calls with exact arguments. A skill describes what an agent can do. Other agents can ask for that work, but they never see what happens underneath.
#What travels on the wire
Agents send each other two types of things. A message is a turn in the conversation, such as a request, a question or an answer. An artifact is the result of a task, the thing the caller actually asked for. An agent sends its artifacts when it finishes the work, and each one has a name, for example route.
Both are built from the same blocks, called parts. There are three types:
- Text: plain language, like "50 km loop around Lake Wigry".
- File: bytes or a URL, with a media type. The Router sends back
route.gpx. - Data: structured JSON. The Router's
routehas the distance, ascent, surface and a few hundred points.
Here is the Router's route artifact, shortened:
{
"name": "route",
"parts": [
{
"mediaType": "application/json",
"data": {
"route": {
"distanceKm": 50.3,
"ascentM": 214,
"coords": [[54.0405, 23.1255], [54.0478, 23.1340], "..."]
},
"bike": "gravel"
}
},
{ "mediaType": "application/gpx+xml", "filename": "route.gpx", "raw": "PD94bWwg..." }
]
}When the Planner passes the route to Weather and the Guide, it attaches it as data, so no LLM has to copy 300 coordinates, burn tokens and maybe make a typo along the way.
Weather and the Guide return their own artifacts, weather and stops. Each agent also sends a short text summary as a separate artifact, which the demo calls answer.
Messages, parts, artifacts and status updates can also carry a metadata field. It is a free-form JSON map, and the spec leaves its contents to you.
#The life of a task
In A2A, work happens in tasks. Each task has its own ID and moves through a small set of states. It starts as submitted, moves to working, and usually ends as completed. It can pause at input-required when the agent needs more information, and it can also end as failed, rejected or canceled.
rejected means the caller sent bad input, like an HTTP 400. failed means the agent itself broke, like a 500. Either way, the status carries a short reason back to the caller, and the caller decides what to do. In the demo the Planner passes the error up. If an LLM decided the Planner's next step instead, it could read the reason, fix the request and try again.
Status updates stream as they happen. The demo logs every event on one line:
[router] working: geocode {"query":"Lake Wigry"}
[router] working: planRoute {...}
[router] artifact: route
[router] completed
[weather] ← task 51be02c4 "..." + dataThe working lines show which tool the agent is using right now, and + data means the message carries a data part.
#Human in the loop
Sometimes the request has no distance or no bike type, like "Loop around Lake Wigry on Saturday". A road bike and a gravel bike need very different routes, so the Router doesn't guess. Its prompt tells it to reply with one short question instead, and the Router ends its turn as input-required, with the question as the status message:
[planner] ← task 3f2a9c1d "Find a loop around Lake Wigry on Saturday"
[router] ← task 9c1d7e0a "Find a loop around Lake Wigry on Saturday"
[router] input-required: What distance and which bike type?
[planner] input-required: What distance and which bike type?
[planner] ← task 3f2a9c1d "40 km, gravel"
[router] ← task 9c1d7e0a "40 km, gravel"
[router] working: geocode {"query":"Lake Wigry"}There are two tasks here. 3f2a9c1d is between you and the Planner, and 9c1d7e0a is between the Planner and the Router. The Router never talks to you. It asks whoever gave it the job. The Planner passes the question up to you, then sends your answer down on the Router's task, and the Router carries on with "40 km, gravel".
The answer always goes back on the same task ID, which is how an agent knows it's a continuation. The protocol doesn't care who answers, so if the Planner already knew the details, it could answer by itself.
#Inside an agent
A2A doesn't run your agent for you. The official TypeScript SDK handles HTTP, tasks and the event stream. The only thing it requires from you is an AgentExecutor that implements two methods:
execute: called for every incoming message. You read the message, do the work and publish events such as status updates, artifacts and the final state.cancelTask: called when the caller wants to stop a task.
What happens inside execute isn't part of the protocol. The agents are built on a plain loop:
- Send the model the conversation and the list of available tools.
- If the model answers with text, that's the final answer. Stop.
- If it asks for tools, run them, add the results to the conversation and go back to step 1.
This is the same basic loop most AI agents run. The model decides which tools to call and when it's done. Tool errors go back to the model as results instead of crashing the loop, so it can see what went wrong and try again. For a deeper look, my teammate Michał Śmiarowski wrote about agent loops in Everyone runs loops. Here is what's inside one.
The protocol stays outside and the agent inside, so you could replace the loop with LangGraph tomorrow and no other agent would notice.
#Tracking tokens and cost
Each agent pays for its own model calls, and the caller sees none of them. That also means each agent can pick its own model. In the demo, the Planner runs on Claude Sonnet 5.5, while the specialists use the cheaper Claude Haiku 4.5, which is enough for their narrow jobs.
A2A doesn't define usage reporting, so the demo uses the metadata field on status updates. Each agent puts its tokens and cost there, and the Planner adds everything up.
Every extra agent repeats some context, so splitting work across agents almost always costs more tokens than one agent doing it all.
#When to use A2A
Time for a reality check — this demo doesn't need A2A at all. A single agent with all the tools, or a few sub-agents in one process, would be simpler, faster and cheaper.
Microservices went through the same phase, when many teams split their apps because everyone else did, not because they needed to. Agents can fall into the same trap, only you also pay for it in tokens.
A2A makes sense when the other agent isn't yours. Take an online shop with a customer support agent. A customer writes: "Where's my parcel, and why was I charged twice?" The shop can't answer that alone:
- the courier's agent knows where the parcel is,
- the payment provider's agent can see what was charged and when.
The shop has no access to their systems or prompts, but each company can publish an agent with an Agent Card. The shop's agent finds them, sends each one a task and waits for the results. If the courier's agent asks "which order number?", the shop's agent answers. Every agent stays a black box, so three companies can work together without opening their systems to each other.
As a rule of thumb, A2A connects your system to someone else's. If you control both sides, start with one agent and split only when there's a real boundary, like another team, another company, another tech stack or data you're not allowed to touch.
AKENA is an engineering studio working across AI, blockchain, and software infrastructure. We build the systems AI agents run on: agent-facing RPC and MCP endpoints, on-chain data pipelines, and the payment, identity, and metering layers in front of them. If your agents need to hand work to agents another team or company runs, we should talk.

