> ## 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

# 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.

```json theme={null}
{
  "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:

```json theme={null}
{
  "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.

### Structured Input (Credentials)

When an agent needs login credentials:

```json theme={null}
{
  "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.

### Multi-Field Form

When an agent needs structured data:

```json theme={null}
{
  "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

1. **Agent hits a blocking point** — policy approval, tool confirmation, or explicit `request_user_input` call
2. **A2UI builder constructs the spec** — `a2ui_approval_builder.ts` or `a2ui_form_builder.ts` in the mesh
3. **Spec is embedded in the A2A task** — as a `DataPart` with MIME type `application/json+a2ui`
4. **Message enters `input-required`** — the A2UI spec is persisted in `task_data`
5. **Notification pipeline fires** — renderers translate the spec for each configured channel
6. **Human responds** — from dashboard, Slack, or webhook callback
7. **Resolution flows back** — field values mapped back to the A2UI field IDs, forwarded to the mesh
8. **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:

```json theme={null}
{
  "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.

## Relationship to ADK Tool Confirmation

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

* [Notifications & Approvals](./notifications) — the full notification pipeline
* [Push Notifications](./push-notifications) — A2A-level async task updates
* [Streaming & Events](./streaming) — how A2UI events flow in SSE streams
* [Guardrails & Safety](./guardrails) — policies that produce A2UI approval requests
* [A2UI specification](https://a2ui.org) — the full A2UI component reference
