A2A & AG-UI protocols¶
At a glance
Priority: P1 · Complexity: ⅗ · Est. time: 3 h · Phase: 3 · Prereqs: MCP, Multi-agent systems, API design You're done when: the capstone exposes its "customs specialist" sub-agent over A2A (discoverable Agent Card, task lifecycle, streaming) and its orchestrator streams to a web UI over AG-UI, and you can explain where MCP, A2A and AG-UI each sit and why you'd never swap them.
Why it matters¶
MCP standardised agent ↔ tools. Two more boundaries remain in any real system:
- Agent ↔ agent across teams, vendors and frameworks — A2A (Agent2Agent). Originated at Google (2025), now under the Linux Foundation; v1.0 announced 2026-03-12 with signed Agent Cards and JSON-RPC / gRPC / HTTP+JSON bindings. Supported by Foundry Agent Service (A2A v1.0 GA), Bedrock AgentCore Runtime, ADK, MS Agent Framework, Pydantic AI and others.
- Agent ↔ user interface — AG-UI (from CopilotKit): an event-streaming protocol so any agent backend can drive any frontend with text streaming, tool-call visibility, shared state and human approvals. Adopted by MS Agent Framework, ADK, AWS, LangGraph, Pydantic AI and more.
For an architect these are integration contracts. The question is not "which is cool" but "what boundary am I crossing, who owns each side, and what lifecycle does the interaction have?"
Core concepts¶
The three-protocol stack¶
flowchart LR
U[User / web app] <-->|AG-UI<br/>event stream| O[Orchestrator agent<br/>LangGraph]
O <-->|MCP<br/>tools & data| T1[ops-tools MCP]
O <-->|MCP| T2[runbooks MCP<br/>Spring AI]
O <-->|A2A<br/>tasks| C[Customs agent<br/>other team, ADK]
O <-->|A2A| P[Partner carrier agent<br/>external org]
C <-->|MCP| T3[customs DB MCP]
| Boundary | Protocol | Unit of work | Lifecycle | Opacity |
|---|---|---|---|---|
| Agent → tool/data | MCP | Tool call / resource read | Mostly request/response (Tasks extension for long jobs) | Tool is transparent: schema known |
| Agent → agent | A2A | Task containing messages and artifacts | Long-running, multi-turn, stateful, input-required, push notifications | Remote agent is opaque: no shared memory, tools or prompts |
| Agent → UI | AG-UI | Run producing a stream of typed events | Streaming per run; bidirectional (user input, approvals, state) | UI sees events, not internals |
Rule of thumb: if the other side reasons autonomously and is owned by someone else, it's A2A; if it's a function, it's MCP; if a human is watching, it's AG-UI.
A2A in depth (v1.0)¶
Discovery — the Agent Card. JSON metadata served at /.well-known/agent-card.json: name, description, provider, version, supported interfaces/transports and URLs, capabilities (streaming, push notifications), security schemes, default input/output modes, and skills (id, description, tags, examples). v1.0 adds Agent Card signing so clients can verify the card wasn't tampered with, and an authenticated extended Agent Card (GetExtendedAgentCard) for capabilities only shown to authorised callers.
Core operations (same semantics across JSON-RPC, gRPC, HTTP+JSON bindings): SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, and push-notification config CRUD.
Data model. A Message (role user/agent) contains Parts (text, file, structured data). Work happens in a Task with an id, optional contextId grouping related tasks, a status, history, and Artifacts (the outputs). v1.0 removed the kind discriminator field (breaking change from 0.3).
Task states:
stateDiagram-v2
[*] --> SUBMITTED
SUBMITTED --> WORKING
WORKING --> INPUT_REQUIRED: needs clarification/docs
INPUT_REQUIRED --> WORKING: client sends message
WORKING --> AUTH_REQUIRED: needs credentials
AUTH_REQUIRED --> WORKING
WORKING --> COMPLETED
WORKING --> FAILED
WORKING --> CANCELED
COMPLETED --> [*]
FAILED --> [*]
CANCELED --> [*]
(Wire names are TASK_STATE_SUBMITTED, TASK_STATE_WORKING, … in v1.0.)
Long-running patterns: streaming (SSE / gRPC stream of status and artifact update events), polling GetTask, or push notifications to a client webhook (authenticate the webhook! it's an SSRF and spoofing vector).
Security: standard schemes declared in the card — API key, HTTP bearer, OAuth 2.0 (auth code, client credentials, device code), OpenID Connect, mTLS. Treat every remote agent's output as untrusted input (OWASP ASI07 Insecure Inter-Agent Communication).
Exposing an agent over A2A¶
# uv add "a2a-sdk[http-server]"
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
class CustomsExecutor(AgentExecutor):
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
# 1. get/create task from context 2. enqueue WORKING status
# 3. run your agent 4. enqueue artifact update 5. enqueue COMPLETED
...
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
...
# Wire with DefaultRequestHandler + an in-memory/SQL task store + A2AStarletteApplication
# (see the SDK's samples; class names change between minor releases).
Version check
a2a-sdk implements A2A 1.0 with backward compatibility for 0.3 (as of Sept 2026). Framework helpers (to_a2a()) may lag the spec; check which protocol version they emit in the Agent Card.
AG-UI in depth¶
AG-UI defines a small set of typed events streamed from agent to UI over HTTP (SSE) or WebSockets, plus inputs from UI to agent:
| Category | Events (examples) | UI use |
|---|---|---|
| Lifecycle | RUN_STARTED, RUN_FINISHED, RUN_ERROR, step started/finished |
Spinners, error banners |
| Text | TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END |
Token streaming |
| Tools | TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, results |
"Looking up MSK-123…", approval buttons |
| State | STATE_SNAPSHOT, STATE_DELTA (JSON Patch) |
Shared state: live investigation board |
| Other | reasoning/thinking steps, custom events, raw | Show plan, domain widgets |
Why it matters: without it, every team invents its own websocket JSON for "tool started", and front-ends are coupled to one framework. With AG-UI, the React app (e.g. CopilotKit) works whether the backend is LangGraph, Pydantic AI, ADK or Agent Framework. Frontend tools (the agent asks the UI to do something — open a map, confirm a refund) and human-in-the-loop approvals become standard.
# Pydantic AI → AG-UI (ASGI app streaming AG-UI events)
from pydantic_ai import Agent
copilot = Agent("openai:gpt-5.2", instructions="You are the Ops Copilot.")
app = copilot.to_ag_ui() # serve with uvicorn; point CopilotKit / an AG-UI client at it
For LangGraph orchestrators, use the AG-UI LangGraph integration (listed in AG-UI docs) so graph node transitions and interrupts become AG-UI events.
A2A vs "just call their REST API"¶
| Plain REST/gRPC | A2A | |
|---|---|---|
| Contract | Bespoke per service | Standard task/message/artifact model |
| Long-running + clarification | You design it | Built-in (INPUT_REQUIRED, streaming, push) |
| Discovery | Docs/wiki | Agent Card, registries |
| Best when | Deterministic service, stable schema | Autonomous agent, open-ended requests, cross-org |
Don't A2A-ify deterministic services. The overhead (task store, lifecycle) only pays when the remote side is genuinely an agent.
Senior-level nuance¶
- Opacity is a feature and a risk. You don't see the remote agent's tools or prompts — good for encapsulation, bad for debugging and safety. Demand trace-context propagation and SLAs in the Agent Card/contract.
- Trust boundaries multiply. Remote agent output can carry prompt injections into your orchestrator (goal hijack via A2A). Sanitise, constrain with structured artifacts, and never auto-grant tools based on remote text.
- Idempotency and cancellation across A2A: retries can create duplicate tasks; use client-side dedupe keys in message metadata and implement
CancelTaskproperly. - Cost attribution: a single user request can fan out into A2A tasks billed to another team. Propagate a cost-center/tenant header and budget.
- AG-UI state deltas are JSON Patch — design your shared state schema deliberately; don't stream your whole LangGraph state (PII, size).
Resources¶
| Resource | Type | Why this one | Level | Cost |
|---|---|---|---|---|
| A2A protocol site | docs | Official: concepts, tutorials, what's new in v1.0 | intermediate | free |
| A2A specification | docs | Operations, task states, security, bindings | advanced | free |
| a2a-python SDK | docs | Official Python SDK; samples link | intermediate | free |
| A2A spec repo | docs | Source of truth, discussions, proto definitions | advanced | free |
| AG-UI docs | docs | Event types, integrations, protocol stack positioning | intermediate | free |
| AG-UI GitHub |
docs | SDKs and framework integrations with runnable demos | intermediate | free |
| Pydantic AI docs | docs | to_a2a() and AG-UI adapters — quickest way to try both |
intermediate | free |
| Foundry: agent overview (A2A publishing) | docs | How a managed platform exposes A2A v1.0 endpoints | intermediate | free |
Hands-on lab¶
Goal (90 min): give the capstone a cross-team agent and a live UI.
- Customs agent over A2A. Build a Pydantic AI (or ADK) customs agent with an MCP tool
get_customs_status. Expose viato_a2a()on port 9000.curl http://localhost:9000/.well-known/agent-card.jsonand inspect skills/capabilities. - Orchestrator calls it. In the LangGraph orchestrator, add a node
ask_customsthat sends an A2A message via thea2a-sdkclient and streams status. Force anINPUT_REQUIREDpath: the customs agent asks for an HS code when missing; the orchestrator routes this back to the user. - Propagate trace context (W3C
traceparent) in A2A message metadata/headers; verify one trace spans orchestrator → customs agent → MCP in Langfuse. - AG-UI. Expose the orchestrator via AG-UI; use a minimal CopilotKit/Next.js app or the AG-UI dojo to show token streaming, a "tool running" indicator and an approval button for
restart_consumer. - Security. Require a bearer token on the A2A endpoint (declared in the Agent Card security schemes); add a test that an unauthenticated
SendMessagefails.
Expected output: a demo where the user asks "clear MSK-123 through customs", sees streaming progress in the UI, is asked for the HS code (relayed from the remote agent), and gets a final artifact; one trace in Langfuse covers all hops.
Questions¶
L1 — Recall¶
Q1. Where is an A2A Agent Card served and what does it contain?
Answer
At /.well-known/agent-card.json. Name, description, provider, version, interfaces/URLs and transports, capabilities (streaming, push notifications), security schemes, default input/output modes, and skills. v1.0 supports signed cards and an authenticated extended card.
Q2. List the A2A task states.
Answer
Submitted, working, input-required, auth-required, completed, failed, canceled (wire names TASK_STATE_*). Completed/failed/canceled are terminal.
Q3. What problem does AG-UI solve, and name four of its event types.
Answer
Standardises the agent→UI streaming contract so frontends aren't coupled to one agent framework. Events: RUN_STARTED, TEXT_MESSAGE_CONTENT, TOOL_CALL_START, STATE_SNAPSHOT, STATE_DELTA, RUN_FINISHED, RUN_ERROR.
L2 — Apply¶
Q4. The remote customs agent takes up to 4 hours. Your orchestrator runs in a request/response API with a 60 s timeout. Design the interaction.
Answer
Send the message, receive a task id immediately, persist it in the orchestrator's durable state (LangGraph checkpoint), and end the current run with a "pending" status to the user. Register a push-notification webhook (authenticated, validated) or poll GetTask from a scheduler; on completion, resume the graph thread and notify the user (AG-UI/email). Handle INPUT_REQUIRED by surfacing a question to the user and sending a follow-up message on the same task.
Q5. How do you prevent a compromised partner agent from hijacking your orchestrator via A2A responses?
Answer
Treat artifacts as untrusted data: require structured artifacts validated by schema, never execute instructions from them, render free text in a quarantined/"data" section of the prompt or process it with a tool-less quarantined LLM (dual-LLM pattern), restrict which tools can be invoked after reading remote content, and apply output filtering. Verify signed Agent Cards and pin the endpoint.
Q6. Your React team wants to show 'Checking shipment MSK-123…' while tools run and an Approve button before writes. Which AG-UI features do you use?
Answer
TOOL_CALL_START/ARGS/END events for progress display; a human-in-the-loop interrupt/frontend tool for approval (the agent pauses on the write tool, UI renders Approve/Reject, sends the decision back as input); STATE_DELTA to update a shared investigation panel.
L3 — Design & trade-offs¶
Q7. A teammate wants to expose every internal agent as an MCP tool instead of adopting A2A. Evaluate.
Answer
Works for short, synchronous delegation where the caller wants a single result (agent-as-tool). Breaks down for multi-turn clarification, long-running work, streaming status, per-agent identity/auth, cancellation and discovery of skills — you'd reinvent A2A on top of MCP. Recommend: MCP for tools and simple agent-as-tool delegation inside a team; A2A at team/org boundaries or where the lifecycle matters.
Q8. Build vs adopt AG-UI for a single internal UI tied to one LangGraph backend.
Answer
Bespoke streaming is simple initially and avoids a dependency. AG-UI pays off with multiple backends/frameworks, reuse of CopilotKit components, standard HITL/frontend-tool semantics, and future migrations. If the backend may change (e.g. move a sub-agent to Agent Framework on Foundry), or other UIs will consume it, adopt AG-UI; cost is small because integrations exist for LangGraph/Pydantic AI.
Q9. What must an A2A contract between two companies specify beyond the protocol itself?
Answer
Auth scheme and credential lifecycle, data classification/residency and retention of task history, SLAs (latency, availability, max task duration), cost/billing per task, rate limits, error semantics, versioning of skills and deprecation, audit and trace sharing, liability for agent actions, and incident contacts.
L4 — Staff-level ambiguity¶
Q10. Your org wants an 'agent marketplace' where teams publish agents for others. Design the governance.
Answer
Registry of signed Agent Cards (owner, data classification, SLA, cost, eval scores); publishing pipeline (security review, red-team eval, trace instrumentation required, auth via corporate IdP); runtime gateway enforcing authZ between agents, quotas and cost attribution; versioning/deprecation policy; observability with cross-agent traces; kill switch per agent; periodic re-certification. Start with 3-5 high-value agents, not a free-for-all.
Q11. The protocols are still moving (A2A 0.3 → 1.0, MCP stateless). How do you avoid churn while still adopting?
Answer
Isolate protocol handling in thin adapters (one module per protocol), keep domain logic protocol-agnostic, pin SDK versions, maintain contract tests per boundary, support N and N-1 during transitions (A2A SDK supports 1.0 + 0.3), and follow the spec's deprecation windows. Adopt where interoperability value is concrete (cross-team/partner), defer where a local function call suffices.
Real-world use cases¶
- Carrier–shipper collaboration: a shipper's planning agent sends A2A tasks to a carrier's booking agent; input-required states handle missing dangerous-goods declarations.
- Enterprise help desk: an orchestrator delegates to HR, IT and finance agents owned by different departments via A2A; one AG-UI chat frontend in Teams/web.
- Ops copilot UI: AG-UI streams an incident investigation with a live shared state board (hypotheses, evidence, actions) and approval buttons for remediations.
- Managed publishing: Foundry Agent Service publishes agents with A2A endpoints; AgentCore Runtime hosts A2A servers (Managed platforms).
Pitfalls & anti-patterns¶
- Using A2A for deterministic services (overhead with no benefit).
- Trusting remote agent output as instructions.
- Unauthenticated push-notification webhooks.
- No trace propagation across agent boundaries — undebuggable fan-outs.
- Streaming full internal state (with PII) to the UI via
STATE_SNAPSHOT. - Coupling the frontend to a framework's private event format.
- Ignoring task cancellation and idempotency.
Checklist¶
- I can place MCP, A2A and AG-UI on a diagram and justify each boundary
- I exposed an agent over A2A and consumed it from the orchestrator, including input-required
- I streamed the orchestrator to a UI via AG-UI with an approval step
- I verified one trace across all hops
- I answered all L3 questions out loud in < 3 min each