GGoldwater.dev
All labs
Hands-on LabIntermediate45–60 min·Updated June 2026

Agent-to-Agent Collaboration (A2A)

Wire up the Agent-to-Agent protocol so independent agents can discover each other and collaborate on a task — with a worked, offline Acme example.

Download starter repo

What you'll build

By the end of this lab you'll have two agents collaborating over the Agent-to-Agent (A2A) protocol — Google's open standard (1.0 GA as of mid-2026) for letting agents discover each other and hand off work securely. You'll build:

  • A specialist — the Acme Orders Agent (an A2A server) that publishes an Agent Card and answers order questions through a real task lifecycle.
  • A primary agent — a Concierge that discovers the specialist, delegates a task to it over A2A, and composes the final answer.

Along the way you'll see the things that make A2A different from "just call an API": agents publish capabilities in a discovery document, work is modeled as a task with a lifecycle (not a one-shot response), and an agent can push back and ask a clarifying question mid-task instead of failing.

Everything runs offline with no third-party packages. The transport is a small standard-library server, but the wire shapes and methods follow the A2A 1.0 spec, so what you see matches real A2A traffic.


Why this matters (the 5-minute business case)

Read this section before handing the lab to your team.

A2A is the protocol for the shift from "one assistant" to "a team of specialized agents." As organizations build more agents, they hit a wall: a single mega-agent that tries to do everything accumulates tools, context, and dependencies until it's slow, expensive, and unreliable. The alternative is many focused agents that collaborate — but that only works if they share a common language for discovering each other and handing off tasks. A2A is that language. Crucially, it treats agents as agents, not as APIs: APIs are rigid and deterministic; agents are fluid, can refine a plan, ask clarifying questions, and run long autonomous tasks.

Four business advantages the protocol is explicitly designed around:

  1. The secure boundary ("protecting the secret sauce"). Enterprise agents often rely on sensitive data or proprietary processes that can't be exposed to a third party or a public model. A2A enables a black-box handoff: you delegate a task to a specialist agent that keeps its data and logic private, and you get back only the high-value output. The requesting agent never sees inside. For security and IP, that encapsulation is the whole game.
  2. Zero context pollution. Models have finite context windows. Cramming a multi-step problem and all its dependencies into one agent fills that window and degrades quality (more hallucination, worse reasoning). Delegating to peer agents that manage their own state keeps the primary agent's context clean — a direct lever on reliability and cost.
  3. Workload distribution. Instead of one team building an entire agentic system, different teams, vendors, or managed services can own specialized agents and keep improving them independently. The same modularity that made microservices manageable, applied to AI capabilities.
  4. Dynamic autonomy. Calling an API returns data or an error. Calling an A2A peer starts a collaboration: the peer can understand intent, refine the plan, and ask for missing input. That's what lets agents handle messy, real-world requests instead of only perfectly-formed ones.

The honest caveats, because pragmatic adoption means naming them:

  • Distributed agents are a distributed system. The moment work spans multiple autonomous agents, you inherit the hard parts of distributed computing: partial failures, latency, retries, debugging across boundaries, and emergent behavior that's harder to reason about than a single call stack. A2A standardizes the protocol, not the operational maturity you'll need.
  • A handoff is a trust and security decision. Delegating a task — especially one with side effects or sensitive inputs — means trusting another agent's identity, behavior, and data handling. A2A carries auth and identity mechanisms, but deciding what to trust is on you (this is exactly where Lab 02's discovery-and-verify discipline pays off).
  • It's powerful, not always necessary. Multi-agent collaboration shines for genuinely separable, specialized workloads. For a single well-scoped task, one agent with a few tools (Lab 04) is simpler and easier to operate. Reach for A2A when the problem is actually a division of labor — not by default.

A useful framing for a non-technical stakeholder: "A2A lets our agents work like a team: each one specializes, they can find each other and hand off work, and a specialist can keep its sensitive data and methods private while still delivering the result. It's how we go from one do-everything assistant to a coordinated workforce of agents."


Concepts you'll use (2-minute primer)

  • Agent Card — a JSON document an agent publishes (at /.well-known/agent-card.json) describing its identity, skills, capabilities, transport, and how to authenticate. It's how agents are discovered and understood. (This is the same application/a2a-agent-card+json artifact Lab 02's ARD registry indexes.)
  • Message — one unit of communication, with a role (user = client→agent, agent = agent→client) and parts (text, file, or structured data).
  • Task — a stateful unit of work the agent creates in response to a message. It has an id, a contextId (groups related tasks/messages), a status.state, and artifacts (the outputs).
  • Task lifecyclesubmitted → working → completed, with input-required (needs more input), failed, canceled, and rejected as other states. Terminal states end the task.
  • The core methodsmessage/send (send a message, get a Task or Message back), tasks/get (poll a task), tasks/cancel. (Streaming and push-notifications exist too, behind capability flags.)

The golden rule: an A2A call is a collaboration, not a function call. You send intent; the peer may complete it, ask for more, or push back.


Prerequisites

You needNotes
Python 3.9+Standard library only — nothing to install
Two terminalsOne for the agent, one for the client/orchestrator
~$0, no LLM requiredThe agents are deterministic so the protocol is the star
Get the starter repo. Files live in a2a-lab/: orders_agent.py, a2a_client.py, client_demo.py, orchestrator.py, plus a2a_types.py / orders_logic.py. Unzip and cd in.

Setup (1 minute)

bash
cd a2a-lab
python3 --version       # 3.9+; nothing to pip install

Pure standard library. (To use the official A2A SDK instead of this teaching implementation, see the README — the agent card and method names stay the same.)


Exercise 1 — Agent Cards and discovery (10 min)

Goal: Understand how one agent finds and understands another.

1a. Start the specialist and read its card

In terminal 1:

bash
python orders_agent.py

It serves an Agent Card at http://127.0.0.1:8010/.well-known/agent-card.json. In terminal 2, run the demo (it starts by fetching the card):

bash
python client_demo.py
text
=== 1. Discover the agent (read its card) ===
Name:   Acme Orders Agent
About:  Answers questions about Acme orders: status, totals, and delivery dates.
Skills: ['order-status']
Transport: JSONRPC  Streaming: False

Open orders_agent.py and look at AGENT_CARD. The fields that matter:

  • `skills` — what the agent can do, each with an id, description, tags, and examples. This is the menu other agents (and humans) read to decide whether to delegate here. Write examples like real requests — they're how a primary agent matches intent to a peer.
  • `capabilities` — optional features (streaming, pushNotifications). A client checks these before using an optional feature; using one that's off is an error.
  • `url` + `preferredTransport` — where and how to call the agent.

1b. Why a card, not just an endpoint

An Agent Card is self-description for autonomy. A primary agent doesn't need to be hardcoded against this peer — it can fetch the card at runtime, read the skills, and decide to delegate. That's what makes a collaborative ecosystem possible instead of brittle point-to-point wiring.

Business callout: the Agent Card is the unit of interoperability — and the bridge to the rest of this series. It's exactly the document Lab 02's ARD registry indexes and verifies. Discovery (ARD) finds the card; A2A is what you do with the agent once you've found and trusted it.

Checkpoint: You can discover an agent and read its advertised skills. Now put it to work.


Exercise 2 — Send a message, get a task with a lifecycle (12 min)

Goal: See why A2A models work as a task with state, not a one-shot API response.

2a. A complete request

Look at the second block of client_demo.py output:

text
=== 2. Send a complete request -> Task completes ===
Task 09c350c8  state=completed
Answer: Order A-1024 is shipped; estimated delivery 2026-06-25.
tasks/get -> state=completed

The client called message/send with a Message (role user, a text part). The agent created a Task, did the work, and returned it in state completed — with the answer delivered as an artifact, not just a string. The client then called tasks/get to fetch the same task by id.

Trace it in the code: a2a_client.send_message builds the message and calls message/send; orders_agent._handle_message_send creates the task and attaches the answer as an artifact via a2a_types.artifact(...).

2b. Why a task, not just a return value

A function call returns once and forgets. A task is stateful and addressable:

  • It has an id you can poll (tasks/get), cancel (tasks/cancel), or reference later.
  • It moves through states — submitted → working → completed — so a long-running job can report progress instead of blocking.
  • It carries artifacts (typed outputs) and a contextId that groups related work into a conversation.

Our work is instant, so the agent jumps straight to completed. But the shape is what scales to the long-running, autonomous jobs A2A is built for (think a research agent that runs for minutes).

Business callout: the task model is what lets agents take on real work, not just answer trivia. A delegated job that takes three minutes, reports progress, and produces a document is the same protocol shape as this instant lookup. That uniformity is why a primary agent can orchestrate fast and slow specialists the same way.

Checkpoint: You can delegate work and retrieve a stateful task. Next, the behavior that truly separates agents from APIs.


Exercise 3 — Multi-turn: when the agent asks you a question (12 min)

Goal: Handle input-required — an agent refining an incomplete request instead of failing.

3a. A vague request

Look at the third block of output:

text
=== 3. Vague request -> agent asks a clarifying question ===
Task 6778fcb4  state=input-required
Agent asks: Which order ID should I look up? (e.g. A-1024)
...replying with the order ID on the same task
Task 6778fcb4  state=completed
Answer: Order A-1099 is processing; estimated delivery 2026-06-29.

The client asked "Can you check on my order?" with no order ID. A REST API would 400. The agent instead transitioned the task to `input-required` and asked a question. The client then continued the same task — passing the original taskId and contextId back with the answer — and the agent finished the job.

Trace it in orders_agent._handle_message_send: with no order ID it returns a task in input-required; when a message arrives carrying an existing taskId, it resumes that task and completes it.

3b. The continuation rule

This is the heart of A2A multi-turn:

  • contextId groups everything in one conversation.
  • taskId identifies the specific unit of work.
  • To continue or refine a task, the client sends a new message with the same `taskId` and `contextId`. The agent picks up where it left off.

That's how agents conduct a back-and-forth — clarifying, refining, confirming — rather than demanding a perfectly-formed request up front.

Business callout: "the agent can ask a clarifying question" sounds small but it's the difference between automation that handles real, messy human requests and automation that only works on clean inputs. Most valuable enterprise tasks are underspecified at first; an agent that negotiates the details is far more useful than one that rejects anything imperfect.

Checkpoint: You can handle a clarifying turn and continue a task. Now make one agent delegate to another.


Exercise 4 — Orchestration: one agent delegating to another (12 min)

Goal: Build a primary agent that discovers a specialist and hands off work — the full A2A value proposition.

4a. Run the Concierge

With the Orders Agent still running in terminal 1:

bash
python orchestrator.py "is order A-1100 delivered?"
text
User: is order A-1100 delivered?
[concierge] discovered peer: Acme Orders Agent (skills: ['order-status'])
[concierge] delegating to Orders Agent: 'is order A-1100 delivered?'
Concierge: Here's what I found: Order A-1100 is delivered; estimated delivery 2026-06-18.

The Concierge (in orchestrator.py) is the agent a user talks to. It doesn't know how to look up orders — instead it: discovers the Orders Agent's card, recognizes the request is an orders question, delegates over A2A, and composes the user-facing reply from the specialist's artifact.

Try the clarification path through the orchestrator too:

bash
python orchestrator.py "can you check on my order?"

The specialist returns input-required, and the Concierge reports back that more info is needed — in a real deployment it would relay the question to the user and continue the task.

4b. The black-box handoff

Notice what the Concierge never touches: orders_logic.py. It doesn't see the order data, the lookup code, or the agent's internal state. It sent intent and received a result. That encapsulation is the "secret sauce" advantage — the specialist could be wrapping a private database, a proprietary model, or a regulated system, and the caller would be none the wiser.

4c. Where this sits in the stack

This closes the series loop. The Orders Agent is:

  • the application/a2a-agent-card+json capability Lab 02 (ARD) discovers and verifies before the Concierge trusts it;
  • a peer the Concierge could power with Lab 01 (Interactions API) reasoning;
  • internally, an agent that might expose its own tools via Lab 04 (MCP);
  • and something you'd hold to a quality bar with Lab 05 (Evals) before letting other agents depend on it.

Discover → trust → reason → integrate tools → evaluate → collaborate. You've now built every layer of the agentic stack.

Business callout: the Concierge + specialist split is how agentic systems scale organizationally. The orders team owns and improves the Orders Agent; the team building the Concierge consumes it via a card and a protocol, with no shared code and a private boundary in between. That's the same decoupling that let microservices scale across teams — now for AI capabilities.

Checkpoint: You have two agents collaborating: discovery, delegation, a clean handoff, and a composed result.


Wrap-up

You built and ran a multi-agent A2A system. You can now:

  • Publish and read an Agent Card for discovery (skills, capabilities, transport)
  • Delegate work with `message/send` and retrieve a stateful Task with artifacts via `tasks/get`
  • Handle `input-required` — continuing a task by reusing its taskId + contextId
  • Build a primary agent that delegates to a specialist as a private black-box handoff

The one idea to carry forward: A2A treats agents as collaborators, not endpoints. An agent call can complete, ask for more, or push back; work is a stateful task, not a one-shot response; and a specialist can deliver value while keeping its data and methods private. That combination is what turns a pile of individual agents into a coordinated team — with the operational caveat that you've also signed up for the realities of a distributed system.


Stretch challenges (for the labs series)

  1. Discover via ARD instead of a hardcoded URL. Publish the Orders Agent's card as an application/a2a-agent-card+json entry in a Lab 02 ARD catalog, sign it, and have the Concierge find and verify the peer before delegating. That wires Labs 02 and 06 together end to end.
  2. A real reasoning Concierge. Replace the Concierge's keyword routing with a Lab 01 model call that reads the peer's skills and decides whether/which peer to delegate to — true dynamic routing.
  3. Add a second specialist. Stand up a "Shipping Agent" with its own card and have the Concierge choose between peers by matching the request to each card's skills.
  4. Long-running tasks. Make a skill that returns submitted/working first (use returnImmediately) and have the client poll tasks/get until it reaches completed — the pattern real long-running agents use.

Troubleshooting

SymptomLikely cause / fix
urlopen error / connection refusedStart python orders_agent.py in another terminal first.
Card fetch 404Use the exact path /.well-known/agent-card.json.
Clarification loop never completesContinue with the same taskId and contextId and include an order ID (e.g. A-1099).
Task not found from tasks/getTasks are in-memory; restarting the agent clears them.
Port 8010 in useChange PORT in orders_agent.py and the URLs in the client/orchestrator.
Want real streaming/authMove to the official A2A SDK; keep the same card + method names.

References

Technical details follow the A2A 1.0 specification and the launch announcement:

A note on accuracy for your readers: this lab is a stdlib teaching implementation of the A2A wire protocol so it runs offline; the official SDKs add streaming, push notifications, auth, and richer types. The agent card fields, message/send / tasks/get methods, and task states follow the 1.0 spec — but pin the spec/SDK version you target and re-verify before publishing, since A2A is actively evolving.
WG

Written by Wes Goldwater, Director of Engineering at Prosigliere.

Hands-on cloud & pragmatic AI from Goldwater.dev.