Skip to main content
Agentheya agents can go beyond plain text and generate rich visual interfaces during a conversation. Instead of describing data in words, the agent can show a formatted card, a metrics row, a status table, or an interactive form — and, when the form is interactive, read the user’s answers back on its next turn. This page is the complete reference: every component, every prop, how to make the UI dynamic, and a set of worked examples for building real, multi-field forms.
All visual components follow the json-render spec, but Agentheya ships its own renderer with a focused, well-supported subset. Stick to what’s documented here — see Limitations for the features that exist in the underlying spec but are not rendered by Agentheya yet.

Where Generative UI shows up

There are three ways a spec reaches the screen. They all use the same component spec and the same renderer, so everything on this page applies to all three.

render_ui tool

A built-in tool the agent calls to draw UI on demand. Can be static or interactive. Enable it on the Tools tab.

Custom tool response

Your own API/code tool returns a _render payload and the agent displays it. Can be interactive.

Inline artifact

The agent embeds a read-only component inside its text reply when a visual is clearer than prose. Always static.

The spec model

A spec is a flat map of elements plus a pointer to the root and an optional state object:
  • root — the id (a key in elements) of the top-level element to render.
  • elements — a map of id → element. Elements are not nested; nesting is expressed by listing child ids in children.
  • state — the data model. Seed it with the starting values for any input fields (see Making it dynamic).
Every element has the same shape:
Every prop in the schema must be present, even when you don’t need it — set unused props to null. Validation rejects a spec with a missing key (null is fine, absent is not). For example a card must include both title and subtitle; if there’s no subtitle, write "subtitle": null. This is the single most common cause of a rejected spec.

Component reference

Eleven components are available. Each table lists all props (remember: all are required-present; use null for unused ones). Allowed enum values are shown in code.

Containers

These hold other elements via children.

card

A titled panel.

list

A flex container that lays its children in a row or column.

form

Groups inputs and a submit button. Visually a labelled section; submission still happens via a button wired to the submit action (see Interactivity).

divider

A horizontal rule. No props.

Display

Read-only components for showing data. All are leaf elements (children: []).

stat

A single KPI: label, value, and an optional trend.
changeType is up / down / neutralnot increase / decrease. The wrong value fails validation.

badge

A small inline label / chip.

alert

A banner notice. Use variant for severity.

key_value

A definition list of label/value pairs.

table

A simple data table.

Interactive

These collect input or trigger submission. They only do anything when the render is interactive (the render_ui tool sets interactive: true, or a custom tool sets _interactive: true).

button

A clickable button. Bind its behaviour with on.press.

input

A form field. The kind prop selects the variant; name is how the value is identified. All nine props are required-present — use null for the ones a given kind doesn’t use. kind values: text, email, number, integer, tel, url, password, textarea, select, checkbox.
Field rules worth memorising:
  • Use kind (not type) to choose the input variant.
  • select options must be objects { "value": "...", "label": "..." } — not plain strings.
  • Every input needs a name.
  • An input with no value: { "$bindState": "/path" } shows a ⚠ marker and its value is not collected on submit. Bind every field you want back.
  • number/integer return a number (or null when empty); checkbox returns a boolean; everything else returns a string.
  • There is no date, radio, or multiselect kind. Use text for dates and select for single choice. (Multi-select can be modelled as several checkbox fields.)

Making it dynamic

The state object is the single source of truth. Inputs read from it and write back to it as the user types; display props read from it; and you reference it from visibility conditions. This is how a form reacts live to what the user does — no agent round-trip needed.

Expressions

Any value-bearing prop (titles, labels, stat.value, alert.message, badge.variant, …) may be a literal or one of these expression objects. Seed each referenced path in state with a starting value ("", 0, false, …). Paths are JSON Pointers (/contact/email, not contact.email).
Structural props stay literal: input kind/name/options, table.columns/rows, and key_value.items are not expression slots. An object with no recognised $-key is rejected, so typos still fail validation.

Conditional visibility

Any element can carry a top-level visible condition. When it evaluates false, the element (and its children) is hidden. Combined with $bindState inputs, this is the core technique for forms that reveal fields based on earlier answers. Add "not": true to any single condition to invert it. Use one comparison operator per condition object.

Repeating over a list

An element can carry a top-level repeat to render its children once per item in a state array. The element itself renders once as the container; its children expand per item.
Inside the repeated children, reference the current item: key (optional) names a stable id field on each item. Pair repeat with the pushState / removeState actions below to build add/remove lists (Recipe 7).

Interactivity and submission

A render is interactive when the surface enables it (render_ui with interactive: true, or a custom tool with _interactive: true). Interactive specs must include at least one button wired to an action. Actions are bound on on.press (buttons) and fire in order. Three kinds:
  • Local — mutate state in place, form stays open (setState, pushState, removeState).
  • Server — run a sandboxed code snippet or invoke a tool when clicked, surface the result inline / in state / in an overlay. Form stays open so the user can keep interacting (runCode, callTool).
  • Terminal — end the interaction and report back to the agent (submit, cancel).

Server actions

runCode and callTool let a button trigger work on the server without ending the interaction — the form stays open, the result is delivered back to the spec, and the user can keep clicking. Both actions require the render to be interactive (render_ui with interactive: true, or _interactive: true on a custom tool); a static render with these verbs is rejected. Both verbs share the same resultMode / target contract: The current form state is sent with every click, so the code / tool sees up-to-date input values.

runCode — sandboxed snippet

params.code is the source. params.lang is "python" (default) or "javascript". The runtime, sandbox isolation, and limits match those of the execute_code tool.

callTool — invoke a registered tool

params.tool is the tool name (anything already available to this agent). params.params is a static argument object — the form state is merged on top at click time, so dynamic fields like query flow through naturally. The result is whatever the tool returns. Example button:
A related watch field (top-level on an element) fires a local action when a state path changes — handy for resetting a dependent field when a parent select changes:

What the agent receives

After the user acts, the agent’s next turn gets:
values is the whole state model — every $bindState path becomes a key. (Cancellation arrives as { "status": "cancelled" } with no values.)
1

Agent renders an interactive form

It calls render_ui with interactive: true (or your tool returns _render + _interactive: true) and then ends its turn.
2

User fills it in and clicks a button

Inputs write to state as they type; the submit/cancel action fires on click.
3

Agent reads the result next turn

The submitted values / action / params arrive as the tool result, and the agent continues.
After an interactive render, the agent must end its turn and wait — it should not narrate the outcome until the submission actually arrives. For destructive actions, gate the follow-through with confirm_action + commit_action; render_ui does not provide that guarantee on its own.

Form recipes

Each recipe below is a complete, valid spec (the { root, elements, state } object). For the render_ui tool, pass it as the spec argument with interactive: true; for a custom tool, return it under _render with _interactive: true.

Recipe 1 — Multi-section intake form

Sections via nested cards, a mix of input kinds, one submit button. Every bound field comes back in values.

Recipe 2 — Conditional fields (reveal on selection)

The contact-method select drives which follow-up field is visible, using visible + $bindState. No scripting required — picking an option re-renders the dependent fields.

Recipe 3 — Reveal a whole section

A visible condition on a container shows or hides all of its children at once. Ticking “ship to a different address” reveals an entire address sub-form. (Validation accepts visible on any element — it’s an element-level field, not a prop.)

Recipe 4 — Decision card with two buttons (intent)

Show details, then offer two outcomes. The agent tells them apart via params.intent in the result.

Recipe 5 — Settings panel (checkboxes, numbers, select)

A grouped settings form. number/integer come back as numbers; checkboxes as booleans.

Recipe 6 — Read-only dashboard

A static summary mixing stats, badges, an alert, and a table. No state, no buttons — perfect for an inline artifact or a static render_ui call.

Recipe 7 — Dynamic line items

repeat renders a row per array item; pushState / removeState add and remove them; $bindItem binds each row’s input back into the item. The Add button generates an id with "$id" and clears the draft field.

Recipe 8 — Live summary with $template

A display prop reads state directly, so a summary updates as the user types. ($template interpolates; it doesn’t do arithmetic — echo values, don’t compute totals.)

Recipe 9 — In-render wizard with setState

setState flips a /step flag and visible shows the matching panel — a multi-step flow inside a single render, no agent round-trip per step.

Guiding the agent

The agent decides when to render UI. Steer it from your Purpose or Rules:
When a support ticket needs a resolution choice, use render_ui (interactive) to show the order details as a key_value and the options as a select, rather than asking in plain text.
For deterministic UI from your own systems, build a custom tool that returns a _render payload — the agent shows exactly what your code produces.

Limits and validation

  • Allowed components only: the eleven listed above. Anything else is rejected.
  • Size: up to 200 elements and a nesting depth of 6.
  • Every prop key must be present (use null for unused ones).
  • Inputs: kind not type; select needs object options; every input needs a name; bind value to collect it.
  • Invalid specs return a tool error with a details.hint so the agent can self-correct on the next turn.

Limitations

A few things in the broader json-render spec aren’t rendered — use the supported pattern instead:

See also