Documentation Index
Fetch the complete documentation index at: https://docs.svantic.com/llms.txt
Use this file to discover all available pages before exploring further.
A2UI — Agent-to-User Interface
A2UI is a structured format for agents to request input from humans. Instead of plain text prompts, agents describe what they need as a JSON spec — fields, actions, validation rules, sensitivity flags — and the rendering surface decides how to present it.
A2UI is transport-agnostic. The same spec renders as a React form in the dashboard, Slack buttons, an email summary, or raw JSON in a webhook payload.
Why A2UI?
When an agent needs human input, the naive approach is to print a text message and hope the human knows what to do. That breaks down in production:
- Approvals need structured responses. “Do you approve?” needs a yes/no button, not a free-text reply.
- Forms need validation. An agent asking for credentials needs a password field, not a plaintext input.
- Different channels have different capabilities. Slack can render buttons. Email can’t. The dashboard can render anything.
- Sensitive data needs protection. Credentials should never appear in Slack notifications or logs.
A2UI solves this by separating the request definition (what the agent needs) from the rendering (how it’s presented to the human).
The Spec
An A2UI payload is a JSON object describing a set of components. Each component has a type and properties that tell the renderer what to display.
{
"type": "Form",
"props": {
"title": "Approve Bulk Deletion",
"description": "The agent wants to delete 47 records older than 30 days.",
"fields": [
{
"id": "decision",
"type": "action",
"label": "Your decision",
"options": [
{ "value": "approve", "label": "Approve", "style": "primary" },
{ "value": "deny", "label": "Deny", "style": "danger" }
]
}
]
}
}
Field Types
| Type | Renders As | Slack Support |
|---|
action | Buttons (approve/deny, custom actions) | Yes — buttons |
text | Text input | Yes — plain text input in modal |
select | Dropdown | Yes — static select (≤5 options) |
number | Numeric input | Partial — text input with hint |
password | Masked input (sensitive) | No — always links to dashboard |
textarea | Multi-line text | Partial — plain text input |
checkbox | Checkbox | No — links to dashboard |
date | Date picker | No — links to dashboard |
Sensitivity
Fields can be marked sensitive: true. Sensitive fields are:
- Never included in Slack messages, emails, or webhook payloads
- Only rendered in the dashboard or terminal (full A2UI clients)
- When a form contains sensitive fields, non-dashboard channels show a summary of the non-sensitive fields and a link: “This request contains sensitive fields — [complete it in the dashboard].”
Examples
Policy Approval (Simple)
When a policy guard blocks a tool invocation:
{
"type": "Form",
"props": {
"title": "Approval Required",
"description": "bulk_delete_records requires approval per your security policy.",
"context": {
"agent": "executor",
"tool": "bulk_delete_records",
"session_id": "sess-42",
"policy": "bulk_operation_guard"
},
"fields": [
{
"id": "decision",
"type": "action",
"options": [
{ "value": "approve", "label": "Approve" },
{ "value": "deny", "label": "Deny" }
]
}
]
}
}
Slack renders this as: Two buttons — Approve and Deny — with the title and description as context text.
Dashboard renders this as: A card with the full context, two buttons, and links to the session and agent details.
When an agent needs login credentials:
{
"type": "Form",
"props": {
"title": "Login Required",
"description": "The agent needs credentials to access geico.com.",
"fields": [
{ "id": "username", "type": "text", "label": "Username", "required": true },
{ "id": "password", "type": "password", "label": "Password", "required": true, "sensitive": true }
]
}
}
Slack renders this as: “Login Required — The agent needs credentials to access geico.com. This request contains sensitive fields — [Complete in Dashboard]” with a button linking to the dashboard.
Dashboard renders this as: A form with a text input and a password input, plus a Submit button.
When an agent needs structured data:
{
"type": "Form",
"props": {
"title": "Invoice Categorization",
"description": "Please categorize these ambiguous line items.",
"fields": [
{
"id": "item_1_category",
"type": "select",
"label": "Server Hosting (Line 3)",
"options": [
{ "value": "infrastructure", "label": "Infrastructure" },
{ "value": "saas", "label": "SaaS" },
{ "value": "other", "label": "Other" }
]
},
{
"id": "item_2_category",
"type": "select",
"label": "Consulting Fee (Line 7)",
"options": [
{ "value": "professional_services", "label": "Professional Services" },
{ "value": "legal", "label": "Legal" },
{ "value": "other", "label": "Other" }
]
},
{ "id": "notes", "type": "textarea", "label": "Additional notes", "required": false }
]
}
}
Slack renders this as: Too many fields for inline rendering — shows summary text + “Complete in Dashboard” button.
Dashboard renders this as: Full form with two dropdowns and a text area.
How A2UI Payloads Flow
- Agent hits a blocking point — policy approval, tool confirmation, or explicit
request_user_input call
- A2UI builder constructs the spec —
a2ui_approval_builder.ts or a2ui_form_builder.ts in the mesh
- Spec is embedded in the A2A task — as a
DataPart with MIME type application/json+a2ui
- Message enters
input-required — the A2UI spec is persisted in task_data
- Notification pipeline fires — renderers translate the spec for each configured channel
- Human responds — from dashboard, Slack, or webhook callback
- Resolution flows back — field values mapped back to the A2UI field IDs, forwarded to the mesh
- Agent continues — receives the response and resumes execution
Rendering Surfaces
| Surface | A2UI Support | Interactive | Sensitive Fields |
|---|
| Dashboard | Full | Yes — forms, buttons, inputs | Yes |
| Terminal (CLI) | Full | Yes — Ink components | Yes |
| Slack | Partial | Yes — buttons, selects, modals | No — links to dashboard |
| Email | Summary only | No — links to dashboard | No |
| Webhook | Full spec as JSON | Yes — receiving system interprets | Depends on receiver |
| Angular app | Full | Yes — @a2ui/angular | Yes |
Relationship to A2A
A2UI payloads travel inside A2A messages as DataPart objects:
{
"kind": "data",
"data": { "type": "Form", "props": { ... } },
"metadata": { "mimeType": "application/json+a2ui" }
}
The application/json+a2ui MIME type tells the receiving client that this part contains a renderable A2UI spec rather than arbitrary data.
When the A2A task state is input-required and the task contains an A2UI DataPart, clients know they need to render a form and send the response back.
ADK’s require_confirmation and request_confirmation() produce tool confirmation events. Svantic maps these to A2UI specs:
| ADK Concept | A2UI Mapping |
|---|
Boolean confirmation (require_confirmation: true) | A2UI form with single action field (approve/deny) |
Advanced confirmation (request_confirmation(hint, payload)) | A2UI form with fields derived from the payload schema |
FunctionResponse with confirmed: true/false | Resolution with decision: approve/deny |
This means ADK tool confirmations flow through the same notification pipeline as Svantic’s policy approvals — same dashboard UI, same Slack integration, same webhook callbacks.
Further Reading