The same envelope that lets an agent reason about the API also drives the UI. Components are dumb. Props lift straight from canonical responses. The chat layer (or your component registry) picks a primitive based on the response shape. One renderer covers every device family that follows the canonical model. This page is for integrators building a chat surface, generative UI, or any agent-facing UI on top of the Amps API. The patterns work against the deployed REST API and will work the same way against the Execution MCP when it ships.Documentation Index
Fetch the complete documentation index at: https://docs.amps.ai/llms.txt
Use this file to discover all available pages before exploring further.
The principle: shape matches primitive
Every canonical response carries enough type information for a registry to match it to a UI primitive without device-specific code:| Response shape | Primitive | Notes |
|---|---|---|
{ success: false, error: { code, message, details? } } | <ErrorAlert> | Surfaces the code, the message, and any actionable details. |
Battery state with commands and state.level | <DeviceStateCard> | One card layout works across every battery. |
EV charger state with commands and state.charging | <DeviceStateCard> | Same component, different state fields surfaced. |
Action lifecycle ({ id, status, command, parameters }) | <ActionTimeline> | Renders the lifecycle states acknowledged → completed (or failed / cancelled). |
| Capability declaration plus user intent | <CapabilityControls> | Renders form controls (sliders for power, percent inputs for target) bounded by the device’s declared min/max. |
Numeric Quantity with unit: "percent" | <Gauge variant="percent"> | Ring gauge, 0-100 scale. |
Numeric Quantity with unit: "kw" | <Gauge variant="power"> | Power gauge, scaled to the parameter’s max. |
Generic envelope ({ success: true, data: { ... } }) | <EnvelopeRenderer> | Best-effort fallback: tabulates fields, recurses into nested envelopes. |
render: "DeviceStateCard" field, for example); the registry honours the hint first and falls back to shape-matching. Hints are useful when the response is ambiguous. Shape-matching covers the rest.
Components stay dumb
A primitive takes typed props, renders, and returns. No data fetching. No effects beyond the visual. The agent layer (chat, tool-call handler, registry) carries all state. This is what keeps the renderer reusable across every device. A<Gauge> for a state-of-charge level:
unit at the type layer. Adding a new unit to the canonical enum is one edit; every consumer picks it up via the type.
The streaming pattern
Tool calls take time. The UI does not block. The pattern that works:- The model proposes a tool call. The chat surface renders a placeholder primitive (
<DeviceStateCard pending />or a generic skeleton) keyed off the tool name. - The tool fires. The placeholder stays visible.
- The response streams in. The registry picks the real primitive. The placeholder swaps for the real component.
- Multiple parallel tool calls render multiple parallel placeholders. Each resolves on its own clock.
list_battery, list_ev_charger, list_hvac in parallel. As each returns, a real card swaps in. Failures render <ErrorAlert> inline without breaking the rest of the row.
The Vercel AI SDK’s multi-step tool-call pattern composes cleanly with this approach. Each tool call carries an ID; the chat layer keys placeholders off the ID; the swap happens on tool result.
The capability-driven control surface
The most agent-native primitive is<CapabilityControls>. It takes a device’s capability declaration plus a target command and renders form controls bounded by the device’s declared min/max:
parameters: {} for a command (typical for auto.* modes) renders no inputs, just the verb and a button. A device that declares power with unit: "kw" and max: 5.0 renders a 0-5 kW slider. The same component, no per-device code.
What the agent gets for free
- Every new battery the platform supports gets the same
<DeviceStateCard>rendering with no UI work. - Every command added to the canonical surface gets a
<CapabilityControls>renderer with no UI work. - Every new unit added to the
Quantityenum gets a typed primitive switch with one edit to<Gauge>. - Every error code surfaces in
<ErrorAlert>with the right code shown and thedetails.deviceCapabilitiessnapshot inviting an immediate retry.
What next
Self-describing responses
Where the response shapes come from and why they carry what they carry.
Confirmation gates
The destructive-action pattern that gates push and cancel behind a click.
Capabilities
The capability contract
<CapabilityControls> reads from.Canonical actions
The action shape
<CapabilityControls> builds.