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.
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 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
UiActionPlugindoes (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
generateContentAPI, which ADK uses, the switch isThinkingConfig(include_thoughts=True), and summary parts arrive markedthought=True; the newer Interactions API calls the same optionthinking_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
workingstatus 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
- A2UI: home and specification versions
- A2UI specification v0.9.1
- Google Developers Blog: Introducing A2UI, an open project for agent-driven interfaces
- AG-UI: overview
- AG-UI: MCP, A2A, and AG-UI
- AG-UI: events
- MCP Apps overview
- ADK: AG-UI user interface for ADK
- ADK: A2UI, Agent-to-UI for ADK
- Gemini Enterprise: register and manage agents using A2UI and A2A
- ADK source: McpTool (MCP App resource URI)
- Gemini API: thinking and thought summaries
- Gemini Enterprise: register and manage A2A agents (A2A v0.3 streaming)
- Gemini Enterprise composite A2UI catalog (v0.9)
- ADK: RunConfig (streaming modes)
- Google Developer forums: Running and debugging A2A agents in Gemini Enterprise
Opinions are my own. Every fact in this post comes from the public documentation linked above. Acme Consumer Brands and its data are fictional.