Surfaces and components
How the agent renders UI inside the chat, why it is safe, and how your own widgets take part.
The assistant answers with surfaces: JSON documents in the kletso.ui/v1 format that describe a tree of typed components. The SDK renders a surface with widgets, so a product list is real cards with real buttons, not a paragraph.
Who decides what
- The model decides what to show. It calls the built-in
render_uitool with a surface. - The runtime validates the surface: known component types only (the agent’s allowlist), size and depth limits, bindings resolved against the data the model attached.
- Your app decides how it looks. Built-in types have default widgets themed to your brand. Custom types (
acme.productCard) are rendered by the builder you registered. Unknown types fall back to the surface’s plain-textfallbackText, so a model mistake never breaks the screen.
Anatomy of a surface
{
"schema": "kletso.ui/v1",
"surfaceId": "sfc_products1",
"root": "list1",
"fallbackText": "Trail Runner 2 (₹1899), Cushion Walk (₹1499)",
"data": { "items": [ { "sku": "SKU-1001", "name": "Trail Runner 2", "price": 1899 } ] },
"components": {
"list1": { "type": "column", "props": { "children": ["card1"] } },
"card1": {
"type": "acme.productCard",
"props": { "sku": { "path": "/items/0/sku" }, "name": "Trail Runner 2", "price": 1899 },
"actions": [ { "id": "add", "kind": "local", "name": "addToCart", "args": { "sku": "SKU-1001" } } ]
}
}
}
componentsis a flat map keyed by id;rootnames the top node; containers reference children by id. This keeps surfaces small and lets a laterui.patchevent change data or single components instead of re-sending everything (the SDK applies patches; the runtime does not emit them yet).- A prop can be a literal or a binding
{ "path": "/items/0/sku" }: a JSON pointer resolved against the surface’sdata. Paths starting with/host/resolve against data your app publishes withsetHostData, so a card can show the live cart total without the value ever leaving the device. Only top-level props are resolved; an unresolved binding drops the prop so the widget uses its default. actionsdescribe what interactions do. See Actions.
The full schema, all 30 built-in types and their props are in the protocol reference.
Custom components
- Write the widget you already have (
ProductCard). - Declare a
KletsoComponentSpecfor it: the type name, the props schema, the actions it exposes. This lives in a Dart file that imports onlykletso_core, so a command-line tool can read it. - Register the builder in your app:
registerComponent(spec.type, (ctx, node) => ProductCard(...), spec: spec). - Run
dart run kletso_flutter:syncand import the resultingkletso.components.jsonin the dashboard (Components → Sync from code). The model’srender_uitool schema now knows your type and props. - Add the type to the agent’s UI allowlist and publish.
Details and code: Custom components.
Limits
| Limit | Value |
|---|---|
| Components per surface | 500 |
| Nesting depth | 16 |
| Surface size | 256 KB |
| Strings / arrays | 8192 characters / 200 items |
| Unknown type | rendered as fallbackText |