Skip to content
abhinavbhatia
Sheet 02 / 05 — Blogs← Archive

Production-ready agents, Part 12.75: How agents show more than text: A2UI, AG-UI and MCP Apps

Agents can answer with cards, forms and charts, not only text, and show progress and thinking while they work. This part explains A2UI, AG-UI and MCP Apps, how Gemini Enterprise and ADK support them, how to stream progress and thought summaries safely, and how Acme's two builds compare.

Until now, Acme’s agents have answered in text. Maria asks what the stockouts cost, and she gets a paragraph with the figures in it. That works, but it is not always the best answer. A comparison of three brands reads better as a small table. A replan that needs approval is clearer as a card with an Approve button. A follow-up such as “break that down by week” is quicker as a button than as another sentence to type.

Three open standards now cover this, and their names are easy to mix up: A2UI, AG-UI and MCP Apps. They are not competitors. Each one standardises a different piece of the path between an agent and the screen. Part 12 and Part 12.5 chose the front doors people use to reach Acme’s agents; this part is about what those front doors can show.

Where are we in the architecture?

This part adds no new box. It explains what the front doors can show: A2UI cards in Gemini Enterprise (the arrow into the router is labelled A2A and A2UI), AG-UI for Acme’s own app, and MCP Apps. Part 1.25 walks through the whole picture, layer by layer.

ACME ARCHITECTURE · PART 12.75 AGENT RUNTIME (PART 11) each agent is its own engine with its own identity A2A · A2UI A2A A2A Shopper Acme's customers P11.75 GE for CX customer chat, voice, reps P11.75 Legal edition industry skills, connectors P11.75 User Maria in marketing P1 Gemini Enterprise where people use agents P12 Other front doors own app (AG-UI), Chat, Slack P12.5 Plugins + Model Armor · checks around every step P9 Router agent Workflow: extract, route, write P1 Supply agent Databricks specialist P3 Marketing agent BigQuery specialist P2 Code sandbox runs generated code P3.5 Skills versioned in Agent Registry P4 Sessions + Memory Bank what it remembers about each user P8 AGENT GATEWAY · EGRESS (FORWARD PROXY) · P12 Gemini models the reasoning engine P1 Other models Model Garden P13 Knowledge RAG and search P14 BigQuery marketing data P2 Databricks supply data P3 Market watch managed agent, public web P1.75 Agent Identity one per agent P7 Auth manager acts for the user P7 Secret Manager no secrets in code P11 Human approval for the one write P9 Cloud Build CI on every change P11 Eval gate blocks a worse agent P10 Cloud Trace one per question P11 Cloud Logging redacted events P11 Agent Analytics BigQuery plugin P9 IDENTITY AND SAFETY SHIPPING MODELS, KNOWLEDGE, DATA OBSERVABILITY deploy this part earlier parts later parts Pn = the part that adds it

where Part 12.75 sits in the Acme architecture: highlighted boxes are the subject of this part, solid boxes come from earlier parts, faded boxes arrive later

What do I need to know first?

  • Generative UI. An interface that an agent chooses or assembles at run time, instead of one a developer laid out in advance. “Show these three numbers as a table with a ‘by week’ button” is a generative UI decision.
  • Declarative vs executable. A declarative UI is a description (“a table with these columns”) that the app turns into its own components. An executable UI is code (HTML and JavaScript) that runs as it is. Declarative is safer, because nothing the agent sends is run; executable is more flexible.
  • Renderer and catalog. The renderer is the part of the app that draws the interface. A catalog is the list of components (text, button, table) that the renderer agrees to draw.
  • Sandboxed iframe. A browser frame that runs a page with restricted access to the page around it, so the code inside cannot read the host page or its cookies.
  • Event stream. A sequence of small typed messages (“text started”, “tool call started”, “state changed”) sent as they happen, usually over Server-Sent Events or WebSockets.

What does each standard do?

A2UI describes what to show, AG-UI carries the conversation between an app and an agent, and MCP Apps let a tool ship its own screen. They solve three different problems, so one system can use all three at once.

Where people get tripped up is the names: they sound like three rival answers to one question, so teams ask which one to pick. A restaurant makes the difference easier to see:

Restaurant                                         Agent UI term
A menu: the dishes this kitchen knows how to cook  catalog
An order ticket: "one soup, one salad, extra       A2UI message (a description,
bread", naming dishes from the menu                not code)
The kitchen cooks each dish its own way, on its    renderer (the app draws its own
own plates                                         components and styling)
The ticket rail and the pass: orders go in,        AG-UI (the event stream, in both
"soup ready" and "table 4 wants the bill" come     directions)
back, and the rail never decides what a dish is
A guest caterer's finished dish, served on a       MCP App (HTML from a tool's
separate tray so it never touches the kitchen      server, in a sandboxed iframe)

A customer cannot send raw ingredients into the kitchen through the ticket, which is why a description is safer than code. The guest caterer can serve anything, which is why their dish stays on its own tray.

How does this apply to Acme? The same three jobs appear in one afternoon for Maria. Her stockout answer in Gemini Enterprise is an order ticket: the router names a column, a title, the figures and a button, and Gemini Enterprise draws them with its own styling (A2UI). If she asks the same question in Acme’s own web app, the stream that carries “asking supply…” and the answer back, and her click on “by week” the other way, is the ticket rail (AG-UI). And if another team calls get_spend over MCP and sees an interactive chart drawn by the tool’s own page, that is the guest caterer’s dish (an MCP App).

THREE STANDARDS, THREE DIFFERENT JOBS Front door / your app a renderer with a catalog of trusted components Acme: Gemini Enterprise, or Acme's own web app Agent decides what to show Acme: the router AG-UI: the connection events back: clicks, approvals A2UI: what to show JSON components, no code travels inside AG-UI events or inside an A2A message MCP host sandboxed iframe runs HTML MCP server a tool plus its own HTML page (an MCP App) MCP Apps: a tool's own UI executable, so it is sandboxed

three standards for agent UI: AG-UI is the event stream between a frontend app and an agent; A2UI is a JSON description of components that can travel inside that stream or inside an A2A message, drawn by the app's own renderer; MCP Apps are HTML pages from an MCP server, run in a sandboxed iframe by the host

One sentence each, from their own documentation:

  • A2UI (Agent-to-User Interface) describes what to show. Its site says it lets agents “generate rich, interactive user interfaces that render natively across web, mobile, and desktop”, “without executing arbitrary code”. The agent sends JSON that names components from a catalog; the app draws them with its own components and styling. Google made the project public in December 2025, noting that “Orchestrator agents and remote A2A subagents can all generate UI layouts which are securely passed as messages, not as executable code”.
  • AG-UI (Agent-User Interaction Protocol) is the connection between an app and an agent. Its documentation calls it “an open, lightweight, event-based protocol that standardizes how AI agents connect to user-facing applications”: the stream of text, tool calls, state and interruptions between your frontend and the agent behind it. It does not define what a component looks like; it can carry A2UI.
  • MCP Apps let a tool bring its own interface. The overview says they “let servers return interactive HTML interfaces (data visualizations, forms, dashboards) that render directly in the chat”, and that they “run in a sandboxed iframe controlled by the host”. This is the executable kind: real HTML from the MCP server.

The AG-UI documentation draws the same line: A2UI is a generative UI specification “allowing agents to deliver UI widgets”, while AG-UI “connects an agentic frontend to any agentic backend”. So the question is never “A2UI or AG-UI?” but “what am I describing, and how does it travel?”.

What does an A2UI message look like?

An A2UI message is a short list of instructions: open a space, put these named components in it, and tell me if a button is pressed. It contains data and component names, never code.

Start with the smallest possible card, in plain words. Suppose an app wants to show a greeting with one button:

1. Open a surface called "greeting".                      (createSurface)
2. Put a Column in it, holding "title" and "ok".          (updateComponents)
3. "title" is a Text component showing the data value
   /name, which is "Sam".
4. "ok" is a Button; when pressed, send event "ok_pressed".
5. Later: set /name to "Maria". The title changes
   without resending the layout.                          (updateDataModel)

The app reads the five lines, draws a column, a line of text and a button in its own style, and sends ok_pressed back when the button is pressed. Each component points to its children by ID, so the list stays flat, and because the text is bound to a data value, the app can change what it says without redrawing the layout. (A plain-words sketch of the A2UI message flow, not exact syntax.)

How does this apply to Acme? The same steps, with real message names.

A2UI sends a few message types: createSurface opens a place to draw, updateComponents sends the components as a flat list linked by IDs, updateDataModel changes the data without resending the layout, and deleteSurface removes it. Here is Acme’s answer card for Maria’s stockout question, in the v0.9.1 format (the current A2UI release, as of October 2026):

{ "version": "v0.9.1",
  "createSurface": { "surfaceId": "stockout_answer",
                     "catalogId": "https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json" } }

{ "version": "v0.9.1",
  "updateComponents": { "surfaceId": "stockout_answer", "components": [
    { "id": "root",    "component": "Column", "children": ["title", "figures", "by_week"] },
    { "id": "title",   "component": "Text", "text": "Acme Pure stockouts, Western Europe" },
    { "id": "figures", "component": "Text",
      "text": "Penalties EUR 36,026 · OTIF 87.4% · 15,011 units unmet" },
    { "id": "by_week", "component": "Button", "child": "by_week_label",
      "action": { "event": { "name": "breakdown_by_week" } } }
    // by_week_label (a Text component) omitted for brevity
  ] } }

(Message types and field names from the A2UI v0.9.1 specification; the Acme components are illustrative, and the exact component properties depend on the catalog.) Three things to notice. Nothing in it is code: a renderer that does not know a component simply does not draw it. The figures are plain text that Acme’s number check (Part 5) has already verified before the card is built, so the card cannot show a number the check did not see. (Acme’s real card is richer: KPI tiles, a table, a chart and follow-up chips, built from Gemini Enterprise’s published composite catalog of Material components, such as MaterialCard, MaterialTable and InteractiveChart, with a plain v0.8 card as the fallback.) And the button sends an event back, not a command: when Maria clicks it, Acme’s UiActionPlugin (Part 9) turns the click into a typed request, “break that down by week”, which then goes through the same guardrails as anything she types.

What does an AG-UI stream look like?

An AG-UI stream is a running commentary of small, typed events, and the app decides how to show each one. The agent never draws the screen; it reports what is happening.

A parcel tracking page works the same way. The courier’s system sends short status events, and the page turns each into a line or a tick:

OrderPlaced        order 4471
Packed
OutForDelivery     "driver: Sam, 12 stops away"
AddressConfirmed   {"door": "side entrance"}      (you changed it, the courier now knows)
Delivered

The courier does not send the page’s layout, only what happened, in order. The page could show the same events as a timeline, a progress bar or a single line, and the fourth event travels the other way: your change reaches the courier while the delivery is still running.

How does this apply to Acme? AG-UI is a sequence of events. For the same question, from Acme’s own web app, the stream between the app and the router would look like this (event types as named in the AG-UI documentation):

RunStarted
StateSnapshot        {"brand": "Acme Pure", "region": "Western Europe"}   (the app's current filters)
ToolCallStart        delegate to supply
ToolCallEnd
TextMessageStart
TextMessageContent   "Penalties were EUR 36,026 in Western Europe..."
TextMessageEnd
RunFinished

The app can show progress as it happens (“asking supply…”), keep its filters in sync with the agent through state events, and pause for approval when the agent asks. The mapping to the parcel page is one to one: RunStarted is the order placed, ToolCallStart is out for delivery, StateSnapshot is the shared detail both sides can see (the address there, Maria’s brand and region filters here), and RunFinished is delivered. ADK supports AG-UI: ADK’s integration page describes it as “an open protocol that handles streaming events, client state, and bi-directional communication between your agents and users”, with CopilotKit as one client.

What does an MCP App look like?

An MCP App is a tool that brings its own web page, and the host runs that page in a locked box. This is the one standard of the three where real code from someone else runs on the user’s screen.

You have seen the simple version on ordinary websites: a news article with a video player or a map embedded from another site. The article does not rebuild the player; it gives the other site a framed rectangle (an iframe) and lets that site’s page run inside it. The browser keeps the two apart, so the embedded player cannot read the article’s cookies or change the rest of the page. The article gets a rich widget it did not have to build, at the cost of trusting the other site enough to embed it.

How does this apply to Acme? The tool’s server plays the other site, and the chat plays the article.

An MCP App is an HTML page that an MCP server offers alongside a tool. When the tool runs, a host that supports MCP Apps renders that page in a sandboxed iframe inside the conversation. At Acme, this is how it would work: get_spend is offered to other teams as an MCP server (the example in Part 1.9), and it attaches an interactive spend chart, and any MCP host that supports MCP Apps would show the chart instead of a line of text. ADK’s MCP tool class can read the UI resource a tool declares for its MCP App, but the page itself only renders where the host supports MCP Apps.

The trade-off is the one from the first section. An MCP App can draw anything a web page can, which is why it is useful for rich charts and maps, and why it runs sandboxed: the host is running code written by the server’s author.

How do they compare?

Pick by what you need to send: a description (A2UI), a live connection (AG-UI) or a tool’s own page (MCP Apps). Side by side:

                A2UI                      AG-UI                       MCP Apps
Standardises    what to show              the app-to-agent connection what a tool shows
What travels    JSON describing           a stream of typed events    HTML from the MCP server
                components                (text, tools, state)
Runs code?      no; the app draws its     not a UI format; it can     yes, in a sandboxed iframe
                own components            carry A2UI
Who renders     the client's renderer,    your own frontend           the MCP host
                from a catalog
Acme use        answer cards in Gemini    Acme's own web app          a chart for teams that use
                Enterprise                (Part 12.5)                 get_spend over MCP

Which do I need? I would ask these in order, cheapest and safest first, and stop at the first yes:

1. Is a sentence enough?                  plain text, no UI standard at all
   ("Penalties were EUR 36,026.")
2. Can a card from a fixed catalog show   A2UI, drawn by the front door's renderer
   it? (figures, a table, a button)       (Acme's answer card in Gemini Enterprise)
3. Do you own the frontend and need live  AG-UI between your app and the agent,
   progress, shared state or approvals?   carrying A2UI cards when needed
                                          (Acme's own web app, Part 12.5)
4. Only then: does a tool need a custom,  an MCP App, from a server you trust,
   interactive widget no catalog has?     in the host's sandbox
   (a map, a drag-to-explore chart)

Each step adds power and adds something to trust. Step 2 runs no code from the agent, step 3 adds a connection you have to build and secure, and step 4 runs another author’s code on the user’s screen.

Where does this sit in Acme’s architecture?

UI is a property of the front doors, not a new box: Gemini Enterprise draws A2UI, Acme’s own app would speak AG-UI, and MCP Apps stay off the main path.

UI is not a new box; it is a property of the front doors and of the arrows into the router. The architecture diagram now shows it in three places:

  • Gemini Enterprise renders A2UI. Google’s registration page says “Gemini Enterprise supports all the standard A2UI components”, which “render using Gemini Enterprise’s built-in styling”, and that it supports A2UI versions v0.9 and v0.8. The page describes registering such agents with A2A, so the arrow from Gemini Enterprise to the router is labelled “A2A · A2UI”. Part 12 compares the two registration types; an agent that should send A2UI cards to Gemini Enterprise follows the A2A path.
  • Acme’s own app speaks AG-UI. At Acme, this is how it would work: its own web app would be the second front door (Part 12.5), AG-UI the natural connection between that app and the router, and the app’s renderer would draw A2UI cards the router sends.
  • MCP Apps belong to MCP servers. They only matter if Acme publishes MCP servers for other teams, so they stay out of the main path.

And the rules from the rest of the series still hold:

  • The number check comes before the card. A card is only another way to show an answer, so the figures in it must be the verified ones. Build the card from the checked findings, not from the writer model’s prose.
  • Clicks are input. A button click is user input like typed text, and it goes through the same guardrails. That is what UiActionPlugin does (Part 9).
  • No secrets in the surface. Anything in a card’s data model can be seen by the client; tokens stay in metadata (Parts 6 and 7).

ADK also has an A2UI integration: an A2UI agent SDK (a2ui-agent-sdk) whose schema manager “loads component catalogs and generates system prompts that teach the LLM how to produce valid A2UI JSON”. My view: for numbers that must be exact, I would rather build the card in code from the verified findings than ask the model to write the JSON, for the same reason the series keeps every figure out of the model’s hands.

How do you show progress and thinking while the agent works?

Stream what the code actually did, show the model’s own account of its thinking only with care, and hold back the answer until it has been checked. Those are three different things, and only the first is always true.

A taxi app shows all three kinds of message while you wait:

"Driver assigned: Sam, grey Prius"     from the booking system: a fact
"Sam is 3 minutes away"                from the car's GPS: a fact
"Sam: nearly there, just parking!"     the driver's own message: probably true, not proof
"Fare: GBP 14.20"                      shown at the end, once the trip is measured

The first two lines come from systems that record what happened, so you can rely on them. The driver’s message is useful and reassuring, but it is the driver describing himself; if he is still two streets away, the message is still sent. And the app does not show a running fare as the final price, because the price is only right once the trip is over.

How does this apply to Acme? The three kinds map one to one: progress lines from code are the booking system and the GPS, thought summaries are the driver’s message, and the final answer is the fare.

Part 11’s latency section explains why this matters: the first token can leave the model in a fraction of a second while the first word of the answer takes several seconds, and progress updates are what the user sees in between.

A delegated question can take ten or twenty seconds: the router reads the question, asks supply over A2A, waits for the query, writes and checks the answer. A blank screen for that long feels broken. So the second UI question, after “what does the answer look like?”, is “what does the user see while the agent works?”. There are three things you can stream, and they are not equally safe:

  • Progress from code. “Asking the supply assistant…”, “Running the reviewed penalty analysis…”. These come from what the system actually did (the trace), so they are always true.
  • Thought summaries from the model. Gemini can return summaries of its reasoning. Google’s thinking guide says “Thought summaries provide insights into the model’s internal reasoning process”, that “By default, only the final output is returned”, and that you can “Use streaming to receive incremental thought summaries during generation”. In the generateContent API, which ADK uses, the switch is ThinkingConfig(include_thoughts=True), and summary parts arrive marked thought=True; the newer Interactions API calls the same option thinking_summaries. These are summaries, not the raw reasoning, and they are the model’s account of itself, not evidence.
  • The answer as it is written. Partial text, token by token. Acme does not stream the final answer to the user, because the number check (Part 5) can only run on the finished text.

How does it reach the screen in Gemini Enterprise?

Send progress yourself, as A2A working status updates; that is the documented route. It works like a courier pushing tracking updates to the page: Gemini Enterprise shows whatever status text your agent sends, and the answer arrives as the final delivery.

Through A2A streaming. Gemini Enterprise’s A2A registration page says it “supports the A2A v0.3 streaming mechanism”: the agent answers message/stream with Server-Sent Events, and each event is a task, a status update or an artifact update. A status update in the working state can carry a short message, and that message is what the user sees while they wait. The final artifact carries the answer and the A2UI card. In the v0.3 shape:

// one SSE event per line of progress, while the agent works
{"jsonrpc": "2.0", "id": 1, "result": {
  "kind": "status-update", "taskId": "t-1", "contextId": "c-1", "final": false,
  "status": {"state": "working", "message": {"kind": "message", "role": "agent",
    "parts": [{"kind": "text", "text": "Asking the supply assistant over A2A..."}]}}}}

// then the answer: text plus A2UI parts (each A2UI message is a data part)
{"jsonrpc": "2.0", "id": 1, "result": {"kind": "artifact-update", "taskId": "t-1", "lastChunk": true,
  "artifact": {"parts": [{"kind": "text", "text": "..."},
                         {"kind": "data", "data": {...}, "metadata": {"mimeType": "application/json+a2ui"}}]}}}

// and the end of the task
{"jsonrpc": "2.0", "id": 1, "result": {"kind": "status-update", "taskId": "t-1",
  "status": {"state": "completed"}, "final": true}}

(A2A v0.3 field names, as Gemini Enterprise speaks them; IDs and text are illustrative.) Two other routes come up, and neither is as dependable today. A community thread reports that an ADK agent with thoughts turned on sends them through A2A as metadata (adk_thoughts), and that in Gemini Enterprise they ended up in the final message rather than in the response’s thoughts section; Gemini Enterprise does not document how it uses that metadata. And the reasoning that the Gemini agent shows is Google’s own agent, not something your registered agent can write into. So the reliable way to show progress from your own agent in Gemini Enterprise, as of October 2026, is to send it yourself as working status messages.

How do Acme’s two builds do it?

Acme exists twice. The ADK build is the one this series follows: an ADK graph workflow on Agent Runtime. An earlier build of the same system was written straight on the Gemini SDK (google-genai), with its own small A2A server and no ADK on the serving path. Both talk to Gemini Enterprise over A2A and both answer with A2UI cards, but they treat thinking very differently:

                     Earlier SDK build                       ADK build (this series)
Thinking             Gemini thinking with                    thinking_level LOW on every agent;
                     include_thoughts=True; level set by     thought summaries not requested
                     configuration
Thoughts             split from the answer (thought=True     dropped: the writer's callback keeps
                     parts), cut to two lines, sanitised,    only parts that are not thoughts
                     streamed as progress
Progress             a rolling three-line block, streamed    no progress lines; the user sees
                     as A2A working status messages:         "working" until the answer arrives
                     thought, plan, hand-off, each step,
                     what came back, writer, fact-check
Answer card          A2UI v0.9 (Gemini Enterprise's          the same A2UI builder, attached to
                     composite catalog) or v0.8, chosen      the final model response by the
                     per request from what the client        writer's after-model callback, from
                     asks for; KPI tiles, table, chart,      the checked findings
                     follow-up chips, "How I answered"
"How I answered"     built from instrumentation, never       built from session state (what ran)
                     from the model's thoughts
Switches             show thoughts, show progress and the    model and thinking level are
                     A2UI version are settings, so each      settings (Part 11)
                     can be turned off without a rebuild

For Maria’s stockout question, the earlier build’s stream looks like this (each line is one status update; the real build marks the kinds with small icons):

working   Reading your question and working out what you need...
working   [thought]   Penalties are owned by supply, so the supply team's agent must answer that part
working   [plan]      Ask supply for penalties; read the campaign figures myself
working   [hand-off]  Asking the supply assistant over A2A...
working   [found]     Supply replied: penalties for Acme Pure, Western Europe        (from tool output)
working   [writer]    Writing the answer   [fact-check] Checking every figure against the data
artifact  the answer text, plus the A2UI card: KPI tiles, table, chart, "How I answered"
completed

(Illustrative wording; the shape is the earlier build’s.) Notice which lines come from where. Only the [thought] and [plan] lines are the model’s own words. Everything else, including what came back, comes from code that watched what actually ran.

The ADK build can do the same with ADK’s parts: turn on include_thoughts=True in the agent’s thinking configuration, pick the thought=True parts out of the events, sanitise them, and send them, together with code-generated progress for each step, as working status messages from the A2A layer. ADK’s RunConfig also offers StreamingMode.SSE for events as they are produced. The production rules below apply either way.

What are the production rules for showing thoughts?

Treat shown thoughts as narration, never as evidence, and make them safe to turn off. Where people get tripped up is that a thought summary reads like an audit trail. It is the taxi driver’s “nearly there”: helpful while you wait, and no substitute for the GPS. Part 5’s number check and Part 11’s traces are the GPS in this series.

These are the rules the earlier build follows, and the ones I would use anywhere:

  • Summaries, never raw reasoning. Show the model’s thought summaries, cut to one or two lines, not its whole reasoning.
  • Thoughts are never evidence. A thought can be wrong, and it is the model describing itself. Every “found” line and every figure must come from a tool result, and “How I answered” on the card is built from instrumentation (what ran), not from thoughts.
  • No model-written figures before the check. The number check runs on the final answer, so nothing streamed earlier may contain a figure the model wrote. Figures in progress lines come from tool output only.
  • Sanitise every line. Redact credentials, run the same content screen as for answers, and drop any line that talks about the system prompt, tools budget, tokens or headers. Thoughts can leak instructions.
  • Make it switchable. Showing thoughts and showing progress should each be a setting, so they can be turned off for a user group or an incident without a deploy.
  • Streaming never breaks a turn. If a progress update fails to send, drop it and carry on; the answer matters more than the narration.
  • Thinking still costs. Hidden or shown, thinking tokens are billed at the output rate (Part 11), and asking for summaries does not make thinking free.

What can go wrong

  • A card that shows unchecked numbers. If the model writes the A2UI JSON and puts a figure in it, the number check on the prose does not see it. Build cards from checked data.
  • Treating a click as trusted. A button event can be forged like any request. Validate it, and route it through the same plugins as typed text.
  • Version mismatch. A2UI moves fast: v0.9.1 is current, v1.0 is a release candidate, and Gemini Enterprise supports v0.9 and v0.8 (as of October 2026). Send a version the renderer supports.
  • MCP Apps from untrusted servers. They run code. Only enable them for servers you trust, and rely on the host’s sandbox, not on the server.
  • Building UI before the answer is right. A beautiful card with a wrong number is worse than a plain sentence with a right one.

What code did we write in this part?

Every part ends with the same map of Acme’s build: which files exist so far, and the code this part added, explained line by line. The map grows with each part.

This part adds no code to Acme’s build. The A2UI, AG-UI and A2A streaming messages it shows illustrate each format, with illustrative Acme components, and the table compares the earlier SDK build with the ADK build. The answer card builder and UiActionPlugin it describes come from earlier parts and are not changed here.

Where does it live?

router/                                             the router agent: extract, route, write
  app/agent.py                                      P5,8        workflow; extract reads memory; App with compaction and resumability
  app/app_utils/services.py                         P8          picks the session, artifact and memory services
  app/fast_api_app.py                               P8          Runner with session, artifact and memory services
  app/remote.py                                     P6,7        A2A client over mTLS; metadata carries Sam's token
  app/writer.py                                     P5,8        writer reads memories and saves the conversation to Memory Bank
  scripts/create_memory_bank.py                     P8          creates the Memory Bank and its topics, once
  tests/eval/acb_metrics.py                         P10         code metrics: route_correct, grounded, golden_numbers
  tests/eval/build_datasets.py                      P10         splits source cases into golden and held-out, seed 42
  tests/eval/calibrate.py                           P10         compares judge scores with human labels (kappa)
  tests/eval/datasets/golden_marketing.json         P10         golden cases with expected route, target and numbers
  tests/eval/eval_config.yaml                       P10         eval metrics, including the faithfulness judge
  tests/eval/gate.py                                P10         thresholds per metric; exits 1 when one fails
  tests/unit/test_router.py                         P10         unit tests; routing checked with one row per question type
marketing/                                          the marketing specialist, on BigQuery
  app/agent.py                                      P1,2,3,4    marketing agent with its skill in a SkillToolset
  app/app_utils/a2a.py                              P6          A2A endpoint and agent card (1.0 and 0.3) for marketing
  app/skills/marketing-kpis/SKILL.md                P4          marketing know-how: which tool, definitions, story checks
  app/skills/marketing-kpis/references/tables.md    P4          tables, grain, measures and the join rule, read on demand
  app/tools.py                                      P1,2        three narrow tools: measures, unserved demand, SQL fallback
  tests/unit/test_marketing.py                      P2          unit tests for the tools, with a fake context and database
supply/                                             the supply specialist, on Databricks
  app/agent.py                                      P3,7,8,9    supply agent and App with the confirmed replan tool
  app/app_utils/a2a.py                              P6          A2A endpoint and agent card (1.0 and 0.3) for supply
  app/skills/supply-catalog/SKILL.md                P4          supply know-how, given only to the supply agent
  app/tools.py                                      P3,7,9      supply tools; the replan tool asks a human first
shared/                                             code all three services share
  adk_common.py                                     P3,6,9,11   plugins and redacted JSON logs, now with the usage ledger
  auth_manager.py                                   P7          auth manager callback: token or consent (off by default)
  engines.py                                        P3,7        run_databricks connects as the signed-in user
  factcheck.py                                      P5          ungrounded_numbers: finds figures not in the findings
  registry.py                                       P2          reads semantic.yaml: measure names, computable measures
  routing.py                                        P1          classify: picks the specialist in code, no model
  safety.py                                         P9          per-user token bucket and injection pattern scan
  semantic.yaml                                     P1,2        registry of measures, owners and published formulas
  skill_source.py                                   P4          loads a skill from disk or a pinned registry revision
  sqlguard.py                                       P3          guard_sql read-only allow-list; redact for log lines
  tests/test_cost_report.py                         P11         unit tests for the cost maths
ops/                                                build, deploy and evaluation tooling
  .cloudbuild/eval_gate.yaml                        P10         after staging: eval run, then gate.py
  .cloudbuild/pr_checks.yaml                        P10,11      offline PR checks, cheapest first, ending with the mutants
  env.sh.example                                    P11         settings for every engine: ids, models, plugin switches, OTEL; no secrets
  infra/gateway/setup_gateway.sh                    P12         egress gateway, endpoint registry and IAP rules, in dry-run
  tools/cost_report.py                              P11         prices the ledger and reports cost per resolved question
  tools/mutants/acb_mutate.py                       P10         pytest plugin that injects each mutant's fault
  tools/pricing.json                                P11         list prices per model, kept out of the code
  tools/run_mutants.py                              P10         runs the offline suites once per mutant

+ added in this part   ~ changed in this part   Pn = the parts that touched the file

The ADK build still sends no progress lines or thought summaries while it works; the post describes how it could, but that is not built.

What did we learn?

  • A2UI describes what to show, as JSON components the client draws itself; nothing is executed.
  • AG-UI is the event-based connection between a frontend app and an agent; it can carry A2UI.
  • MCP Apps let an MCP server’s tool render its own HTML, in a sandboxed iframe in the host.
  • Gemini Enterprise renders standard A2UI components (v0.9 and v0.8) for agents registered with A2A; ADK has integrations for both A2UI and AG-UI.
  • In Acme’s design, cards are built from checked findings, and clicks go through the same guardrails as text.
  • While the agent works, stream progress from code and, if you want, sanitised thought summaries, as A2A working status updates; thoughts are never evidence and never carry figures.

What comes next?

With the front doors and what they can show settled, the series turns to choosing the models behind each step. First, one more way to ship: agents that keep working after the tab closes, in Production-ready agents, Part 12.9: Long-running agents. After that comes Production-ready agents, Part 13: Choosing models.

Sources

Opinions are my own. Every fact in this post comes from the public documentation linked above. Acme Consumer Brands and its data are fictional.