Skip to content

A2UI

An A2UI booking form

A2UI (“Agent to UI”) is an open protocol where the agent describes UI as JSON data — a flat list of components plus a data model — and the client renders it with native components. No code crosses the boundary, so it’s safe to render UI straight from a model.

Moka implements A2UI v0.9 (and reads v0.8 messages), with the standard component catalog.

Every workspace gives the model a built-in render_ui tool. Its description teaches the model the component catalog, so it works with any tool-calling model: GPT, Claude, Gemini, Llama. You can rename it, restrict or extend its components, and theme it; see Customize the render tool and Custom catalogs.

render_ui input (what the model sends)
{
"surfaceId": "signup",
"components": [
{ "id": "root", "component": "Card", "child": "col" },
{ "id": "col", "component": "Column", "children": ["title", "name", "plan", "go"] },
{ "id": "title", "component": "Text", "text": "Join the beta", "variant": "h3" },
{ "id": "name", "component": "TextField", "label": "Your name", "value": { "path": "/name" } },
{ "id": "plan", "component": "ChoicePicker", "label": "Plan", "value": { "path": "/plan" },
"options": [{ "label": "Free", "value": "free" }, { "label": "Pro", "value": "pro" }] },
{ "id": "go-label", "component": "Text", "text": "Sign me up" },
{ "id": "go", "component": "Button", "variant": "primary", "child": "go-label",
"action": { "event": { "name": "signup", "context": { "name": { "path": "/name" }, "plan": { "path": "/plan" } } } } }
],
"data": { "name": "", "plan": "pro" }
}

Moka validates the tree against the active catalogs (a root component must exist, every reference must resolve, components and required props must exist). If it’s invalid, the model gets a precise error and usually fixes it on the next step.

MessagePurpose
createSurfaceStart a surface (surfaceId, catalogId)
updateComponentsAdd or replace components on a surface (flat list, referenced by id)
updateDataModelSet data at a JSON Pointer path (default /)
deleteSurfaceRemove a surface

Exactly one component per surface must have "id": "root". v0.8 messages (surfaceUpdate, dataModelUpdate, beginRendering, {"Text": {...}} wrappers) are converted automatically.

Any property can be a literal or a path into the surface’s data model:

{ "id": "greeting", "component": "Text", "text": { "path": "/user/name" } }

Input components (TextField, CheckBox, Slider, ChoicePicker, DateTimeInput) bind two-way: the user edits the data model directly.

Lists can repeat a template for every item of an array:

{ "id": "list", "component": "List", "children": { "componentId": "row", "path": "/items" } },
{ "id": "row", "component": "Text", "text": { "path": "name" } }

Inside a template, relative paths ("name") resolve against the current item.

When the user presses a Button, Moka resolves the action’s context against the data model and sends the agent a user message:

[ui action] {"name":"signup","surfaceId":"signup","sourceComponentId":"go","timestamp":"2026-09-25T10:12:00.000Z","context":{"name":"Batman","plan":"pro"}}

The chat shows a compact chip (▸ signup) instead of the raw JSON, the button is marked as sent, and the agent continues — for example by calling a booking tool with those values.

See every supported component in the component catalog.