Core principles for building conversation-native Tool UIs. This document will be tuned over time as we iterate and learn.
Fundamentals
Every Tool UI follows these 5 rules:
One Intent per Surface
Each instance does one primary job. A table that filters, exports, and schedules is three surfaces, not one.
Inline, Not Destination
Tool UIs live inside messages. No fullscreen takeovers, no mini-apps. They support the conversation, not replace it.
Schema-First & Addressable
Components render from JSON with stable IDs (surface + key children) enabling references like "the second row" or "the card above". Include asOf when freshness matters.
Assistant-Anchored
The assistant narrates every Tool UI before, during, and after interaction. The UI never "speaks" silently.
Temporal & Lifecycle Aware
Surfaces move through explicit phases and show honest timestamps. Canonical lifecycle:
invocation → output-pending → interactive → committing → receipt → errored.
Roles
Every Tool UI declares its primary role:
- Information: Display data (e.g., DataTable, MediaCard)
- Decision: Capture consequential choices that require commitment and produce receipts (e.g., approve/reject prompts, send/cancel dialogs)
- Control: Adjust parameters that modify behavior without commitment. Safe, iterative, no side effects (e.g., filters, sort, date ranges)
- State: Expose internal tool/assistant activity (observability)
- Composite: Primary + secondary (e.g., DataTable with actions = "information with control")
Lifecycle
Tool UIs follow predictable phases:
invocation → output-pending → interactive → committing → receipt → errored
Temporal overlays: fresh | active | stale | historical | superseded.
Significant changes or time gaps: create a new surface and mark the old one superseded rather than silently updating stale data.
Required Invariants
Core Contract
- Single outcome & declared role
- Fully serializable from schema (JSON-safe, reconstructable)
- Stable IDs for surface and key children
Conversation Requirements
- Every important action maps to a canonical sentence
- Side effects produce durable receipts (status, summary, identifiers, timestamp)
- Temporal honesty (show
asOf and staleness/supersession behavior)
Quality Floor
- Keyboard navigable, screen-reader friendly, AA contrast
- No layout thrash during streaming (skeleton matches final layout; no container resize)
- Errors as first-class surfaces, not toasts
Conversation Coherence
Every UI action has a natural language equivalent:
Example 1: Approving an action
- UI Action: Click "Approve"
- Canonical Sentence: "Approve PR #42"
- User: "yes, approve it"
- Assistant: "Approved PR #42."
Example 2: Selecting items
- UI Action: Select rows 2-4
- Canonical Sentence: "Select items 2 through 4"
- User: "those three"
- Assistant: "Selected 3 items."
This enables the Triadic Loop: User ⇄ Assistant ⇄ Tool UI, where the assistant interprets all interactions.
Anti-Patterns
Don't build these:
- Forms in chat: Multi-field forms pretending to be Tool UIs
- Hidden mutations: Side effects without receipts
- Kitchen sinks: Dashboards crammed into messages
- Mini-apps: Navigation flows inside Tool UIs