Choosing a generative UI tier
An agent's answer can be prose, a card your app draws, a surface built from your components, a one-off visual, an app from a partner, or a whole canvas. All of them work from any agent in the stack — the agent answering the chat or a sub-agent three delegates deep — so the choice is about the answer, not the plumbing. This guide says which to reach for and why.
The tiers
| Tier | Who designs it | Reach for it when | Lab scenario |
|---|---|---|---|
| Controlled: your component for a tool | You | A tool's call is worth showing as it happens: SQL being written, widgets being plotted | plain-dashboard |
| A2UI: a surface from your catalog | You, composed by the agent | A figure, a trend or a small table that answers the question now | a2ui-inline |
| Open Generative UI: a sandboxed visual | The agent | A one-off visual no component covers: a diagram, a heat strip | ogui-visual |
| MCP Apps: an app from a server | The server's owner | UI that belongs to a service you don't own | mcp-partner |
| Canvas: a dashboard beside the chat | You | Several parts, or anything worth keeping | delegate-dashboard |
The tier rule follows from who designs it: our components use the controlled tier or A2UI, one-off visuals use Open Generative UI, and UI from an MCP server is an MCP App. Never ask the model to generate HTML for something you could have drawn yourself: your components are consistent, accessible and cheap to render; a generated visual is none of those, and costs tokens every time.
The placement rubric
Where an answer goes is the agent's decision, guided by its prompt. The lab's agents carry this rubric, and it is the one to start from:
Where an answer goes:
- A short fact: one or two sentences.
- A figure, a trend or a small table that answers the question now: render it
inline with render_a2ui, using the catalog's widgets with a saved query's id.
- Several parts, or anything worth keeping: a dashboard on the canvas.
- A one-off visual no catalog component covers: generateSandboxedUi.
Never paste rows of data into the chat.
Give each agent only the tiers it should use, with client_tools. A visualizer
that builds dashboards doesn't need render_a2ui; an analyst that answers quick
questions does. Every tier an agent holds is a tool the model has to choose
between, and Kaboo holds agents to 20 tools.
Compare the inline and canvas answers to the same question with
yarn lab compare inline-vs-canvas --live: the tool calls, tokens, bytes and
time of each side, from a real model.
Data by reference
Every tier carries ids, never rows. A widget, an A2UI component or a sandbox function names a saved query, and fetches its rows when it renders:
- The answer stays small. A surface is a few kilobytes whatever the data, and so is the conversation that carries it.
- The figures stay live. Change a dashboard variable and every widget that reads the query refreshes; nothing in the transcript goes stale.
- A widget can move. "Add to dashboard" on an inline answer plots the same widget spec on the canvas, unchanged.
The agent saves the query first (sql-workbench___create_queries), then refers
to it. The same rule holds for tool results: return a preview and an id, not
the full result set.