Messages & Components
Agents communicate with humans by sending messages on sessions. Each message can include a tree of composable components that render as rich UI—buttons, forms, tables, charts, and more. All message content is encrypted end-to-end; the server never sees plaintext.
How it works
- Agent creates a session (a persistent conversation channel)
- Agent sends a message with
components—a tree ofComponentNodeobjects - Components render as rich UI on the human side (buttons, forms, tables, charts, etc.)
- Human responds via interactive components (
button_group,qa,rating) - Agent receives the response
ComponentNode interface
Every component in a message is a ComponentNode.
Layout nodes use children to nest other components inside them.
interface ComponentNode {
type: string; // Component type
data?: Record<string, unknown>; // Component config
children?: ComponentNode[]; // Nested components (for layout types)
} Layout nodes
Layout nodes structure your message. They contain
children and control
how nested components are arranged.
| Type | Description |
|---|---|
card | Bordered container with optional title |
group | Structural grouping (no visible chrome) |
tabs | Tabbed content switcher |
story | Instagram-style swipeable slides |
stepper | Numbered vertical steps |
page | Vertical stack with spacing |
Content blocks
Content blocks display information. They are leaf nodes—no children.
| Type | Description |
|---|---|
text | Rich text content |
header | Section headers (level 1-3) |
divider | Horizontal separator |
code | Code blocks with syntax highlighting |
list | Ordered/unordered lists |
quote | Blockquotes with attribution |
table | Data tables with headers |
alert | Info/success/warning/error alerts |
kv | Key-value pairs |
stat | Single metric with trend |
metric_grid | Grid of metrics |
steps | Progress steps (done/active/pending) |
accordion | Collapsible sections |
chart | Bar/line charts |
image | Images with captions |
payment_details | Payment/invoice display |
scheduled_event | Calendar event |
countdown | Countdown timer |
file_download | File download links |
Interactive components
Require responseInteractive components collect input from the human. When a message contains an interactive component, the message status moves through the response lifecycle.
| Type | Description |
|---|---|
button_group | Action buttons (primary/danger/default variants) |
qa | Single select, multi select, or open-ended questions |
rating | Star rating or NPS |
vote | Poll/vote with results |
Examples
Simple decision message
An agent asks a human to approve or reject a refund. The message combines a header, key-value context, and action buttons.
client.send_message(session.id, components=[
{"type": "header", "data": {"text": "Approve refund — $149.95", "level": 3}},
{"type": "kv", "data": {"entries": [
{"key": "Customer", "value": "Jane Smith"},
{"key": "Reason", "value": "Item arrived damaged"},
]}},
{"type": "button_group", "data": {"buttons": [
{"label": "Approve", "action": "approve", "variant": "primary"},
{"label": "Reject", "action": "reject", "variant": "danger"},
]}},
]) Report with metrics
A metrics dashboard sent as a message. No response required—this is purely informational.
await client.sendMessage(session.id, {
components: [
{ type: "metric_grid", data: { metrics: [
{ label: "ARR", value: "$4.2M", change: "+68% YoY" },
{ label: "Churn", value: "1.8%", change: "↓0.4pp" },
] } },
{ type: "chart", data: {
type: "bar",
title: "Revenue by Quarter",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [820, 1100, 1340, 1580],
} },
],
}); Tabbed layout
Components nest inside layout nodes using children.
Here, a tabs node wraps two
group nodes, each with its own content.
await client.sendMessage(session.id, {
components: [
{ type: "tabs", children: [
{ type: "group", data: { label: "Overview" }, children: [
{ type: "kv", data: { entries: [
{ key: "Status", value: "Healthy" },
{ key: "Uptime", value: "99.97%" },
] } },
] },
{ type: "group", data: { label: "Metrics" }, children: [
{ type: "metric_grid", data: { metrics: [
{ label: "Requests", value: "1.2M", change: "+12%" },
{ label: "Errors", value: "0.03%", change: "↓0.01%" },
] } },
] },
] },
],
}); Response lifecycle
Messages containing interactive components follow a response lifecycle.
Set responseRequired: true
to mark a message as needing human action.
Status flow
pending → viewed → responded → completed Optional parameters
| Parameter | Type | Description |
|---|---|---|
responseRequired | boolean | Mark the message as requiring human action. |
deadlineAt | string (ISO 8601) | Auto-expire the message if no response is received by this time. |
priority | string | "normal", "high", or
"critical". Affects notification urgency and sort order.
|
priority: "critical" for urgent
messages—they trigger push notifications and show prominent badges in the app.
See also
- Getting Started — Create your first agent and send a message in 5 minutes
- Embed SDK — Drop message components into your own UI
- SDKs — Python, TypeScript, and Deno client libraries
- MCP Server — Use Hiloop from Claude, Cursor, or Windsurf
- API Reference — Full REST API documentation