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_ui tool 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-text fallbackText, 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" } } ]
    }
  }
}
  • components is a flat map keyed by id; root names the top node; containers reference children by id. This keeps surfaces small and lets a later ui.patch event 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’s data. Paths starting with /host/ resolve against data your app publishes with setHostData, 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.
  • actions describe what interactions do. See Actions.

The full schema, all 30 built-in types and their props are in the protocol reference.

Custom components

  1. Write the widget you already have (ProductCard).
  2. Declare a KletsoComponentSpec for it: the type name, the props schema, the actions it exposes. This lives in a Dart file that imports only kletso_core, so a command-line tool can read it.
  3. Register the builder in your app: registerComponent(spec.type, (ctx, node) => ProductCard(...), spec: spec).
  4. Run dart run kletso_flutter:sync and import the resulting kletso.components.json in the dashboard (Components → Sync from code). The model’s render_ui tool schema now knows your type and props.
  5. Add the type to the agent’s UI allowlist and publish.

Details and code: Custom components.

Limits

LimitValue
Components per surface500
Nesting depth16
Surface size256 KB
Strings / arrays8192 characters / 200 items
Unknown typerendered as fallbackText

Last updated 2026-09-28 · Report an issue with this page