One intent, seven interfaces
The same request answered as text, markdown, a pre-built component, a declarative spec, sandboxed HTML, a whole app, and an agent driving an existing UI.
GoalFeel the trade-off between how much freedom the model gets and how much control, safety and consistency you keep.
Use ← → to move along the spectrum.
{
"type": "tool-call",
"toolName": "showExpenseApproval",
"args": {
"title": "Team offsite",
"amount": 1240,
"submitter": "Priya"
}
}tool call pending · waiting for the person
The model only chose showExpenseApproval and its three props. Layout, styling, copy on the buttons and what they do were written and tested by you.
- Control5how tightly you bound what can appear
- Expressiveness2how many different interfaces the model can produce
- Safety4how small the blast radius of a bad generation is
- Brand fit5how likely the result matches your design system
- Testability5how easily you can check it before a person sees it
- Speed4how fast the first useful pixel appears
- Return path
- The component's buttons resolve the tool call. The agent receives { approved: true } as the tool result.
- Who ships it
- Tool-call rendering in CopilotKit, the Vercel AI SDK and assistant-ui; most production agent UIs start here.
- Takeaway
- The model picks the component and fills its props. You wrote, styled and tested the component.
| Rung | Control | Expressiveness | Safety | Brand fit | Testability | Speed |
|---|---|---|---|---|---|---|
| 5 | 1 | 5 | 3 | 3 | 5 | |
| 4 | 2 | 5 | 3 | 3 | 5 | |
| 5 | 2 | 4 | 5 | 5 | 4 | |
| 4 | 4 | 4 | 4 | 4 | 4 | |
| 2 | 5 | 2 | 2 | 2 | 3 | |
| 1 | 5 | 3 | 2 | 3 | 1 | |
| 3 | 1 | 2 | 5 | 2 | 2 |
How it works: one intent, a policy per surfaceclick to expand
Pick the rung per surface, not per product. A payments confirmation belongs at rung 3. An exploratory analytics answer can live at rung 4 or 5. The same app usually mixes several.
Each rung moves the risk somewhere. Rungs 1–2 put it in parsing your words. Rungs 3–4 put it in your component library. Rung 5 puts it in a sandbox. Rung 7 puts it in what the agent clicks.
The return path matters as much as the render. Ask of any generative UI: when the person acts, what exactly reaches the agent, and who checked it?
// a "degrees of freedom" policy, decided per surface
const policy = {
"expense.approve": { rung: "static", components: ["ExpenseApproval"] },
"analytics.answer": { rung: "declarative", catalog: chartsAndTables },
"report.explore": { rung: "open", sandbox: "allow-scripts",
bridge: ["ui/action"] }, // only these messages
}
function render(surface, modelOutput) {
const p = policy[surface]
if (p.rung === "static") return <Registered name={modelOutput.toolName} {...modelOutput.args} />
if (p.rung === "declarative") return <Catalog spec={modelOutput} allow={p.catalog} />
if (p.rung === "open") return <Sandbox html={modelOutput} allow={p.bridge} />
}