a2ui Documentation

Official TypeScript developer resources and documentation for a2ui-project/a2ui with 17k stars. Verified technical context for AI.

16,726 stars TypeScript TypeScript 74 files Β· ~192,334 tokens
RAW Doc

Technical Documentation: a2ui-project/a2ui

ℹ️ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference

Overview (README.md)

A2UI: Agent-to-User Interface

A2UI is an open-source project, complete with a format
optimized for representing updatable agent-generated
UIs and an initial set of renderers, that allows agents
to generate or populate rich user interfaces.

A gallery of A2UI rendered cards, showing a variety of UI compositions that A2UI can achieve.

⚠️ Status: Early stage public preview

Note: A2UI's current production release is v0.9.1, a patch release in
the stable v0.9 protocol family. The v1.0 specification is a release
candidate, while v0.8 is legacy. The specification and implementations are
functional but are still evolving. We are opening the project to foster
collaboration, gather feedback, and solicit contributions (e.g., on client
renderers). Expect changes.

Summary

Generative AI excels at creating text and code, but agents can struggle to
present rich, interactive interfaces to users, especially when those agents
are remote or running across trust boundaries.

A2UI is an open standard and set of libraries that allows agents to
"speak UI." Agents send a declarative JSON format describing the intent of
the UI. The client application then renders this using its own native
component library (Flutter, Angular, Lit, etc.).

This approach ensures that agent-generated UIs are
safe like data, but expressive like code.

High-level philosophy

A2UI was designed to address the specific challenges of interoperable,
cross-platform, generative or template-based UI responses from agents.

The project's core philosophies:

  • Security first: Running arbitrary code generated by an LLM may present a
    security risk. A2UI is a declarative data format, not executable
    code. Your client application maintains a "catalog" of trusted, pre-approved
    UI components (e.g., Card, Button, TextField), and the agent can only request
    to render components from that catalog.
  • LLM-friendly and incrementally updatable: The UI is represented as a flat
    list of components with ID references which is easy for LLMs to generate
    incrementally, allowing for progressive rendering and a responsive user
    experience. An agent can efficiently make incremental changes to the UI based
    on new user requests as the conversation progresses.
  • Framework-agnostic and portable: A2UI separates the UI structure from
    the UI implementation. The agent sends a description of the component tree
    and its associated data model. Your client application is responsible for
    mapping these abstract descriptions to its native widgetsβ€”be it web components,
    Flutter widgets, React components, SwiftUI views or something else entirely.
    The same A2UI JSON payload from an agent can be rendered on multiple different
    clients built on top of different frameworks.
  • Flexibility: A2UI also features an open registry pattern that allows
    developers to map server-side types to custom client implementations, from
    native mobile widgets to React components. By registering a "Smart Wrapper,"
    you can connect any existing UI componentβ€”including secure iframe containers
    for legacy contentβ€”to A2UI's data binding and event system. Crucially, this
    places security firmly in the developer's hands, enabling them to enforce
    strict sandboxing policies and "trust ladders" directly within their custom
    component logic rather than relying solely on the core system.

Use cases

Some of the use cases include:

  • Dynamic Data Collection: An agent generates a bespoke form (date pickers,
    sliders, inputs) based on the specific context of a conversation (e.g.,
    booking a specialized reservation).
  • Remote Sub-Agents: An orchestrator agent delegates a task to a
    remote specialized agent (e.g., a travel booking agent) which returns a
    UI payload to be rendered inside the main chat window.
  • Adaptive Workflows: Enterprise agents that generate approval
    dashboards or data visualizations on the fly based on the user's query.

Architecture

The A2UI flow disconnects the generation of UI from the execution of UI:

  1. Generation: An Agent (using Gemini or another LLM) generates or uses
    a pre-generated A2UI Response, a JSON payload describing the composition
    of UI components and their properties.
  2. Transport: This message is sent to the client application
    (via A2A, AG-UI, etc.).
  3. Resolution: The Client's A2UI Renderer parses the JSON.
  4. Rendering: The Renderer maps the abstract components
    (e.g., type: 'text-field') to the concrete implementation in the client's codebase.

Dependencies

A2UI is designed to be a lightweight format, but it fits into a larger ecosystem:

  • Transports: Compatible with A2A Protocol and AG-UI.
  • LLMs: Can be generated by any model capable of generating JSON output.
  • Host Frameworks: Requires a host application built in a supported framework
    (currently: Web or Flutter).

Getting started

Pick the path that matches where you want to start:

Path What you get Time
🍜 Quickstart Restaurant Finder Demo Full-stack A2UI running locally with a Gemini powered ADK agent and Lit renderer. Learn A2UI end-to-end and customize to your use case. ~5 min
βš›οΈ Use A2UI with Any Agent Framework & Harness Scaffold an AG-UI app or harness for your framework of choice, then enable A2UI rendering over AG-UI. ~5 min
🎨 A2UI Composer Generate A2UI JSON from a visual editor and paste it into any agent prompt β€” no install required. ~1 min
🎬 A2UI Theater Step through pre-built A2UI streaming scenarios across Lit, React, and Angular renderers β€” no install required. ~1 min

Restaurant Finder demo β€” Summary

Prerequisites: Node.js 18+ (with Corepack enabled), uv, and a Gemini API key.

bash
git clone https://github.com/a2ui-project/a2ui.git
cd a2ui
export GEMINI_API_KEY="your_gemini_api_key"

# Enable Corepack (macOS Homebrew users: see tip below)
corepack enable

yarn install
cd samples/client/lit
yarn demo:restaurant

[!TIP]
macOS Homebrew Users: If you previously installed standalone package managers, unlink conflicts before installing Corepack so Corepack can manage versions per-project:

bash
brew unlink yarn pnpm
brew install corepack
corepack enable

These commands install dependencies across workspaces, build the renderers, start the Python agent, and open the client at http://localhost:5173. For step-by-step instructions, alternative demos, and troubleshooting see the full Quickstart.

Use A2UI with Any Agent Framework & Harness β€” Summary

bash
npx create-ag-ui-app@latest

Use the AG-UI CLI with your framework or harness of choice (Google Chat, ADK, LangGraph, CrewAI, Mastra, Strands, Slack, Teams, etc.), then follow the AG-UI guide to enable A2UI rendering. Some scaffold paths use CopilotKit's A2UI runtime with Next.js under the hood, but the setup surface is AG-UI-first.

Other renderers

For Flutter, check out the GenUI SDK, which uses A2UI under the hood. See docs/public/reference/renderers.md for the full list of client implementations.

Roadmap

We hope to work with the community on the following:

  • Spec stabilization: Moving towards a v1.0 specification.
  • More renderers: Adding official support for React, Jetpack Compose, iOS (SwiftUI), and more.
  • Additional transports: Support for REST and more.
  • Additional Agent frameworks: Genkit, LangGraph, and more.

Contribute

A2UI is an Apache 2.0 licensed project. We believe the future of UI is agentic,
and we want to work with you to help build it.

See CONTRIBUTING.md for details on how to get started.


Public Index (`docs/public/index.md`)


hide:

  • toc

A Protocol for Agent-Driven Interfaces

A2UI enables AI agents to generate rich, interactive user interfaces that render natively across web, mobile, and desktopβ€”without executing arbitrary code.

Specification Versions

Version Status Description
v1.0 Candidate Release candidate. Adds client-to-server RPC (actionResponse), action IDs, and renames theme to surfaceProperties. (Previously designated as v0.10 when in draft). Evolution guide β†’
v0.9.1 Current Current production release. Minor refinements to v0.9, standardizing on the application/a2ui+json MIME type and relaxing surfaceId constraints. Evolution guide β†’
v0.9 Stable Previous stable version. Philosophical shift to Prompt-First. Introduces createSurface, client-side functions, custom catalogs, modular schemas, and validation failed error formatting. Evolution guide β†’
v0.8 Legacy Legacy version. Structured Output first. Baseline surfaces, components, data binding, and adjacency list model.

A2UI is Apache 2.0 licensed,
created by Google with contributions from
CopilotKit and the open source community,
and is in active development on GitHub.

A2UI solves the following problem: how can AI agents safely send rich UIs across trust boundaries?

Instead of text-only responses or risky code execution, A2UI lets agents send declarative component descriptions that clients render using their own native widgets. It's like having agents speak a universal UI language.

This repository contains:

  • A2UI specifications (v0.9.1 current, v1.0 candidate).

  • Implementations for renderers (Angular, Flutter, Lit, Markdown, etc.) on the client side.

  • Transports like A2A which communicate A2UI messages between agents and clients.

  • :material-shield-check: Secure by Design


    Declarative data format, not executable code. Agents can only use pre-approved components from your catalogβ€”no UI injection attacks.

  • :material-rocket-launch: LLM-Friendly


    Flat, streaming JSON structure designed for easy generation. LLMs can build UIs incrementally without perfect JSON in one shot.

  • :material-devices: Framework-Agnostic


    One agent response works everywhere. Render the same UI on Angular, Flutter, React, or native mobile with your own styled components.

  • :material-chart-timeline: Progressive Rendering


    Stream UI updates as they're generated. Users see the interface building in real-time instead of waiting for complete responses.

Get Started in 5 Minutes

How It Works

The typical interaction flow consists of these steps:

  1. User sends a message to an AI agent
  2. Agent generates A2UI messages describing the UI (structure + data)
  3. Messages stream to the client application
  4. Client renders using native components (Angular, Flutter, React, etc.)
  5. User interacts with the UI, sending actions back to the agent
  6. Agent responds with updated A2UI messages

A2UI in Action

Landscape Architect Demo

text
<video width="100%" height="auto" controls playsinline style="display: block; aspect-ratio: 16/9; object-fit: cover;">
  
  Your browser does not support the video tag.
</video>


Watch an agent generate all of the interfaces for a landscape architect application. The user uploads a photo; the agent uses Gemini to understand it and generate a custom form for landscaping needs.

Custom Components: Interactive Charts & Maps

text
<video width="100%" height="auto" controls playsinline style="display: block; aspect-ratio: 16/9; object-fit: cover;">
  
  Your browser does not support the video tag.
</video>


Watch an agent chose to respond with a chart component to answer a numerical summary question.  Then the agent chooses a Google Map component to answer a location question.  Both are custom components offered by the client.

A2UI Composer

We have a public A2UI Composer.

Documentation for the A2UI Composer


Public Quickstart (`docs/public/quickstart.md`)

Quickstart: Run A2UI in 5 Minutes

Get hands-on with A2UI by running the restaurant finder demo. This guide will have you experiencing agent-generated UI in less than 5 minutes.

What You'll Build

By the end of this quickstart, you'll have:

  • A running app with an A2UI renderer (Lit or Flutter).
  • A Gemini-powered agent that generates dynamic UIs.
  • An interactive restaurant finder with form generation, time selection, and confirmation flows.
  • Understanding of how A2UI messages flow from agent to UI.

Prerequisites

Before you begin, choose your client framework:

=== "Lit"

text
- **Node.js** (v18 or later with [Corepack](https://nodejs.org/api/corepack.html) enabled) β€” [Download here](https://nodejs.org/)

=== "Flutter"

text
- **Flutter SDK** β€” [Install here](https://docs.flutter.dev/install)

Common Prerequisites:

[!WARNING]
Security Notice

This demo runs an A2A agent that uses Gemini to generate A2UI responses. The agent has access to your API key and will make requests to Google's Gemini API. Always review agent code before running it in production environments.

Step 1: Clone the Repository

bash
git clone https://github.com/a2ui-project/a2ui.git
cd a2ui

Step 2: Set Your API Key

Export your Gemini API key as an environment variable:

bash
export GEMINI_API_KEY="your_gemini_api_key_here"

Step 3: Run the Agent and Client

=== "Lit"

text
The client application source code is located in `samples/client/lit/shell`. Navigate to the parent samples directory to run the demo:

```bash
cd samples/client/lit

# Enable Corepack (macOS Homebrew users: see tip below)
corepack enable

yarn install
yarn demo:restaurant
```

??? note "Running the agent and client separately"
    The `demo:restaurant` command runs both the A2A agent and the web client
    automatically. It is equivalent to:

    **1. Run the Agent:**

    ```bash
    cd samples/agent/adk/restaurant_finder
    uv run .
    ```

    **2. Run the Client:**

    ```bash
    cd samples/client/lit/shell
    yarn dev
    ```

> [!INFO]
> **About Package Managers:**
>
> The A2UI repository uses `yarn` for its development, but `yarn` is not
> required to **use** A2UI. You may use the package manager of your choice
> on your projects (e.g. `npm` or `pnpm`).

=== "Flutter"

text
**1. Run the Agent:**

In your first terminal, start the Python agent backend:

```bash
cd samples/agent/adk/restaurant_finder
uv run .
```

**2. Run the Client:**

In a second terminal, launch the Flutter web application:

```bash
cd samples/client/flutter/restaurant_finder/app
flutter run -d chrome
```

[!TIP]
Demo Running

If everything worked, you should see the demo app. The agent is now ready to
generate UI!

Step 4: Try It Out

In the web app, try these prompts:

  1. "Book a table for 2" - Watch the agent generate a reservation form
  2. "Find Italian restaurants near me" - See dynamic search results
  3. "What are your hours?" - Experience different UI layouts for different intents

The demo application should update after each response from the agent, with UI
completely generated by the Gemini LLM. The resulting screens (list of restaurants,
reservation flow, reservation confirmation...) are not hardcoded in the source
of the app.


How does it work?

Let's dive deeper into how this demo app is put together, and how you can build similar applications with A2UI.

Interaction sequence diagram

mermaid
sequenceDiagram
    actor User
    box Front-end
    participant App@{"alias":"App (A2UI renderer)"}
    end
    box Back-end
    participant Agent@{"alias":"A2A agent (Python)"}
    participant LLM@{"alias":"Gemini API (LLM)"}
    end

    User->>App: Interacts
    App->>Agent: ClientToServerMessage
    activate Agent
    Agent->>LLM: Query LLM
    activate LLM
    LLM-->>Agent: Generate A2UI JSON payload
    deactivate LLM
    Agent-->>App: (Streaming) JSONL messages
    deactivate Agent
    App-->>User: Updated UI
  1. User interacts with your app (sends a message, clicks a button, etc.)
  2. The A2A agent receives it and sends the conversation to Gemini
  3. Gemini generates A2UI JSON messages describing the UI
  4. The A2A agent streams these messages back to the app
  5. The A2UI renderer converts them into UI components
  6. Your app UI updates

The A2UI JSON payloads

Let's peek at what the agent is returning. Here's a simplified example of the JSON messages:

=== "v0.9"

text
**Creating the surface:**

```json
{ "version": "v0.9.1",
  "createSurface": {
    "surfaceId": "main",
    "catalogId": "https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json"
  }}
```

**Defining the UI:**

```json
{ "version": "v0.9.1",
  "updateComponents": {
    "surfaceId": "main",
    "components": [
      {"id": "header", "component": "Text", "text": "# Book Your Table", "variant": "h1"},
      {"id": "date-picker", "component": "DateTimeInput", "label": "Select Date", "value": {"path": "/reservation/date"}, "enableDate": true},
      {"id": "submit-text", "component": "Text", "text": "Confirm Reservation"},
      {"id": "submit-btn", "component": "Button", "child": "submit-text", "variant": "primary", "action": {"event": {"name": "confirm_booking"}}}
    ]
  }}
```

**Populating data:**

```json
{ "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "main",
    "path": "/reservation",
    "value": {"date": "2025-12-15", "time": "19:00", "guests": 2}
  }}
```

=== "Legacy (v0.8)"

text
**Defining the UI:**

```json
{"surfaceUpdate": {"surfaceId": "main", "components": [
  {"id": "header", "component": {"Text": {"text": {"literalString": "Book Your Table"}, "usageHint": "h1"}}},
  {"id": "date-picker", "component": {"DateTimeInput": {"label": {"literalString": "Select Date"}, "value": {"path": "/reservation/date"}, "enableDate": true}}},
  {"id": "submit-text", "component": {"Text": {"text": {"literalString": "Confirm Reservation"}}}},
  {"id": "submit-btn", "component": {"Button": {"child": "submit-text", "action": {"name": "confirm_booking"}}}}
]}}
```

**Populating data:**

```json
{"dataModelUpdate": {"surfaceId": "main", "contents": [
  {"key": "reservation", "valueMap": [
    {"key": "date", "valueString": "2025-12-15"},
    {"key": "time", "valueString": "19:00"},
    {"key": "guests", "valueInt": 2}
  ]}
]}}
```

**Signaling render:**

```json
{"beginRendering": {"surfaceId": "main", "root": "header"}}
```

Note: In v0.8, `createSurface` was `beginRendering`, components were nested
in the response, and the data model used an adjacency list instead of
flat JSON.

[!TIP]
It's Just JSON

Notice how readable and structured this is? LLMs can generate this easily, and it's safe to transmit and renderβ€”no code execution required.

Peek at the source code

Want to see what the code looks like? Check out:

  • Agent Code: samples/agent/adk/restaurant_finder/ β€” The Python A2A agent

=== "Lit"

text
- **Client Code**: `samples/client/lit/` β€” The Lit web client with A2UI renderer
- **A2UI Renderers**: `typescript/renderers/lit/` (Lit) and `typescript/core/web_core/` (framework-agnostic core)

=== "Flutter"

text
- **Client Code**: `samples/client/flutter/` β€” The Flutter web client with A2UI renderer
- **A2UI Renderer**: `dart/a2ui_flutter/` (Flutter)

Each directory has its own README with detailed documentation.


Troubleshooting

=== "Lit"

text
### Port Already in Use

If port 5173 is already in use, the dev server will automatically try the next available port. Check the terminal output for the actual URL.

### Corepack and Homebrew issues

If you have standalone package managers installed, try to unlink conflicts before installing Corepack so Corepack can manage versions per-project:

> ```bash
> $ brew unlink yarn pnpm
> $ brew install corepack
> $ corepack enable
> ```

=== "Flutter"

API Key Issues

If you see errors about missing API keys:

  1. Verify the key is exported: echo $GEMINI_API_KEY
  2. Make sure it's a valid Gemini API key from Google AI Studio
  3. Try re-exporting: export GEMINI_API_KEY="your_key"

Connection Errors on Startup

If you see ERR_CONNECTION_REFUSED errors when the browser opens, don't worry β€” this is a known race condition. The web app may start faster than the Python agent backend. Just wait a few seconds and refresh the page.

Python / uv Issues

The demo agents require uv to run. If you see uv: command not found:

bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Verify
uv --version

If you encounter other Python errors:

bash
# Make sure Python 3.10+ is available
python3 --version

# Try running the agent manually
cd samples/agent/adk/restaurant_finder
uv run .

Still Having Issues?


Exploring more demos

=== "Lit"

text
### Lit Component Gallery (No Agent Required)

See all the provided Basic Catalog components:

If you're running the gallery from a fresh checkout, build the gallery and its workspace dependencies first:

```bash
cd typescript/renderers/lit/a2ui_explorer
yarn build
```

Start the gallery:

```bash
yarn dev
```

This runs a client-only demo showcasing every standard component (Card, Button, TextField, Timeline, etc.) with live examples and code samples.

=== "Flutter"

Other Languages and Frameworks

A2UI provides samples for other popular frameworks in the samples/client directory:

  • Angular: samples/client/angular
  • Flutter: samples/client/flutter
  • Lit: samples/client/lit
  • React: samples/client/react

Explore the samples/client directory to see all available client implementations.


What's Next?

Congratulations! You've successfully run your first A2UI application. You've seen how an AI agent can generate rich, interactive UIs that render natively in a web applicationβ€”all through safe, declarative JSON messages.

Check out the following links to learn more:


Scripts README (`docs/scripts/README.md`)

Documentation Transformation Scripts

This directory contains utility scripts to prepare our documentation for the MkDocs build process.

Purpose

To ensure a great reading experience both on GitHub and the hosted site, the project uses GitHub-flavored Markdown as the primary source of truth. This script transforms GitHub's native syntax into MkDocs-compatible syntax (specifically for the pymdown-extensions) during the build pipeline.

Supported Conversions (Uni-directional)

The script performs a uni-directional transformation: GitHub Markdown β†’ MkDocs Syntax.

Alert/Admonition Conversion

The script handles the following conversions:

  • GitHub uses a blockquote-based syntax for alerts.
  • MkDocs requires the !!! or ??? syntax to render colored callout boxes.

Running the Conversion

The conversion is run as part of the build pipeline. No additional steps are required. If you need to run the conversion manually, you can run the convert_docs.py script in the repository root.

bash
python docs/scripts/convert_docs.py

Example

  • Source (GitHub-flavored Markdown):

    markdown
    > ⚠️ **Attention**
    >
    > This is an alert.
  • Target (MkDocs Syntax):

    markdown
    !!! warning "Attention"
    This is an alert.

Public Composer Index (`docs/public/composer/index.md`)

A2UI Composer

Try building A2UI widgets interactively with the A2UI Composer.

  1. Navigate to the A2UI Composer

  2. By default, you'll be using the Basic catalog provided by an Angular-based
    renderer

  3. Use the Gemini chat to start building an A2UI interface! (Note: This
    requires a Gemini API key. See below)

Using the Composer UI

The Composer workspace is split into several interactive panels to assist in
developing and debugging A2UI surfaces:

  • Gemini Assistant: A chat interface powered by Gemini. You can use
    natural language prompts to ask Gemini to generate new layouts, refine the
    existing JSON, or modify visual attributes.

  • You can upload an attachment (a mock, for example) by clicking the
    paper clip icon
    {style="width:30px;height:30px;display:inline;vertical-align:middle;"}.

  • You can include a screenshot of the
    current A2UI interface by clicking the camera icon
    {style="width:30px;height:30px;display:inline;vertical-align:middle;"}.

  • Rendered A2UI Preview: Displays the live visual preview of the active
    component.

  • A2UI JSON Editor: Displays the raw JSON payload defining the UI
    structure and component hierarchy. You can edit this directly to instantly
    update the rendered preview.

  • The editor has tooltips on hover,
    showing the description of the A2UI element.

  • If you have A2UI JSON from another source, you can paste it into this
    panel. You can right-click to format the JSON if you want.

  • Debug & Inspection Tabs (Bottom):

    • Data Model: Inspects and modifies the runtime state/data values
      bound to the UI components. Changes here propagate to the preview, and
      user inputs in the preview update this model.
    • Events: Logs user-interaction events (e.g., clicks, inputs, or
      selections) dispatched by the rendered components.
    • Errors: Displays errors from rendering, JSON parsing, or API
      failures.
    • Raw Messages: Shows the communication between the Composer and the
      renderer, as well as interactions with Gemini. (See more below.)

The Components gallery allows you to browse through all the components which
are provided in the current A2UI catalog. Each component has an example
rendered, along with the A2UI JSON to produce that rendering. Use the copy icon
{style="width:30px;height:30px;display:inline;vertical-align:middle;"}
at the top of the Usage panel to copy the full A2UI JSON for this component.

At the bottom of the page is a table listing all the properties of the
component with descriptions, type, and indicating whether the property is
required or not.

Settings

Renderer Application

The settings page allows you to change the renderer application being used.
At this time, there are three preloaded renderer applications:

  • Angular Basic Catalog
  • Lit Basic Catalog
  • React Basic Catalog

If you are building another renderer app, you can select "Custom" from the
dropdown, and put the URL in the text box. (For instructions on building a
renderer app, see A2UI Composer Integration).

Gemini API Key

This is also where you can enter a Gemini API key, enabling use of the Gemini
chat feature.

To obtain an API key:

  1. Go to Google AI Studio and sign in
    with your Google account.
  2. Click Create API key.
  3. Select or create a Google Cloud project when prompted, then click Create key.
  4. Save your key in a secure location!

Note that the A2UI Composer encrypts your key and stores it locally in your
browser's secure database using the Web Crypto API.
Neither Google nor anyone else has access to this key.

Work in Progress

The A2UI team is actively working on the following improvements:

  • Latency reduction: Improving latency within Gemini-assisted workflows.
  • Visual understanding: Updating Gemini-assisted workflows to improve
    visual understanding of already rendered surfaces.

Raw Messages

The messages which flow between the Composer and the renderer can help with
troubleshooting and to gain a better understanding of what's going on. These
messages include:

  • RENDERER_READY: Sent by the renderer once it has fully bootstrapped.
  • A2UI_CATALOG: Sent by the renderer (upon request from the Composer).
    Contains the full A2UI catalog supported by the renderer.
  • COMPONENT_USAGES: Sent by the renderer (upon request from the Composer),
    containing the data to support the Component Gallery page.

Whenever there is a change to the A2UI component's data model, the
DATA_MODEL_CHANGE message is captured, showing the updateDataModel
message which would be sent back to the agent.

Additionally, when using the Gemini chat feature:

  • LLM_REQUEST: Shows the full request (including the system prompt) sent
    to the LLM.
  • LLM_RESPONSE: Shows the full response received from the LLM.

Public Concepts Overview (`docs/public/concepts/overview.md`)

Core Concepts

This section explains the fundamental architecture of A2UI. Understanding these concepts will help you build effective agent-driven interfaces.

See Glossary for short definitions of key terms.

The Big Picture

A2UI is built around three core ideas:

  1. Streaming Messages: UI updates flow as a sequence of JSON messages from agent to client
  2. Declarative Components: UIs are described as data, not programmed as code
  3. Data Binding: UI structure is separate from application state, enabling reactive updates

Key Topics

[Data Flow](data-flow.md)

How messages travel from agents to rendered UI. Includes a complete lifecycle example of a restaurant booking flow, transport options (SSE, WebSockets, A2A), progressive rendering, and error handling.

[Component Structure](components.md)

A2UI's adjacency list model for representing component hierarchies. Learn why flat lists are better than nested trees, how to use static vs. dynamic children, and best practices for incremental updates.

[Data Binding](data-binding.md)

How components connect to application state using JSON Pointer paths. Covers reactive components, dynamic lists, input bindings, and the separation of structure from state that makes A2UI powerful.

Message Types

=== "v0.9 (Stable)"

text
Version 0.9 uses the following message types:

- **`createSurface`**: Create a new surface and specify its catalog
- **`updateComponents`**: Add or update UI components in a surface
- **`updateDataModel`**: Update application state
- **`deleteSurface`**: Remove a UI surface

v0.9 separates surface creation from rendering β€” `createSurface` replaces both `beginRendering` and the implicit surface creation in `surfaceUpdate`. All messages include a `version` field.

=== "v1.0 (Candidate)"

text
Version 1.0 uses the following message types:

- **`createSurface`**: Create a new surface and specify its catalog
- **`updateComponents`**: Add or update UI components in a surface
- **`updateDataModel`**: Update application state
- **`deleteSurface`**: Remove a UI surface
- **`actionResponse`**: Respond to client-initiated actions

v1.0 introduces the `actionResponse` message type, enabling robust client-to-server synchronous RPC capabilities.

=== "v0.8 (Legacy)"

text
Version 0.8 uses the following message types:

- **`surfaceUpdate`**: Define or update UI components
- **`dataModelUpdate`**: Update application state
- **`beginRendering`**: Signal the client to render
- **`deleteSurface`**: Remove a UI surface

For complete technical details, see Message Reference.


Contributing Release Pub Dev (`docs/contributing/release-pub-dev.md`)

Maintenance information for publishing to pub.dev

This page contains maintenance information for publishing to pub.dev.
It is needed when main steps stop working or something needs to be changed.

For main publishing steps, see release.md.

Versioning

We use Semver for package versioning, although before 1.0.0, we will be
incrementing only the minor number for breaking changes and the patch number for
non-breaking changes.

Depending on the package category, the process of publishing to pub.dev follows different rules:

The package is not published, and is not planned to be published

pubspec.yaml has publish_to: none.

The package is planned to be published as production-ready (but wasn't published as such before)

The package should be published to pub.dev with a -wip suffix to reserve the name and maybe to try the package for development purposes.

The package is published as production-ready

  1. The package's version: in pubspec.yaml doesn't contain any suffix.
  2. It is recommended to publish the changes after every pull requests.
  3. If your feature is partially implemented, hide the feature's code behind a false-by-default flag, and make sure the version at main is still ready for production.

CI

The following CI workflows support publishing, making sure the packages are always publish-ready:

In this repo:

In genui repo:

How to enable the workflows

For these workflows to function, the GitHub org needs to be configured.

This section instructs how to do that, in case some future reorg will require re-enabling them
(The permissions cover more than needed, because we may want to do more automation in future).

In https://github.com/organizations/a2ui-project/settings/actions:

  1. Find the section "General actions permissions"

  2. Either select "Allow all actions and reusable workflows" or choose "Allow enterprise, and select non-enterprise, actions and reusable workflows" and add these values (if they are already here, they will be de-dupped automatically):

    text
    peter-evans/create-or-update-comment@*,
    peter-evans/create-pull-request@*,
    peter-evans/repository-dispatch@*,
    dart-lang/ecosystem/.github/workflows/health.yaml@*,
    dart-lang/ecosystem/.github/workflows/post_summaries.yaml@*,
    dart-lang/ecosystem/.github/workflows/publish.yaml@*,

Contributing Release (`docs/contributing/release.md`)

Release process

How to publish packages

Release cadence: every 1-2 weeks.

Pub.dev publishing steps

Between releases, each Dart package in this repository collects the notes of
merged pull requests under ## Unreleased at the top of its CHANGELOG.md.
version: in its pubspec.yaml stays at the last released version, and only a
release pull request changes it.

1. Find the packages to release

A package has something to release when its CHANGELOG.md has notes under
## Unreleased:

2. Open a release pull request

In one pull request, for each package to release:

  • Choose the next version, as described in
    Versioning.
  • Rename ## Unreleased in CHANGELOG.md to ## <version>.
  • Set version: in pubspec.yaml to the same version.

Change nothing else, and merge the pull request.

3. Publish the packages

For each package in the release pull request:

  • Check out the commit that merged the release pull request, not the latest
    main, which may already have unreleased changes.
  • Run flutter pub publish in the package directory, making sure the console
    shows no warnings.
  • Verify that the correct version was uploaded to pub.dev.

If any step fails, file a GitHub issue and inform the team.

For troubleshooting and maintenance, see release-pub-dev.md.

Packages in the genui repository

In these changelogs, find the packages whose top version is not -wip... and
is not published yet (follow the link in the CHANGELOG.md header to verify),
then publish them from the latest main of that repository:

NPM

See web_publishing.md.

PyPI

The Python packages are a2ui-core, a2ui-agent-sdk, and the agent framework integrations a2ui-a2a and a2ui-adk. Releasing one or more of them is performed by providing a prompt to an AI assistant within the a2ui project directory.

text
Release <package names, or all> with a <major|minor|patch> bump

Name the packages to release, for example a2ui-agent-sdk or a2ui-a2a and a2ui-adk, or ask for all to release all four.

The assistant uses the a2ui-release-python skill to inspect changelogs, run local preflight checks, execute a dry run, confirm with you, trigger publishing via the GitHub Actions workflow, and open the changelog pull request. Once publication finishes, you only need to find a reviewer to approve and merge the pull request to update the changelog. Released packages appear on PyPI (a2ui-core, a2ui-agent-sdk, a2ui-a2a and a2ui-adk) as well as under corresponding Git tags and GitHub releases that link to them.

How it works behind the scenes

Publishing is handled by the Release Python SDKs workflow on main. There is no version file to edit and no package build or upload to run locally.

The workflow works out the new version from the latest release tag, tags the
release, builds, stages the artifacts in the OSS Exit Gate Artifact Registry,
and uploads the manifest that triggers publishing. The Exit Gate emails
[email protected] when publishing starts and again when it
finishes. The GitHub release is updated with a link to the published version
once it appears on PyPI, either by the release run itself or by the hourly
Confirm PyPI publication
workflow.

If an assistant is unavailable, maintainers can dispatch the workflow manually from the Actions tab on main. Always run with dry_run: true first to verify the plan and artifact staging before running with dry_run: false.

The release only pushes tags

main is covered by a ruleset that requires a pull request and allows no bypass
actors, so the workflow cannot push to it. Being refused mid-run would leave
artifacts staged in the Artifact Registry, so the workflow does not try: it
pushes only tags, which no ruleset covers, and raises the changelog edit as a
pull request afterwards.

Tags therefore point at the commit that was the tip of main when the run
started, not at the changelog commit. That is deliberate. The repository
requires linear history, so a tag created on a branch commit would be left
unreachable once the pull request is squashed, and the git describe check in
python_ci.yml would start failing.

Why the workflow does not open the changelog pull request directly

The release workflow prepares the changelog edit on a release/changelog-* branch and
stops there. It could open the pull request, but that pull request could never
be merged: GitHub does not start workflow runs for events caused by the
built-in GITHUB_TOKEN, so none of the required checks would ever report, and
the ruleset allows no bypass. Opening it yourself from the link in the job
summary (or having an AI assistant open it using user credentials) allows
the required CI checks to run.

Versions come from git tags

Each package has its own tag series (python/a2ui-core/v*,
python/a2ui-agent-sdk/v*, python/a2ui-a2a/v* and python/a2ui-adk/v*), and
hatch-vcs derives the package version from it at build time. Do not hand-edit a version anywhere; the version.py in each
package only reads the version back out of the installed distribution metadata.

A checkout without tags falls back to the version pinned as fallback-version
in pyproject.toml, which keeps shallow CI clones working. The release workflow
reads the version back out of every built artifact and fails if it does not
match the version it planned, so a release cannot go out on a fallback version.

Things that will stop a release

The preflight checks in
release_version.py fail the run when
the Unreleased section is empty, when the target version already has a tag,
when a proposed version falls outside the range that another package pins it
to, and when a package depends on another package of this repository that no
released version satisfies.

The pins form a chain: a2ui-agent-sdk pins a2ui-core, a2ui-a2a and
a2ui-adk pin a2ui-agent-sdk, and a2ui-adk pins a2ui-a2a in its a2a
extra. A minor a2ui-core bump, for example, needs the pin in
a2ui_agent/pyproject.toml
widened in a pull request that is merged to main before the release is
dispatched. The workflow releases main as it is and cannot change the pin
during the run.

A dependency can be released in the same run: a release of several packages
publishes them in dependency order, a2ui-core, a2ui-agent-sdk,
a2ui-a2a, a2ui-adk, so each package's new dependencies are on PyPI first.
Releasing a2ui-a2a or a2ui-adk on its own requires a released version of
each dependency that satisfies its pins.

a2ui-a2a and a2ui-adk start from 0.0.0, so their first release uses a
minor bump to publish 0.1.0. Both require an a2ui-agent-sdk release newer
than 0.8.0, the last one that shipped a2ui.a2a and a2ui.adk itself. For
their first release, release a2ui-agent-sdk, a2ui-a2a and a2ui-adk
together with a minor bump, or release a2ui-agent-sdk first, merge its
changelog pull request, then release a2ui-a2a and a2ui-adk with a minor
bump.

If authentication fails

The Exit Gate authorizes this repository through Workload Identity Federation,
matching the workflow file path and branch exactly against an allowlist entry in
Google's internal project config:

text
builders: "github_workflow:a2ui-project/a2ui/.github/workflows/release-pypi.yml@refs/heads/main"

Two consequences worth knowing. Renaming release-pypi.yml breaks releases
until the internal config is updated. And the workflow cannot be triggered by
pushing a tag, because the matched claim includes the triggering ref, which is
why the release is dispatched from main and creates its own tag rather than
being started by one.

To test access and artifact staging without publishing anything, run the
Release Python SDKs workflow with
dry_run: true. See go/oss-exit-gate-builders and go/oss-exit-gate-onduty.

Documentation website

MkDocs, configured in .github/workflows/docs.yml, updates https://a2ui.org/ whenever the content of docs/public changes.

Internal troubleshooting and notes

See go/a2ui-release for internal information.


Contributing Triage Templates (`docs/contributing/triage-templates.md`)

A2UI Standard Triage Replies

Canonical replies for recurring triage cases.

Issues

Weekly A2UI Compliance Report

Case: AI-generated compliance report issues, such as
Weekly A2UI Compliance Report.

Triage: priority P3, action backlog (keeps the issue open).

As agreed, this has been assigned a P3 priority: we need better workflow to make it easier to follow up on the findings.

Assigned issue without a priority

Case: The issue already has an assignee but no priority label, so it sits in the triage
queue with nothing else to decide.

Triage: priority P2, action assign_and_fix, keeping the existing assignee.

I assigned a P2 priority to move this out of the triage queue. Please adjust the priority
if needed to better reflect the issue's status.

Stale P2 issue

Case: The issue is P2 but stale.

Triage: add comment.

This issue should be considered for a P1 priority together with other P2 issues in the next planning cycle after we burn through the triage queue.

Stale assigned P1 issue

Case: The issue is P1 and has an assignee, but has been stale for a while.

Triage: add comment.

Ping from triage process: is this issue still on your radar?

Out of Scope / Roadmap Conflict

Case: The user suggests a feature or change that is outside the current scope of the A2UI protocol roadmap.

Triage: priority P4, action backlog (keeps the issue open).

Thanks for the suggestion. This falls outside the current scope of the A2UI protocol roadmap.

Marked as P4: we do not plan to invest in it, and PRs against it will not be reviewed. Leaving it open as a record of the request β€” comment here if you have new arguments to share.

Pull requests

These cases are handled by the gardener directly. The dashboard and apply script
cover issues only, so the actions below are performed by hand.

Superseded PR

Case: A valid contribution that has gone stale and was replaced by a newer PR carrying
the original commit.

Triage: close the PR, linking the superseding one.

Thank you for your contribution! We apologize that this PR has become outdated. To
expedite the process, I created a superseding PR based on your analysis and changes. Your
original commit is included to ensure your contribution is properly recorded.
<link to new PR>

Complex PR without an associated issue

Case: A complex PR that does not address an issue. Straightforward and minor PRs are exempt.

Triage: close the PR.

Thank you for your contribution. Unfortunately, we must decline this PR at this time.
Our team resources are limited, and our policy is to prioritize the review of PRs that address an existing issue prioritized issues. Consider opening issue
and link your PR in the issue description as one of the options for resolution.

PR outside the project's scope

Case: The change targets functionality the maintenance team cannot support long-term.

Triage: close the PR.

Thank you for your contribution to the A2UI ecosystem! Unfortunately, we must decline this PR as the scope of this repository is restricted to features the maintenance team can support long-term. We encourage you to find a more suitable repository for this code or to consider launching your own repository to support this segment of the A2UI ecosystem.

Complex PR towards existing issue but not assigned to the author

Case: Complex PR that is not assigned to the author.
It might be a good contribution, but we do not have enough bandwidth to review it at this time.

Triage: close the PR.

Thank you for your contribution to the A2UI ecosystem! Unfortunately, we must decline this PR at this time: we review PRs against an issue only from the contributor the issue is assigned to, and this one is not assigned to you. Issues are assigned in priority order, so we cannot say yet when this one will be picked up.

We will link this PR to the issue, so whoever takes it on can see your approach. They may reopen this PR and build on it rather than start from scratch.

Meaningful stale PR with conflicts

Case: The PR is meaningful, but has conflicts with the main branch.

Triage: assign status: waiting-for-user-response.

Thank you for your contribution. I am sorry it took so long for our team to review your PR. Unfortunately the branch has conflicts with the main branch. If you believe the issue is not resolved yet, can you, please, resolve the conflicts? We will review it again after you resolve conflicts.

PR that closes already closed issue

Case: The PR closes an issue that has already been closed.

Triage: close the PR.

Thank you for your contribution. I am closing this PR as the linked issue has already been resolved.


Contributing Triage (`docs/contributing/triage.md`)

Triage for PRs and issues

See go/a2ui-triage-internal for internal information.

Goals

Continuously make sure that on a2ui:

  1. External PRs are addressed
  2. Issues are prioritized and addressed
  3. Number of branches is observable

Invariant we want to keep

This section describes goals at a high level. See concrete steps in the section Triage responsibilities.

  1. Issues priority (aligned with other teams in Dash):

    • P0: very urgent, and it should be assigned
    • P1: actively being worked on, and it should be assigned
    • P2: is expected to be converted to P1 within three months, as part of regular planning process
    • P3: not planned, but may become a priority
    • P4: we do not plan to invest in this issue
  2. PRs: We will review PRs from external contributors if at least one of the following is true:

    • the PR contributes to a P0-P3 issue (PRs for P4 issues will not be reviewed)
    • the issue is assigned to a contributor by the A2UI team (as assignee or in the first line of issue description) or has label type: contributions-welcome
    • the change is, in the opinion of the A2UI team, absolutely clear and obviously needed

The relevant issue should be linked in the the PR description.

  1. Branches: list of stale branches should fit on one screen and should not have a button β€˜Next’.

GitHub labels used in triage

  1. Priority labels: P0, P1, P2, P3, P4
  2. status: needs-team-input
  3. status: needs-triage
  4. status: first-line-handled
  5. size: small
  6. type: contributions-welcome
  7. status: waiting-for-author-response

See all github labels.

Two of these labels are automated: status: needs-triage is managed entirely by the bot, and status: waiting-for-author-response is applied manually and automatically cleared once the author replies.

How the automated triage bot works

The triage.mjs script runs via the Flag/Unflag issues and PRs GitHub Actions workflow. It reconciles status: needs-triage across all open items according to the following rules:

  1. Skipped items: The automation skips the following items:
    • Items waiting on author: If an item has status: waiting-for-author-response, it is skipped. The automation removes this label once an external contributor posts a comment, review or review comment after the label was added.
    • Assigned issues: When an issue is assigned, it is assumed that a team member is looking into it.
  2. Issues: Flagged with status: needs-triage if any of the following apply:
    • No priority: The issue lacks a priority label (P0–P4).
    • Unassigned high priority: Labeled P0 or P1 without an assignee.
    • Stale: Inactive beyond the priority threshold:
      • P0: stale for > 1 day
      • P1: stale for > 30 days
      • P2: stale for > 90 days
      • (P3 and P4 issues are never flagged for staleness)
    • Unanswered external comment: The latest human comment is from an external contributor.
  3. Pull Requests: Flagged with status: needs-triage if opened by an external contributor and no maintainer has responded to the author's latest contribution. (Maintainer-authored PRs are not flagged).
  4. Transparency: The automation prints to the console what items are flagged or unflagged and why. This can be used to track triage progress.

Staleness is calculated from the last human contribution (comment, review, or issue/PR creation) rather than updated_at to avoid bot edits resetting the timer.

The workflow runs daily on a schedule (15:00 UTC / 07:00 PST), on manual dispatch, and automatically on issue events (opened, edited, labeled, unlabeled, assigned, unassigned, reopened), new issue comments, and submitted PR reviews or review comments.

Triage responsibilities

First line triage (daily)

For each issue that is not first-line-handled:

  • If it is P0, add label P0 and notify team chat
  • Add label status: first-line-handled

Second line triage (weekly)

Update items with label status: needs-triage, so that the label, managed by the triage bot, disappears. Do at least one of the following:

  • Set priority (P0-P4)
    • Be sure to assign P0/P1 issues to someone.
  • Answer external comments
  • Add status: waiting-for-author-response when you need more information from the author

You can remove the needs-triage label manually to speed up the process, but if the item still matches a rule, the label will come back because the label will be re-applied by the triage bot.

For items where you need input from the team, follow this process (passing any remaining follow-up to the next gardener if still in progress):

  1. add the status: needs-team-input label
  2. post a message to the team chat, suggesting options and/or asking for input
  3. drive the discussion to resolution
  4. remove the label status: needs-team-input

Use standard replies that are provided for standard cases.

Lastly, check for issues which are still unlabeled/unprioritized (without P0-P4 label and haven't had status: needs-triage, status: needs-team-input or status: waiting-for-author-response added to them). If you find such issues, follow the status: needs-triage workflow above (This is just to check for issues which somehow fall through the cracks).

When weekly triage is complete, the needs-triage list should ideally be empty.

AI assistance

Use .agents/skills/a2ui-issue-triage to get agent help with triage.

It forks multiple subagents (one for each issue) to try and reproduce the issue if it is something that can be easily reproduced. It won't attempt to repro something that takes a lot of setup.

It will then display a web UI in a local browser with the list of issues to triage, suggested responses (which you can edit) and a UI to apply suggestions to GitHub.

Frequently asked questions

Why do we allow branches on the repo and thus create work for maintainers?

Eval and e2e tests cannot be executed on pre-submit for PRs from forks, because they require an API key that is visible only on the original repo.

We want evals and e2e's to run on pre-submit at least for team members.

Watching branches is not big extra toil for triage process, because:

  1. If we forbid branches, the work will not disappear, it will just move to the fork
  2. It is easy to cleanup branches because ownership is clear
  3. As team members know it is part of triage to clean them up, they are more careful managing their branches

Why do we need P4? Why not just close the issue?

We need the label P4, because:

  1. Just closing the issue will not give a clear searchable sign why it is closed
  2. External developers can reopen issue, but cannot change label

It is better not to close P4, because:

  1. As we have label P4 anyway, closing the issue is an extra step.

  2. If P4 is open, it is a sign that this feature request is still:

    1. not implemented
    2. seems to be valuable
    3. considered P4

    which adds clarity and makes it harder to push for prioritization.


Contributing Web Publishing (`docs/contributing/web_publishing.md`)

Publishing Guide for A2UI Web Packages

This guide outlines the workflow for project maintainers publishing web packages
(@a2ui/*) to npm through Google's internal Artifact Registry.

Prerequisites: Authentication

Publishing requires access to Google's internal Artifact Registry. Google Cloud
CLI must be authenticated before starting:

sh
gcloud auth login

The release scripts retrieve this authentication token using
gcloud auth print-access-token when needed.


Release Workflow

All scripts should be executed from the repository root. The workflow consists
of a pull request bumping the versions of the packages to release, followed by
publishing from the main branch.

1. Increment Versions (in a Pull Request)

increment_version.mjs can be run on a local branch to bump package versions
and synchronize locks:

sh
# Increment patch version automatically (e.g., 0.9.5 -> 0.9.6)
./typescript/scripts/increment_version.mjs web_core

# Set an explicit version
./typescript/scripts/increment_version.mjs lit 0.10.0-beta.1

This branch should be merged into main through a PR as with any other change
to the repo.

CLI parameters for increment_version.mjs:

  • ``: The name of the package to update (e.g., web_core or @a2ui/web_core).
  • [new-version]: The specific new version to set (e.g., 1.0.1). If omitted, increments the patch version automatically.
  • --skip-sync: Skip synchronizing dependent packages (running yarn install in dependents). Not recommended.

2. Publish to Staging (from `main`)

Once the new versions land in main and it is checked out locally,
publish_npm.mjs can be used to build, test, and upload the packages to
Google's internal Artifact Registry:

sh
./typescript/scripts/publish_npm.mjs --package=lit --package=web_core

This script publishes requested packages in the correct dependency order (e.g.,
ensuring web_core is published before lit), runs unit tests, and verifies
that required core packages exist on the registry.

By default, the script runs in dry-run mode to prevent accidental uploads; the
--no-dry-run flag can be passed to actually upload the packages:

sh
./typescript/scripts/publish_npm.mjs --package=lit --package=web_core --no-dry-run

Artifacts are uploaded to: go/a2ui-oss-exit-gate-artifacts.
This URL points to a web app that allows verifying that the packages have been uploaded correctly.

For each package it should be checked:

  • That it has been uploaded at the expected version
  • That its size is non-zero in the "Files" tab of the details of the version.

CLI parameters for publish_npm.mjs:

  • -p, --package=<name>: Package(s) to publish. Can be specified multiple times. Accepts short names (e.g., web_core) or scoped names (e.g., @a2ui/web_core).
  • --no-dry-run: Actually publish the packages. By default, the script runs in dry-run mode.
  • --skip-tests: Skip building and testing packages before publishing. Not recommended.

3. Trigger Public Release (from `main`)

Once packages are verified in staging, a release manifest must be uploaded to
trigger the internal Exit Gate pipeline, which publishes them to npm:

sh
./typescript/scripts/upload_manifest.mjs --package=web_core --package=lit

Similarly to publish_npm.mjs, this script runs in dry-run by default, so the
--no-dry-run flag must be passed to actually trigger the release:

sh
./typescript/scripts/upload_manifest.mjs --package=web_core --package=lit --no-dry-run

Confirmation emails from Exit Gate and npm reporting on the progress of the
actual publishing will be sent automatically. Publishing normally takes around
5 minutes.

CLI parameters for upload_manifest.mjs:

  • -p, --package=<name>: Package(s) to trigger release for (e.g., --package=lit). Can be specified multiple times.
  • --no-dry-run: Actually trigger the public release via Exit Gate. By default, the script runs in dry-run mode.

What is the `publish:package` yarn script doing?

A2UI web packages depend on each other via workspace:* links during development. When publish_npm.mjs invokes a package's publish:package target, the following preparation steps occur:

  1. Build & Metadata Transformation: prepare-publish.mjs copies build output into dist/, replaces internal workspace: protocols with absolute semantic version ranges (e.g., ^0.10.3), and strips development scripts/dependencies.
  2. Boundary Isolation: Because the root workspace config excludes dist/ (!**/dist), an empty yarn.lock is initialized inside dist/ to establish it as an independent package boundary.
  3. Clean Upload: yarn npm publish --access public executes strictly inside dist/, ensuring only clean production assets are uploaded.

What are valid values for the `--package` argument?

The increment_version.mjs, publish_npm.mjs and upload_manifest.mjs scripts
work with all publishable web packages in the typescript directory of the monorepo:

  • web_core
  • markdown-it
  • angular
  • lit
  • react

The scripts also support the full package names, e.g. @a2ui/web_core, but the
@a2ui/ prefix is not required.


Troubleshooting

  • Dirty Working Tree Warnings: If build artifacts or temporary files
    persist, yarn clean:all can be run from the monorepo root before trying again.

Proposals Eval Suite Expansion (`docs/proposals/eval_suite_expansion.md`)

Evaluation dataset expansion and agentic data points design

Status: Draft

Author: A2UI Team

Created: 2026-07-23

Background

The A2UI evaluation framework located in eval/ tests whether Large Language Models generate valid and contextually appropriate A2UI JSON payloads. The current framework runs Inspect AI tasks (eval/tasks.py) against prompt datasets stored in YAML format. Datasets are encrypted at rest using Transcrypt to prevent data contamination from web crawlers.

While the existing system tests basic generation well, real-world agentic applications present scenarios that the current setup cannot evaluate:

  1. Monolithic dataset files: All evaluation samples reside in monolithic YAML files. There is no mechanism to isolate data points into separate modular datasets (such as core_v0_9_1, customer_a_data, or banking_app), execute only a specific dataset, or view separate reporting tables per dataset.
  2. Disconnected single-string prompts: The existing schema (eval/datasets/dataset_schema.json) uses a standalone promptText string. Real agentic applications pass a structured messages list containing prior user queries, assistant responses, and tool calls with execution outputs before the model generates UI.
  3. Domain system prompts and custom catalogs: In production applications, models receive domain-specific system prompts (such as customer support rules, flight booking domain logic, or medical guidelines) that are separate from A2UI protocol instructions. Applications also define custom component catalogs with specialized UI elements. The current eval engine only injects A2UI protocol instructions without merging custom domain system prompts or supporting multi-turn chat history.
  4. Contributor onboarding: Contributors and external teams creating new data points need a clear, step-by-step workflow covering decryption, schema verification, local evaluation execution, and safe encrypted commits.

This proposal outlines the technical design for expanding the evaluation framework to support separate modular datasets, standard LLM chat completion message formats (messages), custom catalogs with merged domain system prompts, and a contributor guide. All existing data points are cleanly migrated into modular dataset files under eval/datasets/, eliminating legacy monolithic files and obsolete field aliases.


Architecture overview

The expanded evaluation pipeline adds dataset discovery, standard LLM messages array parsing, system prompt merging, and dataset-aware reporting to Inspect AI.

text
/* Detailed source-code truncated for AI context efficiency. */

1. Separate datasets of data points

Directory organization

Dataset files will be organized under eval/datasets/ as separate modular YAML files. The legacy monolithic files (prompts_v0_9_1.yaml and prompts_v1_0.yaml) are migrated directly into named dataset files:

text
eval/datasets/
β”œβ”€β”€ .gitattributes                  # Transcrypt filter rules
β”œβ”€β”€ dataset_schema.json             # Updated JSON schema for data points
β”œβ”€β”€ defaults.py                     # Default prompt templates and catalog paths
β”œβ”€β”€ core_v0_9_1.yaml                # Migrated core dataset for v0.9.1 protocol
β”œβ”€β”€ core_v1_0.yaml                  # Migrated core dataset for v1.0 protocol
β”œβ”€β”€ customer_a_data.yaml            # Customer A specific agentic scenarios
└── flight_booking.yaml             # Domain-specific multi-turn dataset

Each YAML file in eval/datasets/ represents a distinct evaluation dataset named after its filename (e.g. customer_a_data.yaml defines the customer_a_data dataset, and core_v0_9_1.yaml defines the core_v0_9_1 dataset). Alternatively, a sample can specify an explicit dataset property in its metadata.

Dataset resolution and CLI execution

The CLI runner in eval/main.py and the CI runner in eval/bin/run_ci_evals.py will support filtering by dataset:

bash
# Run all datasets (default)
uv run main.py

# Run only a specific dataset
uv run main.py --dataset customer_a_data

# Run multiple specific datasets
uv run main.py --datasets customer_a_data,core_v0_9_1

# Run a quick sanity check on a specific dataset
uv run main.py --dataset customer_a_data --sanity

CLI argument specifications

  • --dataset <name>: Evaluates only samples belonging to the named dataset.
  • --datasets <name1,name2>: Evaluates samples belonging to any of the listed datasets (comma-separated).
  • When no dataset flag is supplied, the framework defaults to evaluating all active datasets or the default core datasets.

Dataset-aware reporting and summary tables

The evaluation reporting tools in eval/bin/report_evals.py will group and calculate metrics per dataset:

Console output format

text
=== Evaluation Results Summary ===

--- Dataset: customer_a_data (Pass Rate: 100.00%) ---
customer_a_flight_select  | Algorithmic: PASS | Judging: C  | Inference Time: 3.20s
customer_a_seat_picker    | Algorithmic: PASS | Judging: C  | Inference Time: 2.80s

--- Dataset: core_v0_9_1 (Pass Rate: 90.00%) ---
contact_form              | Algorithmic: PASS | Judging: C  | Inference Time: 1.90s
data_binding_list         | Algorithmic: PASS | Judging: P  | Inference Time: 2.10s
  [Judging Failure Reason (Grade P)]:
    Minor cosmetic variation in column label.

==================================
Dataset Summary:
  customer_a_data : 2/2 passed (100.00%)
  core_v0_9_1     : 9/10 passed (90.00%)
Overall Pass Rate: 91.67% (Threshold: 90.00%)
Inference Time - Average: 2.50s | Median: 2.45s
==================================

Markdown summary format (`eval_summary.md`)

The generated markdown summary file will include a top-level dataset summary table followed by detailed per-dataset breakdowns:

markdown
### Evaluation Summary: a2ui_v0_9_1_eval

- **Status**: PASS
- **Model**: `google/gemini-3.6-flash`
- **Overall Pass Percentage**: `91.67%` (Threshold: `90.00%`)

#### Dataset Performance Summary

| Dataset Name      | Total Samples | Algorithmic Pass Rate | Judging Pass Rate | Dataset Pass Rate |
| :---------------- | :-----------: | :-------------------: | :---------------: | :---------------: |
| `customer_a_data` |       2       |        100.0%         |      100.0%       |      100.0%       |
| `core_v0_9_1`     |      10       |        100.0%         |       90.0%       |       90.0%       |

#### Detailed Dataset Results: customer_a_data

| Sample / Task            | Algorithmic | Judging | Inference Time | Status |
| :----------------------- | :---------: | :-----: | :------------: | :----: |
| customer_a_flight_select |    PASS     |    C    |     3.20s      |  PASS  |
| customer_a_seat_picker   |    PASS     |    C    |     2.80s      |  PASS  |

2. Standard LLM chat schema and field reference guide

To align the evaluation format with industry-standard LLM chat completion APIs (such as OpenAI Chat Completions, Anthropic Messages API, and Inspect AI's internal state.messages), conversational turns and user prompts are consolidated under the familiar messages array. Every data point explicitly declares its required catalog path.

Standalone field reference

Field Name Type Required? Clear Explanation & Purpose
name string Yes Unique identifier for this evaluation sample (e.g. customer_a_login_form). Used in logs and result tables.
description string Yes Human-readable explanation of what this test case verifies.
catalog string Yes Path to the component catalog JSON file defining available UI widgets and validation functions. Supports the {version} placeholder (e.g. specification/{version}/catalogs/basic/catalog.json). Required on every data point so solvers and scorers explicitly know which component schema and rules apply.
messages array of message objects Yes The conversation messages. An ordered list of chat turns preceding and including the final UI request. Each item specifies a role (user, assistant, system, or tool), content, and optional tool_calls or tool_call_id.
target string No The primary grading criteria and expected UI outcome. Used by the LLM-as-a-judge (measured_model_graded_qa) to evaluate whether the generated output meets requirements. If omitted, defaults to the value of description.
dataset string No The name of the dataset module this sample belongs to. If omitted, the loader infers it from the YAML filename.
system_prompt string No Domain-specific system instructions representing the host application (e.g., application persona, customer service rules, or API constraints). The eval engine merges this domain prompt with the A2UI protocol instructions.
protocol_role string No The A2UI persona instruction passed to the SDK prompt generator. Specifies the high-level role for UI generation (Default: "You are an AI assistant. Based on the following request, generate a stream of JSON messages that conform to the provided JSON Schemas."). Most contributors can leave this omitted.
generation_rules string No Technical protocol constraints appended to the SDK workflow prompt. Defines formatting rules such as required surface ID naming, component root ID constraints, or adjacency list rules. Most contributors can leave this omitted to use standard protocol defaults.
allowed_surface_ids array of strings No List of valid surface identifiers the model is allowed to target (Default: ["main"]).

Detailed breakdown of key concepts

1. Why `catalog` is required

Every A2UI evaluation requires a component catalog. Making catalog mandatory removes ambiguity for contributors, solver prompt generators, and validators. A contributor always explicitly declares the catalog path (such as specification/{version}/catalogs/basic/catalog.json for standard protocol widgets or a domain catalog path like specification/{version}/catalogs/customer_a/catalog.json).

2. Why `messages` is the preferred terminology

Across the LLM ecosystem (OpenAI, Anthropic, LiteLLM, Ollama, and Inspect AI), messages is the universal term for chat interaction sequences:

  • Direct framework alignment: Inspect AI stores conversation turns in state.messages. Using messages in our YAML schema creates a 1:1 conceptual mapping.
  • Avoids ambiguity: The word "context" is overloaded in AI (often referring to RAG retrieval chunks, context windows, or A2UI action event contexts like action.event.context). messages unambiguously refers to the chat history.
  • Consistent single-turn and multi-turn authoring:
    • Single-turn prompt: messages contains a single user turn (role: user, content: "Create a login form").
    • Multi-turn conversation: messages contains prior user, assistant, and tool turns, ending with the user turn that prompts the UI response.

3. `target` vs `description`

  • description is a high-level summary of what the test case does, used for human documentation and readability in test manifests.
  • target is the detailed rubric evaluated by the grading model. It specifies the exact expectations for the UI, such as required component types (e.g. "Must contain a Card with a TextField and a primary Button"), data bindings (e.g. "TextField must bind to /auth/email"), and interactive actions.

4. `system_prompt` vs `protocol_role`

  • system_prompt represents the domain application persona. In a real application, an agent has domain rules unrelated to A2UI (for example: "You are the Acme Airlines Agent. Always confirm passenger baggage before checkout.").
  • protocol_role represents the A2UI protocol prompt. This is the standard SDK instruction telling the LLM that it must output structured JSON conforming to the A2UI schema.
  • The eval engine merges both: domain instructions are presented first, followed by protocol instructions.

5. `generation_rules`

  • This field controls the technical A2UI rules injected into the prompt's ## Workflow Description: section.
  • Standard protocol rules (e.g. "Construct the UI on surfaceId 'main'" and "Ensure there is one root component with id 'root'") are injected by default.
  • If a test case needs custom protocol behavior (for example, rendering to a surface named 'sidebar'), the contributor specifies those extra rules in generation_rules.

JSON schema specification

The schema in eval/datasets/dataset_schema.json validates the clean dataset structure with catalog required:

text
/* Detailed source-code truncated for AI context efficiency. */

Example multi-turn agentic data point (YAML)

Below is an example of an agentic data point representing a flight booking assistant that interacts via tools before rendering a custom A2UI interface:

text
/* Detailed source-code truncated for AI context efficiency. */

Example single-turn data point (YAML)

Below is an example of a migrated single-turn core data point:

yaml
- name: contact_form
  dataset: core_v0_9_1
  description: Renders a contact form with email and submit button.
  catalog: 'specification/{version}/catalogs/basic/catalog.json'
  messages:
    - role: user
      content: 'Create a contact form with an email text field and submit button.'
  target: |
    The response must contain a Card or Column with a TextField bound to '/contact/email' and a primary Button.

3. Eval engine execution mechanics

When executing an evaluation sample, the solver in eval/a2ui_eval/strategies/format.py coordinates prompt merging, chat message list assembly, inference, and scoring.

Step 1: System prompt generation and merging

The solver merges the domain-specific system prompt with the protocol instructions generated by the A2UI SDK:

  1. SDK prompt generation: The A2UI SDK DirectJsonPromptGenerator loads the required catalog and generates protocol rules, JSON schemas, component catalog definitions, and output tag requirements (<a2ui-json>).
  2. Domain prompt merging: If the data point includes a system_prompt, the solver combines them into a unified system instruction:
python
def merge_system_prompts(domain_prompt: str, a2ui_prompt: str) -> str:
    """Merges domain-specific rules with A2UI protocol instructions."""
    parts = []
    if domain_prompt.strip():
        parts.append(f"## Domain Instructions\n{domain_prompt.strip()}")
    if a2ui_prompt.strip():
        parts.append(f"## UI Protocol Instructions\n{a2ui_prompt.strip()}")
    return "\n\n".join(parts)
  1. The merged prompt is inserted as the first message (ChatMessageSystem) in Inspect AI's state.messages.

Step 2: Chat messages assembly

The solver populates state.messages directly from the sample's messages array:

  • User turns: Converted to ChatMessageUser(content=turn["content"]).
  • Assistant turns: Converted to ChatMessageAssistant(content=turn.get("content", ""), tool_calls=turn.get("tool_calls")).
  • Tool turns: Converted to ChatMessageTool(content=turn["content"], tool_call_id=turn["tool_call_id"]).

The final turn in messages triggers the assistant's UI generation response.

Step 3: Model inference

The solver invokes measured_generate(), passing the assembled message list to the model. The model generates its completion on the final turn.

Step 4: Multi-stage scoring

  1. Programmatic validation (a2ui_scorer):
    • Resolves the required catalog path specified in the data point.
    • Parses JSON from <a2ui-json> tags.
    • Runs the catalog validator (validate_components) to verify schema adherence, parent-child references, and root component presence.
  2. LLM judge scoring (measured_model_graded_qa):
    • Evaluates the generated UI against the target criteria using the grading model (e.g. google/gemini-3.6-flash).
    • Produces a grade of C (Correct), P (Partial Credit), or I (Incorrect).

4. Contributor guide: checking in data points and encryption

This step-by-step guide is for contributors adding or updating data points in the A2UI evaluation framework.

Why data points are encrypted

Evaluation datasets are encrypted at rest in Git using Transcrypt. This prevents public search engines and automated LLM training web crawlers from indexing our test prompts and answers, avoiding artificial score inflation from model memorization.

Step 1: Initial decryption setup

Before modifying or viewing datasets, initialize Transcrypt with the repository evaluation password:

bash
cd eval
bin/transcrypt -p <PASSWORD>

(Request the password from an A2UI maintainer if you do not have it.)

Once decrypted, all YAML files in eval/datasets/ appear as regular plaintext files on your local filesystem.

Step 2: Create or update a dataset file

  1. Create a new YAML file under eval/datasets/<dataset_name>.yaml (for example, eval/datasets/customer_a_data.yaml).
  2. Add your evaluation samples following the standard chat completion format with a required catalog:
yaml
- name: customer_a_login_form
  dataset: customer_a_data
  description: Tests generation of a secure login card with email and password fields.
  catalog: 'specification/{version}/catalogs/basic/catalog.json'
  system_prompt: 'You are the Customer A Authentication Agent. Help users sign in.'
  messages:
    - role: user
      content: 'Create a login form with email, password, and a submit button.'
  target: |
    The response must contain:
    1. A Card or Column root component.
    2. A TextField for email bound to '/auth/email'.
    3. A TextField for password bound to '/auth/password'.
    4. A primary Button labeled 'Sign In'.

Step 3: Validate your dataset against the schema

Run the automated dataset schema test before running evaluations:

bash
uv run python -m pytest tests/test_dataset.py

You can also validate specific dataset files directly:

bash
uv run python -m a2ui_eval.dataset --validate datasets/customer_a_data.yaml

If your YAML file violates the schema (e.g. missing required fields like catalog or unknown properties), the validator will output the exact line and error.

Step 4: Run a local evaluation test

Test how your new data points perform against Gemini models:

bash
# Set your API key
export GEMINI_API_KEY="your_api_key"

# Run a quick sanity check on your new dataset (2 samples, flash-lite)
uv run main.py --dataset customer_a_data --sanity

# Run the full evaluation on your dataset
uv run main.py --dataset customer_a_data

Step 5: Inspect results in the log viewer

Start Inspect AI's interactive web viewer to examine model outputs, merged system prompts, and validator rationales:

bash
uv run inspect view start

Open http://localhost:7575 in your browser to view the trace of each sample.

Step 6: Commit and push changes

Because .gitattributes configures Transcrypt filters for *.yaml files in the eval/datasets/ directory, Git will automatically encrypt the files when staging:

bash
git add eval/datasets/customer_a_data.yaml
git commit -m "feat(eval): add customer_a_data evaluation dataset"
git push origin your-branch

To verify that files are encrypted before pushing, run:

bash
git diff --cached

If Transcrypt is active, the staged diff will show encrypted cipher text.


5. Migration path for existing data points

The evaluation framework migrated legacy monolithic prompt files (prompts_v0_9_1.yaml and prompts_v1_0.yaml) into clean, modular dataset files (core_v0_9_1.yaml and core_v1_0.yaml) conforming to the new messages schema.

Migration strategy

  1. Standardizing on messages: Single-string promptText queries are converted into structured messages arrays with role-based conversation turns (user, assistant, tool, system).
  2. Mandatory component catalogs: Every sample specifies an explicit relative catalog path (e.g. specification/{version}/catalogs/basic/catalog.json).
  3. Modular dataset separation: Data points are partitioned into logical, manageable YAML files under eval/datasets/ that are dynamically discovered by the dataset loader.
  4. In-memory runtime fallback (loader safety): To prevent breakage if a developer branch or scratch file still contains legacy fields, the dataset loader in eval/a2ui_eval/dataset.py includes a runtime normalizer that converts promptText to messages and maps legacy field aliases on the fly.

Code changes roadmap

File Path Nature of Change Purpose
eval/datasets/dataset_schema.json Update Require catalog, use messages for chat turns, and include dataset, system_prompt, protocol_role, and generation_rules fields.
eval/datasets/core_v0_9_1.yaml Create Migrated v0.9.1 data points using the messages format and explicit catalog.
eval/datasets/core_v1_0.yaml Create Migrated v1.0 data points using the messages format and explicit catalog.
eval/a2ui_eval/dataset.py Update Add dataset discovery, messages array conversion to Inspect AI ChatMessage objects, required catalog enforcement, and validation CLI.
eval/a2ui_eval/strategies/format.py Update Implement domain system prompt merging and messages chat history prepending.
eval/a2ui_eval/scorers.py Update Use explicit catalog path in a2ui_scorer.
eval/tasks.py Update Point to migrated datasets and accept dataset and datasets filtering parameters.
eval/main.py Update Add --dataset and --datasets CLI flags.
eval/bin/report_evals.py Update Add per-dataset grouping in console summaries and markdown reports.
eval/bin/run_ci_evals.py Update Pass dataset filtering arguments to main.py.
eval/tests/test_dataset.py Update Add unit tests for dataset loading, messages chat turn parsing, required catalog check, and schema validation.

Proposals Spec Driven Development (`docs/proposals/spec_driven_development.md`)

**Scaling A2UI codebases with spec-driven development**

**Background**

The A2UI team has adopted recent development practices of using agents to write the majority of our code, using designs, specifications, prompts and other codebases as inputs to guide it. In this paradigm, the job of human developers is primarily to write and maintain the documentation that agents use to write code. This spec-driven development (SDD) approach is being adopted across the software industry in various ways. Members of the A2UI team have tried many different approaches, including generating detailed design docs which are then reviewed by humans for implementation, maintaining markdown guides which can be used to implement multiple variants of a codebase (e.g. renderer_guide.md for web renderers) or creating detailed Github issues that are used by agents to implement features.

The A2UI team aims to write and maintain several codebases across multiple languages with a small team. We have an opportunity to do this more efficiently by formalizing our use of spec-driven development, and building shared tooling to help agents follow it smoothly. We can reuse existing approaches to spec-driven development, but adapt them to work with our specific circumstances where we own a family of codebases which implement the same functionality across different languages.

**Goals**

Adopt spec-driven development with a standardized approach across the A2UI team in service of the following goals:

  • Encourage a culture of where we spend more effort creating and reviewing specifications.
  • Allow the A2UI team to more quickly implement features across SDK implementations in multiple languages.
  • Allow contributors outside of the core A2UI team to more efficiently write and maintain their own A2UI SDKs, for example the Jetpack Composer and Swift UI renderers.
  • Allow the A2UI team to collaborate more efficiently by standardizing the way that we record SDK and feature specifications.
  • Increase the productivity of agents working in our codebase on any task by giving them better access to relevant design documentation.
  • Make it easier for humans and agents to understand the discrepancies between different SDK codebases by emphasizing better definition of features and a clear separation between required features and optional (a.k.a. new or experimental) features.
  • Avoid disrupting existing workflows used by team members and ensure they still have freedom to experiment with novel techniques.

**Overview**

This document outlines the Spec-Driven Development (SDD) methodology for the A2UI repository. The goal of SDD is to streamline the implementation of the A2UI protocol and its features across multiple programming languages and UI frameworks by establishing clear, language-agnostic specifications (called Blueprints) and leveraging AI coding agents to scale development.

mermaid
graph TD
    FB[Feature Blueprint] -->|Merges into| MB[Module Blueprint]
    FB -->|Guides implementation of| CB[Codebase Blueprint]
    MB -->|Specifies architecture of| CB
    CB -->|Documents| C[Codebase Implementation]

Under this model:

  1. Module Blueprints define the high-level architecture, core interfaces, and behavior of repository modules (e.g., a2ui_core, a2ui_inference, a2ui_react, etc.).
  2. Feature Blueprints describe specific feature additions or behavioral changes to a module.
  3. Codebase Blueprints reside within each platform's implementation folder, tracking implemented features and documenting local design decisions or deviations.

Formal blueprints are primarily used for major features, e.g. the addition of a new public API, behavior, protocol version or architectural change. By having these specifications to be checked into the codebase, we create a natural opportunity for review of feature designs before they are implemented. We also ensure that when we add a feature to one SDK codebase, we can easily port it to other languages.

**Smaller features**

Smaller features can still be implemented ad-hoc with no formal blueprint. These small feature typically:

  • Make changes to functionality that is not explicitly documented in the module blueprint, or
  • Address local bugs, refactorings, or performance optimizations that do not affect the public API or cross-language protocol compliance, or
  • Add codebase-specific utility functions or internal helpers that do not impact compatibility or consistency with other SDKs.

Specification-driven development is still useful for these features, but the specifications don’t always need to be checked into the file system. They could be checked in as blueprints, or written in Github issues, or exist as temporary files on a developer’s file system.

**Document types**

**Feature blueprint**

Feature blueprints describe a diff in functionality that can be implemented in a codebase.

**Required features**

A Required Feature Blueprint describes a feature that has its design merged into all relevant module blueprints. It is expected to be eventually implemented in all codebases for the module. Required feature blueprints are added (or promoted from optional status) before they are implemented in all codebases and there are no guaruntee that the feature is implemented in all codebases within any particular timeframe. It is the job of codebase owners to keep their feature sets as up to date as possible. The codebase blueprints are the source of truth for which features are implemented in each codebase.

  • Agents implementing new codebases for a particular module never need to read required feature blueprints. They can completely implement the module and all its required features using only the module blueprint, because it has all the necessary details from existing required feature blueprints.
  • Agents implementing new required features in existing codebases primarily use the required feature blueprint to design and implement changes.

**Optional features**

An Optional Feature Blueprint describes a feature that is not baked into the module blueprint and is not expected to be implemented in all codebases. It allows platforms to support experimental or framework-specific features without forcing compliance across all SDKs.

  • Decoupled Lifecycle: Optional feature blueprints exist as standalone specification files in blueprints/features/ and are not merged into the base Module Blueprint.
  • Ad-hoc Implementation: Each codebase can decide independently whether to support an optional feature based on platform capabilities and user needs.
  • Discovery: Codebases that implement an optional feature must list it in their codebase.blueprint.md under implemented_features to let clients and agents know it is supported.

**Feature dependencies**

A feature blueprint can optionally specify a list of dependencies in its frontmatter. This list includes other feature blueprints that this feature directly depends on. The purpose of this field is to provide a pragmatic way for coding agents to identify other recent features that might be missing from a codebase and need to be implemented first, rather than being an exhaustive list of all architectural dependencies.

**my_feature.blueprint.md structure (Example)**

Every feature blueprint must follow a standardized Markdown structure with YAML frontmatter. The feature is named with a date prefix, e.g. YYYY_MM_DD_feature_name.blueprint.md, so that if there are many feature blueprints, it's easy to sort them by date and find the most recent and relevant ones. The date in the filename and frontmatter must match, and it is the date that the feature blueprint was initially written.

(Note: This date is used solely for rough chronological sorting of features. It does not indicate any dependency structureβ€”a feature does not necessarily depend on previously created features, nor does it imply it does not depend on features proposed later.)

Below is an example feature blueprint for a dynamic theming feature, written in a language-agnostic way:

text
/* Detailed source-code truncated for AI context efficiency. */

**Module blueprint**

A Module Blueprint describes an entire architectural module in a language-agnostic way. It serves as the primary source of truth for building a new codebase from scratch or verifying the correctness of an existing one.

All required features that affect the module are merged into the module blueprint when the feature is added or promoted, so that new codebases can be implemented using only the module blueprint and without consulting specific feature blueprints. Module blueprints act as a coherent, compacted description of the module, ensuring that the overall module specification does not have unbounded growth over time as features are added. As features are layered on top of each other, it is expected that the module blueprint will find concise ways to describe the overall system which are smaller than a simple concatenation of the feature blueprints.

**a2ui_core.blueprint.md Structure (Example)**

Every module blueprint must follow a standardized Markdown structure with YAML frontmatter. Below is an example module blueprint for the core state layer (a2ui_core):

md
---
name: a2ui_core
included_features: 
  - bidirectional_rpc
  - dynamic_binding
---

# **Core State Layer (a2ui_core) Module Blueprint**

## **Architecture Overview**

The core state layer is the framework-agnostic engine of A2UI. It is responsible for parsing inbound JSON streams from the agent, maintaining the active UI surface models, resolving JSON Pointer data bindings, and dispatching client actions back to the transport layer.

## **Core Interfaces**

- **`MessageProcessor`**: The central controller that ingests `A2uiMessage` envelopes and directs updates.
  ...

## **Conformance Test Plan**

Every implementation of `a2ui_core` must pass the core conformance test suite:

- **JSON Parsing Suite**: Validates envelope compliance against `server_to_client.json` and throws `A2uiValidationError` on schema violations.
- **Data Model Binding Suite**: Verifies absolute and relative pointer resolution, type coercion rules, and reactive signal emission.
- **Path to Mock Data**: `specification/v1_0/test/conformance/a2ui_core/`

**Codebase blueprint**

A Codebase is a concrete, language-specific or framework-specific implementation of a module. Examples of codebases in this repository include typescript/core/web_core (TypeScript implementation of a2ui_core), typescript/renderers/react (React implementation of a2ui_react), and kotlin/agent_sdk_legacy (Kotlin implementation of a2ui_inference and a2ui_core).

Every codebase must contain a codebase.blueprint.md file in its root directory. This file maps the concrete implementation back to the language-agnostic module blueprint, tracking its feature support and documenting local engineering decisions. The codebase blueprint should be updated at the same time as the code to keep it consistent with the codebase.

**codebase.blueprint.md Structure (Example)**

Below is an example codebase blueprint for a React renderer codebase (a2ui_react):

text
---
associated_module: a2ui_react
implemented_features: 
  - dynamic_theming
  - call_function_rpc
---
# **React Renderer Codebase Blueprint**
## **Architecture & Styling**
This codebase implements the `a2ui_react` module blueprint using React 19 and standard functional components.
* **Reactivity**: We map the core `a2ui_core` state signals to React state via a custom `useSignal` hook to trigger declarative re-renders.
* **Context Propagation**: We use React Context (`ComponentContext.Provider`) to propagate the data binding scopes recursively down the widget tree.
## **Technical Decisions & Overrides**
* **Dynamic Binders**: Unlike static languages, we utilize TypeScript runtime reflection to implement **Generic Binders**. This automatically resolves `DynamicString` properties into static props at the adapter layer, avoiding manual boilerplate for custom components.
* **Event System**: To handle React's synthetic event loop, we throttle input change actions (e.g., `TextField` keystrokes) in the adapter before committing them to the local `DataModel` to optimize performance.

**Developer journeys**

This section explains what steps will be taken by developers and agents to perform common tasks using spec-driven development.

**Specify a new required feature**

  1. Create required feature blueprint (significant human input required)
  2. Update module blueprint based on the feature blueprint, to ensure the module blueprint fully specifies the feature and how to implement it. Add the feature name to the module’s β€œincluded features”. (coding agent)
  3. Send PR for review. You can also include an implementation in a codebase in the same PR if you want - see β€œImplement an optional or required feature in a codebase”.

**Specify a new optional feature**

  1. Create optional feature blueprint (significant human input required)
  2. Send PR for review. You can also include an implementation in a codebase in the same PR if you want - see β€œImplement an optional or required feature in a codebase”.

**Promote an optional feature to be required**

  1. Update module blueprint based on the feature blueprint, and add it to the module’s β€œincluded_features”. (coding agent)
  2. Send PR for review.

**Implement an optional or required feature in a codebase**

  1. Verify that the codebase does not already contain the feature
  2. Create a temporary design describing in detail how the feature should be implemented in the specific codebase, taking into account the feature blueprint, the codebase blueprint, and the actual codebase code. This file should not be checked in. (human input required)
  3. Use the temporary design to implement the feature
  4. Update the codebase blueprint to add the feature to the "implemented_features" list and include any codebase-specific decisions that were made as part of the feature implementation.
  5. Send PR for review.

**Implement all the features necessary to bring a codebase β€œup to date”**

  1. Read the codebase blueprint and module blueprint and identify all the required features in the module that are not in the codebase.
  2. Implement each feature in chronological order, based on their blueprints, following the steps to implement a feature above.
  3. Consult the module blueprint and verify that the codebase now matches it, making minor changes to the codebase as necessary to make it as consistent as possible to the module blueprint.
  4. Update the codebase blueprint to add the feature to the "implemented_features" list.
  5. Send PR for review, either one PR per feature, or one PR for the entire update.

**Resolve inconsistencies between a module blueprint and all of its associated codebases**

  1. Read the relevant module blueprint
  2. Search for all codebases that associated with the module
  3. Analyse every codebase associated with the module, identifying:
    1. Required features that are missing from each codebase
    2. Discrepancies between the blueprint and the actual implementation e.g. API names or structures which are inconsistent
    3. Discrepancies between the codebases, for which the module blueprint provides no guidance.
  4. Report the above and propose actions to take to reduce the inconsistencies including:
    1. Adding additional detail to the blueprints to reduce ambiguity
    2. Update the module blueprint to explicitly mark a detail as being a codebase-level decision
    3. Updating codebases to match the module blueprints
    4. Update the codebase blueprint to document a reason that it has intentionally deviated from the module blueprint for a language-specific reason.
  5. Implement some of the proposed actions, based on human discretion (significant human input required)
  6. Send PR for review.

**Implement a new codebase**

  1. Create a temporary design describing in detail how the codebase should be implemented based on the module blueprint. (significant human input required)
  2. Implement the module based on the temporary design
  3. Create a new codebase blueprint, summarizing the design of details that are not specified in the module blueprint.
  4. Split up the changes into manageable chunks to review over several PRs.

**Clean up feature blueprints**

Feature blueprints undergo a clean, Git-centric lifecycle to prevent the blueprints directory from becoming cluttered with obsolete specifications.

  1. Required Features: Once a required feature has been fully implemented in all active codebases and its requirements have been integrated into the base Module Blueprint, the feature blueprint file is moved to the archived/ folder.
  2. Optional Features: Optional feature blueprints remain in blueprints/features/ as long as they are actively supported. If they are promoted to required, they are integrated into the Module Blueprint and deleted. If they are deprecated or abandoned, they are moved to the archived/ folder.

**Implementation**

**Folder structure**

To keep specifications organized and isolated by default, all SDD blueprints, validation tools, and specialized skills reside in the top-level /blueprints/ directory:

text
/
β”œβ”€β”€ blueprints/
β”‚   β”œβ”€β”€ README.md                           # Directive instructing agents to ignore folder unless explicitly requested
β”‚   β”œβ”€β”€ validate_blueprints.py              # Blueprint frontmatter and structure validator
β”‚   β”œβ”€β”€ link_skills.sh                      # Helper script to symlink blueprint skills into .agents/skills/
β”‚   β”œβ”€β”€ modules/                            # Language-agnostic Module Blueprints
β”‚   β”‚   β”œβ”€β”€ a2ui_core.blueprint.md
β”‚   β”‚   └── a2ui_framework_adapter.blueprint.md
β”‚   β”œβ”€β”€ features/                           # Unmerged / Optional Feature Blueprints (no date prefix)
β”‚   β”‚   β”œβ”€β”€ dynamic_theming.blueprint.md
β”‚   β”‚   └── archived/                       # Merged / Required Feature Blueprints
β”‚   β”‚       └── bidirectional_rpc.blueprint.md
β”‚   β”œβ”€β”€ codebases/                          # Codebase Blueprints tracking module compliance by commit hash
β”‚   β”‚   β”œβ”€β”€ typescript/
β”‚   β”‚   β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”‚   β”‚   └── web_core/
β”‚   β”‚   β”‚   β”‚       └── codebase.blueprint.md
β”‚   β”‚   β”‚   └── renderers/
β”‚   β”‚   β”‚       └── react/
β”‚   β”‚   β”‚           └── codebase.blueprint.md
β”‚   β”‚   └── python/
β”‚   β”‚       └── a2ui_core/
β”‚   β”‚           └── codebase.blueprint.md
β”‚   └── skills/                             # SDD Blueprint Skills (isolated from default agent use)
β”‚       β”œβ”€β”€ a2ui-blueprint-maintenance/
β”‚       β”œβ”€β”€ a2ui-blueprint-navigator/
β”‚       β”œβ”€β”€ a2ui-create-feature-blueprint/
β”‚       └── a2ui-implement-feature-from-blueprint/

**Skills**

To support automated execution of spec-driven tasks without cluttering default agent contexts, specialized SDD skills are maintained in blueprints/skills/. Developers can optionally symlink them into .agents/skills/ using blueprints/link_skills.sh:

bash
# Enable SDD skills locally
./blueprints/link_skills.sh

# Remove local SDD symlinks when done
rm -f .agents/skills/a2ui-blueprint-navigator \
      .agents/skills/a2ui-create-feature-blueprint \
      .agents/skills/a2ui-implement-feature-from-blueprint \
      .agents/skills/a2ui-blueprint-maintenance
  • a2ui-blueprint-navigator: A read-only analytical guide. It discovers blueprints and inspects codebase compliance against module blueprint git commit hashes (module_blueprint_commit).
  • a2ui-create-feature-blueprint: Provides instructions on how to create a feature blueprint inside blueprints/features/ without date prefixes.
  • a2ui-implement-feature-from-blueprint: Provides instructions on implementing feature specs and updating codebase compliance commit hashes.
  • a2ui-blueprint-maintenance: Manages merging feature specs into Module Blueprints and archiving feature blueprints to blueprints/features/archived/.

**Blueprint validation**

We implement a blueprint validator script (blueprints/validate_blueprints.py) that verifies all blueprints conform to required schemas and naming rules. It runs on CI (.github/workflows/validate_blueprints.yml) to block submission of invalid blueprints.

The validation script checks:

  • Frontmatter compliance: Verify all mandatory YAML fields are present and correctly typed.
  • Entity naming rules: Ensure feature names and module names use snake_case and match their filenames (feature_name.blueprint.md).
  • Commit hash & reference integrity: Validate that codebase_path, associated_module, optional module_blueprint_commit, and implemented_features in codebase blueprints point to valid targets.

**Creation of initial blueprints**

  • Module blueprints: We will migrate our existing guides and documentation in the blueprint formats described above. E.g. renderer_guide.md will become the module blueprints for core and framework adapter layers.
  • Codebase blueprints: We will have Gemini generate these by analysing the codebases.

Public Roadmap (`docs/public/roadmap.md`)

Roadmap

This roadmap outlines the current state and future plans for the A2UI project. The project is under active development, and priorities may shift based on community feedback and emerging use cases.

Current Status

Protocol

Version Status Notes
v0.8 πŸ†— Prior Initial public release, minimal support
v0.9 πŸ†— Prior Feature complete, legacy support
v0.9.1 πŸ†— Current stable release
v1.0 πŸ“‹ Candidate release candidate

Renderers

Client libraries Status Platform Notes
Web Core Lib βœ… Stable Web Shared core lib for all web renderers
Web Components (Lit) βœ… Stable Web Framework-agnostic, works anywhere
Angular βœ… Stable Web Full Angular integration
Flutter (GenUI SDK) βœ… Stable Multi-platform Works on mobile, web, desktop
React βœ… Stable Web Official React renderer
Lynx βœ… Stable Mobile, Web, Desktop ReactLynx renderer for A2UI v0.9
SwiftUI πŸ“‹ Planned iOS/macOS Planned for Q2 2026
Jetpack Compose πŸ“‹ Planned Android Planned for Q2 2026
Vue πŸ’‘ Proposed Web Community interest
Svelte/Kit πŸ’‘ Proposed Web Community interest
ShadCN (React) πŸ’‘ Proposed Web Community interest

Transports

Transport Status Notes
A2A Protocol βœ… Complete Native A2A transport
AG-UI βœ… Complete Day-zero compatibility
REST API βœ… Complete Request/response over HTTP
WebSockets βœ… Complete Bidirectional WS
MCP (Model Context Protocol) βœ… Complete Context sharing

Agent frameworks

Integration Status Notes
Any agent with A2A support βœ… Complete Day-zero compatibility thanks to A2A protocol
Any agent with AG-UI support βœ… Complete Day-zero compatibility thanks to AG-UI protocol
AG2 βœ… Complete A2UIAgent
ADK 🚧 In Progress Still designing developer ergonomics, see samples
Genkit πŸ’‘ Proposed Community interest
LangGraph πŸ’‘ Proposed Community interest
CrewAI πŸ’‘ Proposed Community interest
Claude Agent SDK πŸ’‘ Proposed Community interest
OpenAI Agent SDK πŸ’‘ Proposed Community interest
Microsoft Agent Framework πŸ’‘ Proposed Community interest
AWS Strands Agent SDK πŸ’‘ Proposed Community interest

Recent Milestones

Q2 2025

Many research projects across multiple Google teams, including integration into internal products and agents.

Q4 2025 v0.8

  • v0.8.0 spec released
  • A2A extension (thanks Google A2A team! teased at a2asummit.ai)
  • Flutter renderer (thanks Flutter team!)
  • Angular renderer (thanks Angular team!)
  • Web components (Lit) renderer (thanks Opal team & friends!)
  • AG-UI / CopilotKit integration (thanks CopilotKit team!)
  • Github public release (Apache 2.0)

Q2 2026 v0.9

  • Release spec 0.9
  • Release web core and renderers to support spec 0.9
  • Launch official A2UI React renderer
  • Improve theming support for renderers
  • Launch A2UI Agents SDK (python)
  • Improve developer ergonomics

Upcoming Milestones

Q3 2026 v0.9 & v1.0

Jetpack Compose Renderer (Android):

  • Native Compose UI components
  • Material Design 3 support
  • Android platform integration

SwiftUI Renderer (iOS/macOS):

  • Native SwiftUI components
  • iOS design language support
  • macOS compatibility

Performance Optimizations

  • Renderer performance benchmarks
  • Lazy loading for large component trees
  • Virtual scrolling for lists
  • Component memoization strategies

Q4 2026 v1.0

Finalize v1.0 of the protocol with:

  • Stability guarantees
  • Migration path from v0.9
  • Comprehensive test suite
  • Certification program for renderers

Long-Term Vision

Full App UIs

Configurable, Customizable UIs - facilitated by A2UI (agent optional)

  • Developer tooling for A2UI at build time
  • Patterns for A2UI caching, as config layer for static UI
  • Designs and samples for full app composition

Multi-Agent Coordination

Enhanced support for multiple agents contributing to the same UI:

  • Recommended agent composition patterns
  • Conflict resolution strategies
  • Shared surface management

Accessibility Features

First-class accessibility support:

  • ARIA attribute generation
  • Screen reader optimization
  • Keyboard navigation standards
  • Contrast and color guidance

Advanced UI Patterns

Support for more complex UI interactions:

  • Drag and drop
  • Gestures and animations
  • 3D rendering
  • AR/VR interfaces (exploratory)

Ecosystem Growth

  • More framework integrations
  • Third-party component libraries
  • Agent marketplace integration
  • Enterprise features and support

How to Influence the Roadmap

We welcome community input on priorities:

  1. Submit feedback form: Use this anonymous form to share your requirements and feedback
  2. Vote on Issues: Give πŸ‘ to GitHub issues you care about
  3. Propose Features: Open a discussion on GitHub (search for existing discussions first)
  4. Submit PRs: Build the features you need (search for existing PRs first)
  5. Join Discussions: Share your use cases and requirements (search for existing discussions first)

Planned Release Cycle

  • Major versions (1.0, 2.0): Annual or when significant breaking changes are needed
  • Minor versions (1.1, 1.2): Quarterly with new features
  • Patch versions (1.1.1, 1.1.2): As needed for bug fixes

Versioning Policy

A2UI follows Semantic Versioning:

  • MAJOR: Incompatible protocol changes
  • MINOR: Backward-compatible functionality additions
  • PATCH: Backward-compatible bug fixes

Stay Updated

  • Watch the GitHub repository for updates
  • Star the repo to show your support
  • Follow releases to get notified of new versions

Last Updated: June 2026

Have questions about the roadmap? Start a discussion on GitHub.


Public Composer Composer Renderer Integration (`docs/public/composer/composer_renderer_integration.md`)

A2UI Composer Integration Manual

Background

The A2UI Composer has no knowledge or integration with any particular catalog or
renderer stack. In order to see the A2UI JSON rendered, it relies on a β€œRenderer
Application.” The A2UI Composer hosts the Renderer Application in an iframe and
communicates with it via postMessage. The iframe hosting the renderer
application has sandbox='allow-scripts allow-same-origin allow-forms', which
should not be a problem for simply rendering the A2UI JSON.

The Renderer Application is responsible for accepting A2UI JSON and using its
renderers to display the result.

The Bridge

To simplify the work of integrating with the A2UI Composer, a bridge has
been created. This is a small amount of Javascript code that the Renderer
Application incorporates, and coordinates all the communication between the A2UI
Composer and the Renderer Application.

To further simplify the integration, different framework-specific wrappers are
provided.

Examples

Check out the sample renderer apps:

They all serve the same version of the Basic catalog.

When running the hosted A2UI Composer,
you can see each of these renderer applications in action by going to the
Settings page and clicking the Renderer dropdown.

Using Angular

As an example, we'll walk through creating a Renderer application based on
Angular.

Add dependencies

First, add the core integration package to your project dependencies list:

text
yarn add a2ui-bridge @a2ui/web_core @a2ui/angular

Of course, if you’re using a different package manager, follow the appropriate
steps.

Create the wrapper component

Create a new component that looks like this:

ts
import {Component, inject} from '@angular/core';
import {SurfaceComponent} from '@a2ui/angular/v0_9';
import {A2uiSandboxConnection} from 'a2ui-bridge/angular';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [SurfaceComponent],
  template: `
    <main class="sandbox-shell">
      @if (sandbox.surfaceId()) {
        <a2ui-v09-surface [surfaceId]="sandbox.surfaceId()" />
      } @else {
        <p style="padding: 24px; color: #666; font-family: sans-serif; text-align: center;">
          Waiting for RENDER_A2UI payloads...
        </p>
      }
    </main>
  `,
})
export class AppComponent {
  protected sandbox = inject(A2uiSandboxConnection);
}

Note that contents of the @else can be modified as you see fit, but the rest
of the template should remain as-is.

Bootstrap your renderer application

Configure the standard Angular bootstrapping entrypoint file (src/main.ts),
passing your catalog classes dynamically to the sandbox provider mapping:

ts
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {provideA2uiSandbox} from 'a2ui-bridge/angular';
import {BasicCatalog} from '@a2ui/angular/v0_9';

bootstrapApplication(AppComponent, {
  providers: [
    provideA2uiSandbox([BasicCatalog]), // Injects and exposes dynamic catalogs
  ],
}).catch((err) => console.error('A2UI Sandbox Bootstrap Failed:', err));

Make sure to replace BasicCatalog with your catalog.

NOTE on change detection compatibility: The provideA2uiSandbox helper is
100% compatible with both standard Zone-based change detection (using zone.js)
and Zoneless change detection (provideZonelessChangeDetection())
out-of-the-box.


Public Concepts Actions (`docs/public/concepts/actions.md`)

Handling User Actions

This guide explains how A2UI handles user interactions. Components use the action property to trigger either local Functions (executed on the renderer) or Events (dispatched to the agent). In addition, Data Model Synchronization ensures the agent always has access to the full UI state, enabling seamless multi-modal interactions like voice commands. This design enables highly responsive interfaces while maintaining a secure, restricted environment.

Action Architecture

Actions allow UI components to trigger behavior defined in the Action schema in common_types.json. Actions can trigger:

  1. Events: Dispatched to the Agent for processing (executed on Agent, e.g., clicking "Submit").
  2. Functions: Executed entirely on the renderer using FunctionCall (executed on Renderer, e.g., opening a URL).

1. Functions (Local)

Functions execute immediate behavior on the renderer without a network round-trip. The agent is not informed of local function calls. They use the functionCall keyword.

json
{
  "id": "help-btn",
  "component": "Button",
  "child": "help-text",
  "action": {
    "functionCall": {
      "call": "openUrl",
      "args": {"url": "https://a2ui.org/help"}
    }
  }
}

Common uses for Functions include:

  • Navigation: Opening a URL or switching tabs.
  • Validation: Checking inputs before submission (see Checks below).

2. Events (Agent)

Events send data to the agent for processing. They use the event keyword.

Components like Button expose an action property. Here is how an Event is wired:

json
{
  "id": "submit-btn",
  "component": "Button",
  "child": "btn-text",
  "action": {
    "event": {
      "name": "submit_reservation",
      "context": {
        "time": {"path": "/reservationTime"},
        "size": {"path": "/partySize"}
      }
    }
  }
}
  • name: A stable identifier for the agent to switch on.
  • context: A map of key-value pairs. Values can be literal or use a path to pull from the current state of the data model.

NOTE: Context vs. Data Model: While the Data Model represents the entire state tree of a surface, the context in an action is effectively a hand-picked "view" or subset of that state. This simplifies the Agent's job by providing exactly the values needed for a specific event, without requiring the Agent to navigate a potentially large and complex data model.

Basic Catalog Function Validation (Checks)

The basic catalog defines a limited set of checks that can be performed on the renderer. Interactive components can define a list of checks (using the Checkable schema in common_types.json). For a Button, if any check fails, the button is automatically disabled on the renderer.

  • UX Focus: Validation checks are designed to manage UI State (User Experience) by preventing invalid interactions before they happen. They are not a replacement for Data Integrity checks, which must still be performed on the agent.

This allows the UI to enforce requirements (like a non-empty field) before the user even tries to submit.

json
{
  "id": "submit-button",
  "component": "Button",
  "child": "submit-text",
  "checks": [
    {
      "condition": {
        "call": "required",
        "args": {"value": {"path": "/partySize"}}
      },
      "message": "Party size is required"
    }
  ],
  "action": {"event": {"name": "submit_booking"}}
}

Local State Updates & The "Write" Contract

Before an Event is even dispatched, the renderer is already managing the state of the UI locally. A2UI defines a Read/Write Contract for all input components (like TextField, CheckBox, or Slider).

  1. Read (Model β†’ View): When a component renders, it pulls its value from the bound path in the Data Model.
  2. Write (View β†’ Model): As soon as a user interacts (e.g., typing a character or clicking a checkbox), the renderer immediately writes the new value into the local Data Model.

This means the local model is always the source of truth for the UI's current state. This "View-to-Model" synchronization happens purely on the renderer. The data model is only sent to the agent when an event occurs (like a Button click).

IMPORTANT: Synchronous Updates: Local model updates are synchronous. This guarantees that the Data Model is fully updated before any Event resolves its context paths or a DataModelSync payload is packaged. There are no race conditions between typing and clicking; the "Write" is always committed first.

This local-first approach offers a significant Performance Benefit. Because synchronization is immediate and local, developers don't need to implement network debouncing or worry about latency jitters as a user types in a TextField. The network is completely protected from "UI noise" (like individual keystrokes) until the user is ready to dispatch a formal Event.

The Form Submission Pattern

This separation allows for a robust form submission pattern:

  • Binding: A TextField is bound to /reservationTime.
  • Interaction: The user types "7:00 PM". The local model at /reservationTime is updated instantly.
  • Submission: The user clicks a "Book" button. The button's Event resolves the path: "/reservationTime" from the local model and sends the current value to the agent.

User Interaction Flow

When a user interacts with a component (e.g., clicks a button):

  1. Resolve: The renderer resolves all path references in the context against the local Data Model.
  2. Construct: The renderer builds an action payload conforming to client_to_server.json.
  3. Dispatch: The payload is sent via the chosen transport (e.g., A2A, WebSockets).

Example: The Action Payload (v0.9)

If a user clicks the button above with a data model containing {"reservationTime": "7:00 PM", "partySize": 4}, the renderer sends a message using the action key:

json
{
  "version": "v0.9",
  "action": {
    "name": "submit_reservation",
    "surfaceId": "booking-surface",
    "sourceComponentId": "submit-btn",
    "timestamp": "2026-02-25T10:40:00Z",
    "context": {
      "time": "7:00 PM",
      "size": 4
    }
  }
}

IMPORTANT: A Note on Versioning (v0.8 vs v0.9): In v0.8, the top-level payload key was userAction (e.g., {"userAction": {...}}). v0.9 transitioned to the simpler action key shown above. Standard protocol parsers expect the key corresponding to the version declared in the payload.

Agent Processing

The Agent (or an Orchestrator) receives this event and acts on it. In an agentic system, the agent usually converts the event into a hidden user query for the LLM.

Example Agent Processing (Python):

python
if action_name == "submit_reservation":
    time = context.get("time")
    size = context.get("size")
    # Feed this to the LLM
    query = f"User submitted a reservation for {size} people at {time}."
    response = await llm.generate(query)

Renderer-to-Agent Error Reporting

In addition to Events triggered by the user, the renderer can report system-level errors back to the agent using the error payload defined in client_to_server.json.

Validation Failures

If the agent sends A2UI JSON that violates the catalog schema or protocol rules, the renderer sends a VALIDATION_FAILED error. This is a critical feedback loop for agentic systems:

json
{
  "version": "v0.9",
  "error": {
    "code": "VALIDATION_FAILED",
    "surfaceId": "booking-surface",
    "path": "/components/0/children",
    "message": "Expected array of strings, got null."
  }
}

The agent can catch this error, apologize (or self-correct internally), and re-send the corrected UI.

Data Model Sync (v0.9)

A2UI v0.9 introduced a powerful "stateless" synchronization feature. This allows the renderer to automatically include the entire data model of a surface in the metadata of every message it sends to the agent.

Enabling Sync

Synchronization is requested by the agent during surface initialization. By setting sendDataModel: true in the createSurface message, the agent instructs the renderer to start the sync loop.

json
{
  "version": "v0.9",
  "createSurface": {
    "surfaceId": "booking-surface",
    "catalogId": "https://a2ui.org/catalogs/v1/basic.json",
    "sendDataModel": true
  }
}

Sync on the Wire

When sync is enabled, the renderer does not send the data model as a separate message. Instead, it attaches it as metadata to the outgoing transport envelope (e.g., an A2A message).

In an A2A (Agent-to-Agent) binding, the data model is placed in an a2uiClientDataModel object within the envelope's metadata field.

Example A2A Envelope with Sync:

json
{
  "parts": [{"text": "Submit the reservation"}],
  "metadata": {
    "a2uiClientDataModel": {
      "version": "v0.9",
      "surfaces": {
        "booking-surface": {
          "reservationTime": "7:00 PM",
          "partySize": 4,
          "notes": "Window seat preferred"
        }
      }
    }
  }
}

Why use Data Model Sync?

  • Simpler Wiring: You don't need to manually map every input field into a button's context property. The agent can simply inspect the metadata to see the current state of all fields.
  • Stateless Agents: The agent doesn't need to maintain local state for every user session; it receives the full current context with every single interaction.
  • Verbal Shortcuts: Allows the user to trigger Events via voice or text (e.g., "okay submit") even without clicking a specific button. Since the agent receives the updated data model with the text message, it can process the request immediately.

Renderer Metadata & Capabilities

Before an agent can safely send a UI, the renderer must advertise which component catalogs it supports. This is handled via the a2uiClientCapabilities object.

Advertising Capabilities

Renderers include an a2uiClientCapabilities object in the metadata of their messages to the agent (e.g., in the metadata field of an A2A Message).

json
{
  "v0.9": {
    "supportedCatalogIds": [
      "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
      "https://my-company.com/catalogs/v1/custom.json"
    ],
    "inlineCatalogs": []
  }
}
  • supportedCatalogIds: An array of catalog URIs the renderer can render.
  • inlineCatalogs: (Optional) For development or specialized environments, allows sending the full catalog schema inline.

Without this handshake, an agent cannot be certain that the renderer can handle the specific components being sent.

Transport & Encoding

A2UI is transport-agnostic, but it is most commonly used over A2A (Agent-to-Agent) or WebSockets. Understanding how the payload is wrapped is crucial for implementation.

A2A Encoding

In the standard A2A binding, A2UI messages are encoded as an A2A DataPart. To identify it as an A2UI payload, the part must be wrapped with specific metadata:

  • mimeType: application/a2ui+json

The data field of the DataPart contains a list of A2UI messages. This allows multiple updates (e.g., createSurface followed by updateComponents) to be sent in a single network packet.

NOTE: A2A Versioning: The use of a list in the data field was introduced in A2A v1.0. Earlier versions of the A2A protocol expect the data field to contain a single JSON object.

json
{
  "kind": "data",
  "metadata": {
    "mimeType": "application/a2ui+json"
  },
  "data": [
    {
      "version": "v0.9",
      "action": { ... }
    }
  ]
}

Security Considerations

A2UI is designed with secure, sandboxed communication as a core principle. Because the protocol relies on passing user state and interaction triggers over the network, it enforces strict boundaries on data visibility and execution.

Sandboxed Execution

A core selling point of A2UI is security through restriction. By prohibiting arbitrary code execution (like injecting raw JavaScript) from the agent, A2UI ensures that agents can only trigger pre-registered behaviors. The functionCall mechanism acts as a safe, sandboxed way for the agent to interact with the renderer's environment without exposing the user to malicious scripts.

Data Model Isolation and Orchestrator Routing

When sendDataModel: true is enabled, the renderer includes the surface's entire data model in outgoing messages. Developers must understand the visibility of this data:

  • Point-to-Point Visibility: Only the backend receiving the transport envelope (the Agent that created the surface, or an intermediate Orchestrator) can read this payload.
  • The Orchestrator's Responsibility: In a multi-agent architecture, a central Orchestrator often routes user intents to specialized sub-agents. The Orchestrator must enforce data isolation. It is responsible for parsing the a2uiClientDataModel, identifying the surfaceId, and ensuring that the data model is only passed to the specific sub-agent that owns that surface. Data from one agent's surface must never leak to another agent.

Orchestration & Routing

In multi-agent systems, a central Orchestrator often manages interactions between a user and several specialized sub-agents. A key challenge is ensuring that action messages from the renderer are routed back to the specific sub-agent that generated the UI surface.

The Surface Ownership Pattern

To handle routing in a multi-agent architecture, an orchestrator must maintain a mapping of each surfaceId to its owning sub-agent. The official Python SDK provides the A2uiSubagentMap utility to manage this mapping safely.

For a complete working example, see the orchestrator agent executor sample.

1. Mapping Ownership from Server Events

When a sub-agent emits an A2UI createSurface or deleteSurface the orchestrator intercepts the message and records the ownership in the session state using update_from_server_event.

python
from a2ui.adk import A2uiSubagentMap, SurfaceIdAlreadyExistsError
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutorConfig
from google.adk.a2a.executor.config import ExecuteInterceptor
from google.adk.a2a.events import Event as A2AEvent
from google.adk.events.event import Event
from google.adk.sessions import SessionService, Session

async def after_event_save_surface_id(a2a_event: A2AEvent, event: Event, session_service: SessionService, session: Session):
    for a2a_part in a2a_event.status.message.parts:
        try:
            await A2uiSubagentMap.update_from_server_event(
                a2a_part,
                event.author,
                session_service,
                session
            )
        except SurfaceIdAlreadyExistsError as e:
            # Handle surface ID collision
            pass
    return a2a_event

config = A2aAgentExecutorConfig(
    execute_interceptors=[
        ExecuteInterceptor(after_event=after_event_save_surface_id)
    ]
)

2. Routing Client Events

When the client renderer sends an A2UI action or error message back, the orchestrator looks up the surfaceId in the session state using get_subagent_name_for_client_event and routes the request to the correct sub-agent.

python
from a2ui.adk import A2uiSubagentMap
from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.genai import types as genai_types
from google.adk.agents.remote_a2a_agent import convert_genai_part_to_a2a_part

async def route_client_event(callback_context: CallbackContext, llm_request: LlmRequest):
    a2a_part = convert_genai_part_to_a2a_part(llm_request.contents[-1].parts[-1]) # Assume response has a single a2a part

    target_agent = await A2uiSubagentMap.get_subagent_name_for_client_event(
        a2a_part,
        callback_context.state
    )

    if target_agent:
        # Programmatically trigger the planner's transfer_to_agent function
        return LlmResponse(
            content=genai_types.Content(
                parts=[
                    genai_types.Part(
                        function_call=genai_types.FunctionCall(
                            name="transfer_to_agent",
                            args={"agent_name": target_agent},
                        )
                    )
                ]
            )
        )

orchestrator_agent = LlmAgent(
    name="orchestrator",
    before_model_callback=route_client_event,
    # other configs ...
)

This pattern ensures that the bidirectional communication loop remains intact and stateful for each feature area.

Preventing Data Leakage via Metadata Stripping

In a multi-agent environment, the a2uiClientDataModel object can contain the state for multiple surfaces owned by different sub-agents. To prevent sensitive data leakage, an orchestrator must strip the data model metadata to include only the surfaces owned by the target sub-agent.

You can use the strip_unowned_surfaces_from_data_model method in an outbound interceptor to mutate the data model dictionary in place.

python
from a2ui.adk import A2uiSubagentMap
from a2a.client.middleware import ClientCallInterceptor, ClientCallContext
from a2a.types import AgentCard

class A2UIMetadataInterceptor(ClientCallInterceptor):
    async def intercept(self, request_payload: dict, agent_card: AgentCard, context: ClientCallContext):
        message = request_payload.get("params", {}).get("message")

        # Strip the data model to prevent data leakage
        if data_model := message.get("metadata", {}).get("a2uiClientDataModel"):
            await A2uiSubagentMap.strip_unowned_surfaces_from_data_model(
                agent_card.name,
                data_model,
                context.state,
            )

        return request_payload

By stripping the metadata, the orchestrator ensures that each sub-agent receives only the portion of the data model that it is authorized to see.

WARNING: Security Risk - State Scraping: If an orchestrator fails to strip the a2uiClientDataModel, a malicious or compromised sub-agent could read the state of other active surfaces. For example, a weather sub-agent could scrape sensitive data from a banking surface if the orchestrator leaks the entire multi-surface data model. Stripping is a mandatory security requirement for multi-agent systems.


Comprehensive Examples

1. Button Submit (Explicit Context)

This example shows a button that explicitly gathers the data it needs to send.

Component Definition:

json
{
  "id": "submit-button",
  "component": "Button",
  "child": "submit-text",
  "action": {
    "event": {
      "name": "submit_booking",
      "context": {
        "partySize": {"path": "/partySize"},
        "reservationTime": {"path": "/reservationTime"}
      }
    }
  }
}

Resulting Action Payload:
The agent receives an action object with the partySize and reservationTime directly in the context field.

2. Verbal Submit (Data Model Sync)

In this scenario, the user doesn't click a button. Instead, they say "Okay, submit the form."

Initialization:
The agent created the surface with sendDataModel: true:

json
{
  "version": "v0.9",
  "createSurface": {
    "surfaceId": "booking-surface",
    "catalogId": "...",
    "sendDataModel": true
  }
}

Renderer Transmission:
The renderer sends an A2A message containing the user's text and the data model in the metadata:

json
{
  "parts": [{"text": "Okay, submit the form"}],
  "metadata": {
    "a2uiClientDataModel": {
      "version": "v0.9",
      "surfaces": {
        "booking-surface": {
          "partySize": 4,
          "reservationTime": "7:00 PM"
        }
      }
    }
  }
}

Agent Processing:
The agent sees the user's intent ("submit") and looks at the metadata to find the current values of partySize and reservationTime, allowing it to complete the task without further clarification.


Public Concepts Catalogs (`docs/public/concepts/catalogs.md`)

A2UI Catalogs

Overview

This guide defines the A2UI Catalog architecture and provides a roadmap for implementation. It explains the structure of catalog schemas, outlines strategies for using the pre-built "Basic Catalog” versus defining your own application-specific catalog, and details the technical protocols for catalog negotiation, versioning, and runtime validation.

Catalog Schema

A catalog schema is a JSON Schema file outlining the components, functions, and themes that agents can use to define A2UI surfaces using server-driven UI. All A2UI JSON sent from the agent is validated against the chosen catalog.

Catalog JSON Schema is below

json
{
  "Catalog": {
    "type": "object",
    "description": "A collection of component and function definitions.",
    "properties": {
      "catalogId": {
        "type": "string",
        "description": "Unique identifier for this catalog."
      },
      "components": {
        "type": "object",
        "description": "Definitions for UI components supported by this catalog.",
        "additionalProperties": {
          "$ref": "https://json-schema.org/draft/2020-12/schema"
        }
      },
      "functions": {
        "type": "array",
        "description": "Definitions for functions supported by this catalog.",
        "items": {
          "$ref": "#/$defs/FunctionDefinition"
        }
      },
      "theme": {
        "title": "A2UI Theme",
        "description": "A schema that defines a catalog of A2UI theme properties.",
        "type": "object",
        "additionalProperties": {
          "$ref": "https://json-schema.org/draft/2020-12/schema"
        }
      }
    },
    "required": ["catalogId"],
    "additionalProperties": false
  }
}

Catalog Strategy

Every A2UI surface is driven by a Catalog. A catalog is simply a JSON Schema file that tells the agent which components, functions, and themes are available for it to use.

Whether you are building a simple prototype or a complex production application, the requirement is the same: you must provide a catalog definition that the agent uses to express UI.

The Basic Catalog

To help developers get started quickly, the A2UI team maintains the Basic Catalog.

This is a pre-defined catalog file that contains a basic set of general-purpose components (Buttons, Inputs, Cards) and functions. It is not a special "type" of catalog; it is simply a version of a catalog that we have already written and have open source renderers for.

The basic catalog allows you to bootstrap an application or validate A2UI concepts without needing to write your own schema from scratch. It is intentionally sparse to remain easily implementable by different renderers.

Since A2UI is designed for LLMs to generate the UI at either design time or runtime, we do not think portability requires a standardized catalog across multiple clients; the LLM can interpret the catalog for each individual frontend.

See the A2UI v0.9 basic catalog

Defining Your Own Catalog

While the Basic Catalog is useful for starting out, most production applications will define their own catalog to reflect their specific design system.

By defining your own catalog, you restrict the agent to using exactly the components and visual language that exist in your application, rather than generic inputs or buttons. This catalog can be built entirely from scratch, or it can import definitions from the Basic Catalog to save time (e.g., using the Basic text definitions while defining your own unique Card component).

For simplicity we recommend building catalogs that directly reflect a client's design system rather than trying to map the Basic Catalog to it through an adapter. Since A2UI is designed for GenUI, we expect the LLM can interpret different catalogs for different clients.

See an example Rizzcharts catalog

Recommendations

Usecase Recommendation Effort
Adding A2UI to a mature frontend Define a catalog that mirrors your existing design system. Medium
Adding A2UI to a new/greenfield app Start with Basic Catalog, then evolve into your own catalog as the app evolves Low (assuming renderer exists)

Building a Catalog

A catalog is a JSON Schema file that conforms to the Catalog schema that defines the components, themes and functions an agent can use when building a surface.

Example: A Minimal Catalog

Here is a simple catalog defining a single component.

json
{
  "$id": "https://github.com/.../hello_world/v1/catalog.json",
  "catalogId": "https://github.com/.../hello_world/v1/catalog.json",
  "components": {
    "HelloWorldBanner": {
      "type": "object",
      "description": "A simple banner greeting.",
      "properties": {
        "message": {
          "type": "string",
          "description": "The banner text."
        },
        "backgroundColor": {
          "type": "string",
          "default": "#f0f0f0"
        }
      },
      "required": ["message"]
    }
  }
}

When the agent uses that catalog, it generates a payload strictly conforming to that structure:

json
[
  {
    "version": "v0.9",
    "createSurface": {
      "surfaceId": "hello-world-surface",
      "catalogId": "https://github.com/.../hello_world/v1/catalog.json"
    }
  },
  {
    "version": "v0.9",
    "updateComponents": {
      "surfaceId": "hello-world-surface",
      "components": [
        {
          "id": "root",
          "component": "HelloWorldBanner",
          "message": "Hello, world! Welcome to your first catalog.",
          "backgroundColor": "#4CAF50"
        }
      ]
    }
  }
]

Catalog Linking

A catalog's only external references are to the protocol's common_types.json and to peer catalogs: other catalogs that the agent or renderer already holds locally, because they are registered with it or supplied together with the referencing catalog. Peer catalogs are never fetched over the network.

A reference into a peer has the form #/components/<Name> (a whole component, usually inherited with allOf), #/components/<Name>/properties/ (one property's schema) or #/functions/<Name>/.... `` is the peer's catalogId or $id, or a path or file name that ends one of them:

json
{
  "catalogId": "https://example.com/catalogs/branded/v1/catalog.json",
  "components": {
    "BrandedText": {
      "type": "object",
      "allOf": [
        {"$ref": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json#/components/Text"}
      ],
      "properties": {
        "component": {"const": "BrandedText"},
        "badge": {"$ref": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json#/components/Text/properties/text"}
      },
      "required": ["component", "badge"]
    }
  }
}

The SDKs resolve such references at runtime against the other catalogs you register or pass in (linkCatalogs / link_catalogs, withPeerCatalogs / with_peer_catalogs; the agent SDKs and MessageProcessor link the catalogs they are given). Peers are matched in this order, and the first match wins:

  1. common_types.json always means the protocol's common types, and a document named exactly catalog.json means the referencing catalog itself.
  2. A peer whose catalogId or $id equals the document or ends with it as a path suffix.
  3. A path ending in /catalog.json that no peer matched in step 2 means the referencing catalog itself.
  4. A peer whose $id or catalogId has the same file name as the document. Only catalog.json itself is generic, so mcp_catalog.json matches a peer by file name.

A reference that matches no peer is a catalog error. Prefer full $ids, which always match exactly one peer. See Cross-Catalog References in the v1.0 specification for the normative rules.

You can also bundle external references into a freestanding catalog at build time. This catalog registration process, also called "Linking", is consolidated under a single, multi-platform Node.js script (register-catalogs.js).

This linking script is natively wrapped inside Xcode Build Phases (for iOS/macOS client builds) and Gradle tasks (for Android client builds) to compile, aggregate, and link static and dynamic schemas seamlessly during your application's build phase. Renderers that don't resolve peer catalogs at runtime need bundled catalogs.

Composition & Imports

You do not have to define everything from scratch. You can define a catalog which uses existing components from the basic or other catalogs, and one that reusing the existing rendering logic.

Example: Extending the Basic Catalog

This catalog imports all elements from the Basic Catalog and adds a new SuggestionChips component.

json
{
  "$id": "https://github.com/.../hello_world_with_all_basic/v1/catalog.json",
  "catalogId": "https://github.com/.../hello_world_with_all_basic/v1/catalog.json",
  "components": {
    "allOf": [
      {"$ref": "basic_catalog_definition.json#/components"},
      {
        "SuggestionChips": {
          "type": "object",
          "description": "A list of suggested prompts",
          "properties": {
            "suggestions": {
              "type": "array",
              "description": "The suggested prompts."
            }
          },
          "required": ["suggestions"]
        }
      }
    ]
  }
}

Make sure to link and resolve external references during compilation using your platform's Xcode Build Phase or Gradle task (running register-catalogs.js).

Example: Cherry-picking Components

This catalog imports only Text from the Basic Catalog to build a simple Popup surface.

json
{
  "$id": "https://github.com/.../hello_world_with_some_basic/v1/catalog.json",
  "catalogId": "https://github.com/.../hello_world_with_some_basic/v1/catalog.json",
  "components": {
    "allOf": [
      {"$ref": "catalogs/basic/catalog.json#/components/Text"},
      {
        "Popup": {
          "type": "object",
          "description": "A modal overlay that displays an icon and text.",
          "properties": {
            "text": {"$ref": "common_types.json#/$defs/ComponentId"}
          },
          "required": ["text"]
        }
      }
    ]
  }
}

Make sure to link and resolve external references during compilation using your platform's Xcode Build Phase or Gradle task (running register-catalogs.js).

Implementing Renderers

Client renderers implement the catalog by mapping the schema definition to actual code.

First, define the component API in TypeScript matching your catalog schema:

typescript
// api.ts
import {ComponentApi} from '@a2ui/web_core/v0_9';
import {z} from 'zod';

export const HelloWorldBannerApi = {
  name: 'HelloWorldBanner',
  schema: z.object({
    message: z.string(),
    backgroundColor: z.string().default('#f0f0f0'),
  }).strict(),
} satisfies ComponentApi;

Next, implement the component extending CatalogComponent:

typescript
// hello_world_banner.ts
import {CatalogComponent} from '@a2ui/angular/v0_9';
import {Component, computed} from '@angular/core';
import {HelloWorldBannerApi} from './api';

@Component({
  selector: 'hello-world-banner',
  template: `
    <div [style.background-color]="backgroundColor()">
      <h2>Hello World Banner</h2>
      <p>{{ message() }}</p>
    </div>
  `,
})
export class HelloWorldBanner extends CatalogComponent<typeof HelloWorldBannerApi> {
  protected readonly message = computed(() => this.props()['message']?.value() || '');
  protected readonly backgroundColor = computed(() => this.props()['backgroundColor']?.value() || '#f0f0f0');
}

Finally, register your custom components in an AngularCatalog:

typescript
// catalog.ts
import {AngularCatalog, BASIC_COMPONENTS, BASIC_FUNCTIONS} from '@a2ui/angular/v0_9';
import {HelloWorldBanner} from './hello_world_banner';
import {HelloWorldBannerApi} from './api';

const customBannerComponent = {
  ...HelloWorldBannerApi,
  component: HelloWorldBanner
};

export const MY_CATALOG = new AngularCatalog(
  'https://github.com/.../hello_world/v1/catalog.json',
  [...BASIC_COMPONENTS, customBannerComponent],
  BASIC_FUNCTIONS
);

You can see a working example of a client renderer in the Orchestrator demo.

[!NOTE]
The Orchestrator demo currently uses v0.8 APIs. For a v0.9 example of catalog registration, see the DemoCatalog in the Angular explorer.

Additionally, for client-side functions, the client determines the function's execution boundary (such as clientOnly status) at runtime by reading its configuration from the active catalog definition.

A2UI Catalog Negotiation

Because clients and agents can support multiple catalogs, they must agree on which catalog to use through a catalog negotiation handshake.

Step 1: Agent advertises its support catalogs (optional)

The agent may optionally advertise which catalogs it is capable of speaking (e.g., in the A2A Agent Card). This is informational; it helps the client know if the agent supports their specific features, but the client doesn’t have to use it.

Example of an A2A AgentCard advertising that the agent supports the basic and rizzcharts catalogs

json
{
  "name": "Ecommerce Dashboard Agent",
  "description": "This agent visualizes ecommerce data...",
  "capabilities": {
    "extensions": [
      {
        "uri": "https://a2ui.org/a2a-extension/a2ui/v0.8",
        "description": "Provides agent driven UI using the A2UI JSON format.",
        "params": {
          "supportedCatalogIds": [
            "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
            "https://github.com/.../rizzcharts_catalog_definition.json"
          ]
        }
      }
    ]
  }
}

Step 2: Client advertises its support catalogs (required)

The client sends a list of supportedCatalogIds to the Agent, ordered by preference, in the metadata of every message. This tells the agent exactly what the client is prepared to render right now.

Example of A2A message containing the supportedCatalogIds in metadata

json
{
  "parts": [
    {
      "text": "What is the current status of my flight?"
    }
  ],
  "metadata": {
    "a2uiClientCapabilities": {
      "supportedCatalogIds": [
        "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
        "https://github.com/.../rizzcharts_catalog_definition.json"
      ]
    }
  }
}

Step 3: Agent Selection

When the agent creates a new surface, it selects the best match from the client's supportedCatalogIds list. This choice is locked for the lifetime of that surface. If no compatible catalog is found, the agent will not send a UI.

Example A2UI Message from the agent defining the catalog_id used in a surface

json
{
  "createSurface": {
    "surfaceId": "salesDashboard",
    "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
  }
}

Catalog Naming & Versioning

A2UI component catalogs require versioning because catalog definitions are often built in at compile time, so any mismatch between what an agent generates and what a client can render can affect the UI.

CatalogId Naming Convention

The catalogId is a unique text identifier used for negotiation between the client and the agent.

  • Format: While the catalogId is technically a string, the A2UI convention is to use a URI (e.g., https://example.com/catalogs/mysurface/v1/catalog.json).
  • Purpose: We use URIs to make the ID globally unique and easy for human developers to inspect in a browser.
  • No Runtime Fetching: This URI does not imply that the agent or client downloads the catalog at runtime. The catalog definition must be known to the agent and client beforehand (at compile/deploy time). The URI serves only as a stable identifier.
  • JSON Schema Compatibility ($id and catalogId): Because A2UI catalogs are currently represented as JSON Schema documents, catalog definitions should include both $id (for JSON Schema tooling) and catalogId (for A2UI SDKs and catalog negotiation), setting both fields to the same URI.

Versioning Guidelines

Catalogs should be treated like APIs where version skew between agents and renderers is expected. Catalogs must maintain forward and backward compatibility across versions so that agents, renderers, and templates can evolve without breaking active sessions or stored transcripts.

Catalog Schema Evolution Rules

To maintain backward and forward compatibility, the following rules apply:

  1. Additive Only: It's safe to add new components, functions, or optional properties.
  2. Deprecate Rather than Delete: Never delete existing components, functions, or properties. Mark them deprecated: true along with an x-deprecated-reason instead. Renderers should retain handling logic for deprecated components and properties to maintain backward compatibility for historical transcripts, cached states, and templates.
  3. Type Invariance: Renderers may ignore fields that change the type of existing fields. Catalogs should avoid changing the data type of an existing field. Note that add enum to a string doesn't change the type and won't break anything since enums are expected to be open.
  4. Open Enums: Treat enum definitions as open so older renderers do not fail when new enum variants are introduced upstream. Catalogs declare enums normally, just the renderer implementation should handle unknown enum variants.
  5. Graceful Degradation: Renderers should provide a graceful fallback for unknown components instead of failing the entire view hierarchy or surface.
  6. Round-Trip Unknown Component/Field Preservation: Intermediary services such as orchestrators should preserve unknown properties when serializing/deserializing messages.
  7. Major Version Bumps: Reserve major catalog version bumps strictly for sweeping cleanups (e.g., removing long-deprecated fields) or fundamentally breaking structural changes.

Deprecating Catalog Properties

When deprecating an existing component, function, or property:

  • deprecated (boolean, optional): Standard JSON Schema annotation indicating that a component, function, or property is deprecated.
  • x-deprecated-reason (string, optional): Human-readable explanation of why the entity is deprecated and what to use instead.
json
{
  "TextBox": {
    "type": "object",
    "properties": {
      "color": {
        "type": "string",
        "description": "Icon color specification.",
        "deprecated": true,
        "x-deprecated-reason": "Use style property to set color using CSS."
      },
      "style": {
        "type": "string",
        "description": "CSS properties such as color."
      }
    }
  }
}

Major Version Bumps with CatalogId

When introducing fundamentally breaking changes that necessitate a major version bump, include the version in the catalogId URI. This allows using A2UI catalog negotiation to support multiple versions simultaneously during a migration, ensuring zero downtime.

Recommended Pattern:

Change Type URI Example Description
Current .../rizzcharts/v1/catalog.json Version 1.x. Supports all additive updates in the 1.x branch.
Breaking .../rizzcharts/v2/catalog.json A new schema introducing breaking structural changes.

Handling Migrations

To upgrade a catalog across major versions without breaking active agents, use A2UI Catalog Negotiation:

  1. Client Update: The client updates its list of supportedCatalogIds to include both the old and new versions (e.g., [".../v2/...", ".../v1/..."]).
  2. Agent Update: Agents are rebuilt with the v2 schema. When they see the client supports v2, they prefer it.
  3. Legacy Support: Older agents that have not yet been rebuilt will continue to match against v1 in the client's list, ensuring they remain functional.

A2UI Schema Validation & Fallback

To ensure a stable user experience, A2UI employs a two-phase validation strategy. This "defense in depth" approach catches errors as early as possible while ensuring clients remain robust when facing unexpected payloads.

Two-Phase Validation

  1. Agent-Side (Pre-Send): Before transmitting any UI payload, the agent runtime validates the generated JSON against the catalog definition.
    • Purpose: To catch hallucinated properties or malformed structures at the source.
    • Outcome: If validation fails, the agent can attempt to fix or regenerate the A2UI JSON, or it can do graceful degradation such as falling back to text in a conversational app.
  2. Client-Side: Upon receiving the payload, the client library validates the JSON against its local definition of the catalog.
    • Purpose: Security and stability. This ensures that the code executing on the user's device strictly conforms to the expected contract, protecting against version mismatches or compromised agent outputs.
    • Outcome: Failures here are reported back to the agent using the β€œerror” client message

Graceful Degradation

Even if a payload passes schema validation, the renderer may encounter runtime issues (e.g., a missing asset, a component implementation not yet loaded, or a platform-specific limitation).

Clients should not crash when encountering these errors. Instead, they should employ Graceful Degradation:

  • Unknown Components: If a component is recognized in the schema but not implemented in the renderer, render a "safe" fallback (e.g., a generic card with the component's debug name) or skip rendering that specific node entirely.
  • Text Fallback: If the entire surface fails to render, display the raw text description (if available) or a generic error message: "This interface could not be displayed."

Client-to-Server Error Reporting

When the client detects a validation error or a runtime failure, it can report this back to the agent. This allows the agent system to log the failure for developers or adjust its future behavior.

The client sends a VALIDATION_FAILED event using the standard A2UI Client-to-Server Event Schema.

Example of client reporting a missing required field

json
{
  "version": "v0.9",
  "error": {
    "code": "VALIDATION_FAILED",
    "surfaceId": "flight-status-card-123",
    "path": "/components/FlightCard/flightNumber",
    "message": "Missing required property 'flightNumber' in component 'FlightCard'."
  }
}

Inline Catalogs

Inline catalogs sent by the client at runtime are supported but not recommended in production. More details about them can be found here.


Public Concepts Components (`docs/public/concepts/components.md`)

Components & Structure

A2UI uses an adjacency list model for component hierarchies. Instead of nested JSON trees, components are a flat list with ID references.

Why Flat Lists?

Traditional nested approach:

  • LLM must generate perfect nesting in one pass
  • Hard to update deeply nested components
  • Difficult to stream incrementally

A2UI adjacency list:

  • Flat structure, easy for LLMs to generate.
  • Send components incrementally.
  • Update any component by ID.
  • Clear separation of structure and data.

The Adjacency List Model

=== "v0.8"

text
```json
{
  "surfaceUpdate": {
    "components": [
      {
        "id": "root",
        "component": {
          "Column": {
            "children": { "explicitList": ["greeting", "buttons"] }
          }
        }
      },
      {
        "id": "greeting",
        "component": {
          "Text": { "text": { "literalString": "Hello" } }
        }
      },
      {
        "id": "buttons",
        "component": {
          "Row": {
            "children": { "explicitList": ["cancel-btn", "ok-btn"] }
          }
        }
      },
      {
        "id": "cancel-btn",
        "component": {
          "Button": {
            "child": "cancel-text",
            "action": { "name": "cancel" }
          }
        }
      },
      {
        "id": "cancel-text",
        "component": {
          "Text": { "text": { "literalString": "Cancel" } }
        }
      },
      {
        "id": "ok-btn",
        "component": {
          "Button": {
            "child": "ok-text",
            "action": { "name": "ok" }
          }
        }
      },
      {
        "id": "ok-text",
        "component": {
          "Text": { "text": { "literalString": "OK" } }
        }
      }
    ]
  }
}
```

=== "v0.9 and later"

text
```json
{
  "version": "v0.9.1",
  "updateComponents": {
    "surfaceId": "main",
    "components": [
      {
        "id": "root",
        "component": "Column",
        "children": ["greeting", "buttons"]
      },
      {
        "id": "greeting",
        "component": "Text",
        "text": "Hello"
      },
      {
        "id": "buttons",
        "component": "Row",
        "children": ["cancel-btn", "ok-btn"]
      },
      {
        "id": "cancel-btn",
        "component": "Button",
        "child": "cancel-text",
        "action": { "event": { "name": "cancel" } }
      },
      {
        "id": "cancel-text",
        "component": "Text",
        "text": "Cancel"
      },
      {
        "id": "ok-btn",
        "component": "Button",
        "child": "ok-text",
        "action": { "event": { "name": "ok" } }
      },
      {
        "id": "ok-text",
        "component": "Text",
        "text": "OK"
      }
    ]
  }
}
```

v0.9 and later use a flatter component format: `"component": "Text"` instead of nested `{"Text": {...}}`, and children are simple arrays instead of `{"explicitList": [...]}`.

Components reference children by ID, not by nesting.

Component Basics

Every component has:

  1. ID: Unique identifier ("welcome")
  2. Type: Component type (Text, Button, Card)
  3. Properties: Configuration specific to that type

=== "v0.8"

text
```json
{
  "id": "welcome",
  "component": {
    "Text": {
      "text": { "literalString": "Hello" },
      "usageHint": "h1"
    }
  }
}
```

=== "v0.9 and later"

text
```json
{
  "id": "welcome",
  "component": "Text",
  "text": "Hello",
  "variant": "h1"
}
```

The Basic Catalog

To help developers get started quickly, the A2UI team maintains the Basic Catalog.

This is a pre-defined catalog file that contains a basic set of general-purpose components (Buttons, Inputs, Cards). It is not a special "type" of catalog; it is simply a version of a catalog that has open source renderers available.

For the complete component gallery with examples, see Component Reference.

Static vs. Dynamic Children

=== "v0.8"

text
**Static (`explicitList`)** - Fixed list of child IDs:

```json
{
  "children": {
    "explicitList": ["back-btn", "title", "menu-btn"]
  }
}
```

**Dynamic (`template`)** - Generate children from data array:

```json
{
  "children": {
    "template": {
      "dataBinding": "/items",
      "componentId": "item-template"
    }
  }
}
```

For each item in `/items`, render the `item-template`. See [Data Binding](data-binding.md) for details.

=== "v0.9 and later"

text
**Static** - Fixed list of child IDs:

```json
{
  "children": ["back-btn", "title", "menu-btn"]
}
```

**Dynamic** - Generate children from data array:

```json
{
  "children": {
    "path": "/items",
    "componentId": "item-template"
  }
}
```

For each item in `/items`, render the `item-template`. See [Data Binding](data-binding.md) for details.

Hydrating with Values

Components get their values two ways:

=== "v0.8"

text
- **Literal** - Fixed value: `{"text": {"literalString": "Welcome"}}`
- **Data-bound** - From data model: `{"text": {"path": "/user/name"}}`

=== "v0.9 and later"

text
- **Literal** - Fixed value: `{"text": "Welcome"}`
- **Data-bound** - From data model: `{"text": {"path": "/user/name"}}`

LLMs can generate components with literal values or bind them to data paths for dynamic content.

Composing Surfaces

Components compose into surfaces (widgets):

=== "v0.8"

text
1. LLM generates component definitions via `surfaceUpdate`
2. LLM populates data via `dataModelUpdate`
3. LLM signals render via `beginRendering`
4. Client renders all components as native widgets

=== "v0.9 and later"

text
1. LLM creates a surface via `createSurface` (specifying catalog; in v1.0, can also include initial data model and components)
2. LLM generates component definitions via `updateComponents`
3. LLM populates data via `updateDataModel`
4. Client renders all components as native widgets

A surface is a complete, cohesive UI (form, dashboard, chat, etc.).

Incremental Updates

Incremental updates support the following operations:

  • Add - Send new component definitions with new IDs
  • Update - Send component definitions with existing ID and new properties
  • Remove - Update parent's children list to exclude removed IDs

The flat structure makes all updates simple ID-based operations.

Defining Your Own Catalog

While the Basic Catalog is useful for starting out, most production applications will define their own catalog to reflect their specific design system.

By defining your own catalog, you restrict the agent to using exactly the components and visual language that exist in your application, rather than generic inputs or buttons.

See Defining Your Own Catalog Guide for implementation details.

Best Practices

  1. Descriptive IDs: Use "user-profile-card" not "c1"
  2. Shallow hierarchies: Avoid deep nesting
  3. Separate structure from content: Use data bindings, not literals
  4. Reuse with templates: One template, many instances via dynamic children

Public Concepts Data Binding (`docs/public/concepts/data-binding.md`)

Data Binding

Data binding connects UI components to application state using JSON Pointer paths (RFC 6901). It allows A2UI to efficiently define layouts for large arrays of data and to show updated content without regenerating it from scratch.

Structure vs. State

A2UI separates:

  1. UI Structure (Components): What the interface looks like
  2. Application State (Data Model): What data it displays

This enables:

  • Reactive updates.
  • Data-driven UIs.
  • Reusable templates.
  • Bidirectional binding.

The Data Model

Each surface has a JSON object holding state:

json
{
  "user": {"name": "Alice", "email": "[email protected]"},
  "cart": {
    "items": [{"name": "Widget", "price": 9.99, "quantity": 2}],
    "total": 19.98
  }
}

JSON Pointer Paths

Syntax:

  • /user/name - Object property
  • /cart/items/0 - Array index (zero-based)
  • /cart/items/0/price - Nested path

Example:

json
{"user": {"name": "Alice"}, "items": ["Apple", "Banana"]}
  • /user/name β†’ "Alice"
  • /items/0 β†’ "Apple"

Literal vs. Path Values

=== "v0.8"

text
**Literal (fixed):**
```json
{
  "id": "title",
  "component": {
    "Text": {
      "text": { "literalString": "Welcome" }
    }
  }
}
```

**Data-bound (reactive):**
```json
{
  "id": "username",
  "component": {
    "Text": {
      "text": { "path": "/user/name" }
    }
  }
}
```

=== "v0.9 and later"

text
**Literal (fixed):**
```json
{
  "id": "title",
  "component": "Text",
  "text": "Welcome"
}
```

**Data-bound (reactive):**
```json
{
  "id": "username",
  "component": "Text",
  "text": { "path": "/user/name" }
}
```

When /user/name changes from "Alice" to "Bob", the text automatically updates to "Bob".

Reactive Updates

Components bound to data paths automatically update when the data changes:

=== "v0.8"

text
```json
{
  "id": "status",
  "component": {
    "Text": {
      "text": {"path": "/order/status"}
    }
  }
}
```

=== "v0.9 and later"

text
```json
{
  "id": "status",
  "component": "Text",
  "text": {"path": "/order/status"}
}
```
  • Initial: /order/status = "Processing..." β†’ displays "Processing..."
  • Update: Send a data model update with status: "Shipped" β†’ displays "Shipped"

No component updates neededβ€”just data updates.

Dynamic Lists

Use templates to render arrays:

=== "v0.8"

text
```json
{
  "id": "product-list",
  "component": {
    "Column": {
      "children": {
        "template": {
          "dataBinding": "/products",
          "componentId": "product-card"
        }
      }
    }
  }
}
```

=== "v0.9 and later"

text
```json
{
  "id": "product-list",
  "component": "Column",
  "children": {
    "path": "/products",
    "componentId": "product-card"
  }
}
```

Data:

json
{
  "products": [
    {"name": "Widget", "price": 9.99},
    {"name": "Gadget", "price": 19.99}
  ]
}

Result: Two cards rendered, one per product.

Scoped Paths

Inside a template, paths are scoped to the array item:

=== "v0.8"

text
```json
{
  "id": "product-name",
  "component": {
    "Text": {
      "text": {"path": "/name"}
    }
  }
}
```

- For `/products/0`, `/name` resolves to `/products/0/name` β†’ "Widget"
- For `/products/1`, `/name` resolves to `/products/1/name` β†’ "Gadget"

=== "v0.9 and later"

text
```json
{
  "id": "product-name",
  "component": "Text",
  "text": {"path": "name"}
}
```

- For `/products/0`, `name` resolves to `/products/0/name` β†’ "Widget"
- For `/products/1`, `name` resolves to `/products/1/name` β†’ "Gadget"

Adding/removing items automatically updates the rendered components.

Input Bindings

Interactive components update the data model bidirectionally:

=== "v0.8"

text
| Component          | Example                                     | User Action      | Data Update                |
| ------------------ | ------------------------------------------- | ---------------- | -------------------------- |
| **TextField**      | `{"text": {"path": "/form/name"}}`          | Types "Alice"    | `/form/name` = `"Alice"`   |
| **CheckBox**       | `{"value": {"path": "/form/agreed"}}`       | Checks box       | `/form/agreed` = `true`    |
| **MultipleChoice** | `{"selections": {"path": "/form/country"}}` | Selects "Canada" | `/form/country` = `["ca"]` |

=== "v0.9 and later"

text
| Component          | Example                                     | User Action      | Data Update                |
| ------------------ | ------------------------------------------- | ---------------- | -------------------------- |
| **TextField**      | `{"value": {"path": "/form/name"}}`         | Types "Alice"    | `/form/name` = `"Alice"`   |
| **CheckBox**       | `{"value": {"path": "/form/agreed"}}`       | Checks box       | `/form/agreed` = `true`    |
| **ChoicePicker**   | `{"value": {"path": "/form/country"}}`      | Selects "Canada" | `/form/country` = `["ca"]` |

Best Practices

  • Use granular updates: Update only changed paths.

=== "v0.8"

text
```json
{
  "dataModelUpdate": {
    "path": "/user",
    "contents": [{"key": "name", "valueString": "Alice"}]
  }
}
```

=== "v0.9 and later"

text
```json
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "user_profile",
    "path": "/user/name",
    "value": "Alice"
  }
}
```
  • Organize by domain: Group related data.

    json
    {"user": {...}, "cart": {...}, "ui": {...}}
  • Pre-compute display values: Formats data (currency, dates) on the agent before sending.

    json
    {"price": "$19.99"} // Not: {"price": 19.99}

Public Concepts Data Flow (`docs/public/concepts/data-flow.md`)

Data Flow

How messages flow from agents to UI.

Architecture

text
Agent (LLM) β†’ A2UI Generator β†’ Transport (SSE/WS/A2A)
                                      ↓
Client (Stream Reader) β†’ Message Parser β†’ Renderer β†’ Native UI

Message Format

A2UI defines a sequence of JSON messages that describe the UI. When streamed, these messages are often formatted as JSON Lines (JSONL), where each line is a complete JSON object.

=== "v0.8"

text
```jsonl
{
  "surfaceUpdate": {
    "surfaceId": "main",
    "components": [...]
  }
}
{
  "dataModelUpdate": {
    "surfaceId": "main",
    "contents": [
      {
        "key": "user",
        "valueMap": [
          { "key": "name", "valueString": "Alice" }
        ]
      }
    ]
  }
}
{
  "beginRendering": {
    "surfaceId": "main",
    "root": "root-component"
  }
}
```

=== "v0.9"

text
```jsonl
{
  "version": "v0.9",
  "createSurface": {
    "surfaceId": "main",
    "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
  }
}
{
  "version": "v0.9",
  "updateComponents": {
    "surfaceId": "main",
    "components": [...]
  }
}
{
  "version": "v0.9",
  "updateDataModel": {
    "surfaceId": "main",
    "path": "/user",
    "value": { "name": "Alice" }
  }
}
```

Why this format?

A sequence of self-contained JSON objects is streaming-friendly, easy for LLMs to generate incrementally, and resilient to errors.

Lifecycle Example: Restaurant Booking

User: "Book a table for 2 tomorrow at 7pm"

=== "v0.8"

text
**1. Agent defines UI structure:**

```json
{
  "surfaceUpdate": {
    "surfaceId": "booking",
    "components": [
      {
        "id": "root",
        "component": {
          "Column": {
            "children": {
              "explicitList": ["header", "guests-field", "submit-btn"]
            }
          }
        }
      },
      {
        "id": "header",
        "component": {
          "Text": {
            "text": { "literalString": "Confirm Reservation" },
            "usageHint": "h1"
          }
        }
      },
      {
        "id": "guests-field",
        "component": {
          "TextField": {
            "label": { "literalString": "Guests" },
            "text": { "path": "/reservation/guests" }
          }
        }
      },
      {
        "id": "submit-btn",
        "component": {
          "Button": {
            "child": "submit-text",
            "action": {
              "name": "confirm",
              "context": [
                { "key": "details", "value": { "path": "/reservation" } }
              ]
            }
          }
        }
      }
    ]
  }
}
```

**2. Agent populates data:**

```json
{
  "dataModelUpdate": {
    "surfaceId": "booking",
    "path": "/reservation",
    "contents": [
      { "key": "datetime", "valueString": "2025-12-16T19:00:00Z" },
      { "key": "guests", "valueString": "2" }
    ]
  }
}
```

**3. Agent signals render:**

```json
{
  "beginRendering": {
    "surfaceId": "booking",
    "root": "root"
  }
}
```

**4. User edits guests to "3"** β†’ Client updates `/reservation/guests` automatically

**5. User clicks "Confirm"** β†’ Client sends action:

```json
{
  "userAction": {
    "name": "confirm",
    "surfaceId": "booking",
    "context": {
      "details": {
        "datetime": "2025-12-16T19:00:00Z",
        "guests": "3"
      }
    }
  }
}
```

**6. Agent responds** β†’ Updates UI or sends:

```json
{ "deleteSurface": { "surfaceId": "booking" } }
```

=== "v0.9"

text
**1. Agent creates surface:**

```json
{
  "version": "v0.9",
  "createSurface": {
    "surfaceId": "booking",
    "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
  }
}
```

**2. Agent defines UI structure:**

```json
{
  "version": "v0.9",
  "updateComponents": {
    "surfaceId": "booking",
    "components": [
      {
        "id": "root",
        "component": "Column",
        "children": ["header", "guests-field", "submit-btn"]
      },
      {
        "id": "header",
        "component": "Text",
        "text": "Confirm Reservation",
        "variant": "h1"
      },
      {
        "id": "guests-field",
        "component": "TextField",
        "label": "Guests",
        "value": { "path": "/reservation/guests" }
      },
      {
        "id": "submit-btn",
        "component": "Button",
        "child": "submit-text",
        "variant": "primary",
        "action": {
          "event": {
            "name": "confirm",
            "context": {
              "details": { "path": "/reservation" }
            }
          }
        }
      }
    ]
  }
}
```

**3. Agent populates data:**

```json
{
  "version": "v0.9",
  "updateDataModel": {
    "surfaceId": "booking",
    "path": "/reservation",
    "value": {
      "datetime": "2025-12-16T19:00:00Z",
      "guests": "2"
    }
  }
}
```

**4. User edits guests to "3"** β†’ Client updates `/reservation/guests` automatically

**5. User clicks "Confirm"** β†’ Client sends action:

```json
{
  "version": "v0.9",
  "action": {
    "name": "confirm",
    "surfaceId": "booking",
    "context": {
      "details": {
        "datetime": "2025-12-16T19:00:00Z",
        "guests": "3"
      }
    }
  }
}
```

**6. Agent responds** β†’ Updates UI or sends:

```json
{
  "version": "v0.9",
  "deleteSurface": { "surfaceId": "booking" }
}
```

Transport Options

A2UI is transport-agnostic β€” any mechanism that can deliver JSON messages works:

  • A2A Protocol: Standardized agent-to-agent communication, also used for agent-to-UI delivery
  • AG-UI: Bidirectional, real-time agent-UI protocol
  • REST / HTTP: Simple request-response or Server-Sent Events (SSE) for one-way streaming
  • WebSocket: Persistent bidirectional connection, ideal for real-time updates and user actions
  • Any other transport: gRPC, message queues, custom protocols β€” if it can carry JSON, it works

See transports for implementation details.

Progressive Rendering

Instead of waiting for the entire response to be generated before showing anything to the user, chunks of the response can be streamed to the client as they are generated and progressively rendered.

Users see UI building in real-time instead of staring at a spinner.

Error Handling

The system handles errors as follows:

  • Malformed messages: Skip and continue, or send error back to agent for correction.
  • Network interruptions: Display error state, reconnect, agent resends or resumes.

Performance

To optimize performance:

  • Batching: Buffer updates for 16ms, batch render together.
  • Diffing: Compare old/new components, update only changed properties.
  • Granular updates: Update /user/name not entire / model.

Public Concepts Glossary (`docs/public/concepts/glossary.md`)

Glossary

A2UI protocol, between renderer and agent:

The A2UI protocol defines a conversation between an agent and a renderer:

  1. The renderer sends its UI capabilities as a set of A2UI catalogs, with instructions on how to use them.
  2. The agent then repeats a loop:
    • Sends UI to render and functions to call, based on the catalog it received
    • Receives user input from the renderer
    • Updates the data shown in the UI

Agent and model:

To produce responses for the renderer, the agent sends requests to a model. The model can be:

  • a remote or local LLM, in most cases
  • another agent, in more complex orchestrations
  • a deterministic engine, usually for testing

Interaction overview:

mermaid
sequenceDiagram
    participant Renderer
    participant Agent
    participant Model

    Renderer->>Agent: Catalog & instructions

    loop Agentic flow
        Renderer->>Agent: User input
        Agent->>Model: Context + User input
        Model->>Agent: Response
        Agent->>Agent: Parse response
        Agent->>Renderer: Data + UI Updates. Function calls.
    end

A2UI SDKs:

The A2UI team provides two SDKs for a number of languages and frameworks:

  • Agent SDK: sets up the agent side of the interaction with the renderer and the model.
  • Framework adapter SDK (aka renderer SDK): sets up the renderer side of the interaction with the agent and handles incoming messages.

Terms in this glossary:

The terms below are grouped into three categories:

  • A2UI protocol terms: interaction between the renderer and the agent.
  • A2UI agent terms: agent architecture and the Agent SDK.
  • Generative UI terms: general generative UI concepts.

A2UI protocol terms

Terms required by the A2UI protocol.

A2UI agent and A2UI renderer

The protocol is designed for AI-powered agents, but it works with deterministic agents as well. For example, an agent may return a pre-canned A2UI UI.

If the agent is stateless, or does not guarantee that it preserves the catalog, the renderer should send the catalog with every message.

Some agents use a predefined catalog, so renderers must either support that catalog or use an adapter.

GenUI component

A UI component that the agent is allowed to use. Examples: date picker, carousel, button, hotel selector.

Catalog

  1. Itemized renderer capabilities:
    • List of components that the agent can use to generate UI
    • List of functions that can be called by the renderer or the agent
    • Styles and themes
  2. An explanation of how to use those capabilities.

Depending on the use case, catalog components can be more or less domain-specific:

  • Less specific:

    Basic UI primitives like buttons, labels, rows, columns, option-selectors and so on.

  • More specific:

    Components like HotelCheckout or FlightSelector.

Basic catalog

A catalog maintained by the A2UI team for getting started with A2UI quickly.

See the basic catalog.

Catalog transformer

A rule set that filters, adapts, or mutates a pristine catalog before system prompt instructions are generated or payload validation schemas are compiled.

Why catalog transformers are needed

A catalog specifies every component and function that a renderer supports, but agents often need to prune or restrict it:

  • Context window optimization: A catalog can define dozens of components and functions. Injecting all of their JSON schemas into a model's system prompt costs context window tokens, adds prompt latency, and raises inference costs. A pruning transformer keeps only the components that the agent's task needs.
  • Capability guardrails: Some workflows or security roles need interactive features restricted. For example, a guest mode may allow display cards and text, but no form inputs or administrative components.
  • Model signature reduction: Pruning function rules and validation checks produces compact prompt instructions for small models.

Example catalog transformers

  • ComponentPruningTransformer: a utility class that filters the catalog based on a list of allowed component names.

  • FunctionPruningTransformer: a utility class that filters the catalog based on a list of allowed function names.

Capabilities object

The set of catalogs that are available for use. An agent or a renderer advertises what it supports through this set, so the other side knows what it may send.

For example, if a renderer supports Basic, Hotels and Flights catalogs, it will advertise them in its capabilities object.

A capabilities object is exchanged through transport metadata or an initialization payload, not as an A2UI message. The exact placement is transport-specific.

The objects are named differently in earlier versions of the protocol: v0.9 and v0.9.1 call them client and server capabilities, and v0.8 defines only a client-side schema.

Surface

An area of UI, built by the A2UI agent and managed by the A2UI renderer, that consists of a number of components. Surfaces cannot nest.

A2UI message

A message between the agent and the renderer.

Because the protocol supports streaming, a message can be finished (fully delivered) or unfinished (partially delivered). A finished message is either completed (delivered successfully) or interrupted (delivery stopped because of a technical problem).

See the data flow guide.

Agent turn

The set of messages that the agent sends before it starts waiting for user input.

Data model

An observable, hierarchical, JSON-like object, shared between the renderer and the agent and updatable by both. Each surface has its own data model.

Components can bind to nodes of the data model and update automatically when those values change.

The data model gives two-way synchronization: it captures user interactions into a state object that is sent to the agent, and it lets the agent push data updates back to the UI.

See the data binding guide.

Data reference

In a component definition, a reference to a data element, resolved either by a path in the data model or by value.

See the example in the basic catalog.

Function

A named, catalog-defined operation with a declared argument signature and return type. Functions execute logic without sending raw executable code between the agent and renderer.

Functions are defined in catalogs and can execute on either the renderer or the agent:

  • Renderer function: A function executed on the renderer. Renderer functions handle client-side tasks such as formatting values, validating input fields, navigating, or controlling local UI state. The agent can also request the renderer to execute a function (for example, to display a notification or trigger device feedback).
  • Agent function: A function executed on the agent. When the UI requires server-side logic (such as checking inventory, querying a database, or verifying credentials), the renderer requests the agent to execute the function and return the result.

An A2UI function is not the same as an LLM tool:

Feature A2UI function LLM tool invocation
Executor A2UI renderer (for renderer functions) or agent (for agent functions). Model host or agent environment outside the UI.
Timing During UI rendering, on user interaction, or as a request between turns. Before or during model response generation.
Purpose UI logic: validation, formatting, client actions, or app operations. Model reasoning, web search, database retrieval.
Definition Declared in an A2UI catalog with argument and return signatures. Declared in a tool definition passed to the model.
State access Scoped to surface data context, local UI state, or catalog methods. Scoped to external tools, APIs, and services.

For details on how functions are routed and executed between the agent and renderer, see Functions in A2UI in the protocol specification.

Function call

An invocation of a catalog function with a set of arguments.

Function calls appear in several contexts:

  • Dynamic value bindings: The renderer calls a renderer function to calculate a component property value dynamically (for example, formatting a date or currency).
  • Validation rules: The renderer calls a renderer function to validate user input before submission.
  • Component interaction triggers: The renderer calls a renderer function when a user interacts with a component (for example, opening a URL).
  • Remote procedure calls (RPCs): The agent requests the renderer to execute a renderer function, or the renderer requests the agent to execute an agent function.

Function calls execute locally when the target function is implemented in the caller's environment. When the function resides on the other side of the agent-renderer boundary, the call is communicated over the protocol. In architectures where the agent and renderer run within the same process (such as a standalone desktop application), these calls can execute in-process without network transport.

Action

An event triggered by a user interaction with a UI component that is dispatched from the renderer to the agent.

An action informs the agent that an interaction occurred (such as clicking a button or selecting an option). It conveys the user's intent along with an action name, relevant values extracted from the renderer data model, and an optional description of the action.

Actions represent user intent communicated back to the agent to drive conversational turns or business logic. In contrast, when a component interaction triggers behavior directly without notifying the agent (such as opening a URL or running a client-side calculation), it performs a function call rather than dispatching an action.

See the detailed guide on actions.

A2UI agent terms

Terms used to describe agent architecture and the Agent SDK.

A2UI tag

In model-to-agent messages, a delimiter tag such as <a2ui-json> or <a2ui> that bounds an A2UI payload block inside the model's text output.

A single model turn can mix conversational text and structured UI blocks, so A2UI tags mark where each UI block starts and ends.

Inference format

The format of the structured UI blocks in model-to-agent messages, for example JSON, Express DSL syntax, or Elemental HTML tags.

Phases of parsing of model-to-agent responses

Tag unwrapping:

The first phase of response parsing (unwrap), where the parser scans the model response for opening and closing A2UI tags, and separates conversational text (e.g., "Here is your summary:") from the enclosed raw UI blocks.

Compilation:

The second phase of response parsing (compile), where the raw UI blocks extracted during unwrapping (standard JSON, Express DSL syntax, or Elemental HTML tags) are parsed, decompiled, and transformed into standard A2UI protocol payloads such as createSurface and updateDataModel.

Agent architecture

An A2UI agent can be built in several ways:

  • Same-process or server-side:

    The agent and the renderer may live in one process of a client side application. Example: a desktop Flutter application.

    Or the renderer may live on the box that displays the UI, and the agent on another box (server).

  • Orchestrator agent:

    A central orchestrator manages interactions between a user and several specialized sub-agents. The orchestrator can be in the same process or on the server.

  • Pulling / pushing:

    An agent can wait for messages and requests from the renderer, or push them to it.

  • Stateful / stateless:

    Agents can preserve state or be stateless.

  • Mixed with other protocols:

    A2UI can be used in combination with other protocols. For example, an agent may be an MCP and/or A2A server.

  • Something else:

    Custom variations of the options above are also possible.

Agent and renderer functionality consists of layers that can be developed separately and reused:

  • Core library:

    Set of primitives needed to describe a catalog and to interact with the agent.

    For example, see the JavaScript web core library.

  • Catalog schema:

    Definition of a catalog in the form of JSON.

    For example, see the basic catalog schema.

  • Agent SDK:

    Code that implements the agent's interaction with the model and the renderer.

  • Framework adapter (or renderer SDK):

    Code that runs the agent's instructions in a concrete framework. For example:

    • JavaScript core and catalogs may be adapted to Angular, Electron, React and Lit frameworks.
    • Dart core and catalogs may be adapted to Flutter and Jaspr frameworks.

    See the Angular adapter.

  • Catalog implementation:

    Implementation of the catalog schema for a framework.

    For example:

mermaid
flowchart TD;
cimpl-->fadapter("Framework<br>Adapter");
cimpl("Catalog<br>Implementation")-->cschema("Catalog<br>Schema");
cschema-->core("Core<br>Library");
agentsdk("Agent SDK")-->cschema;
agentsdk-->core;
fadapter-->core;
renderer-->fadapter;
renderer("Renderer")-->cimpl;
agent("Agent")-->agentsdk;

Generative UI terms

Terms that the A2UI protocol does not require, but that are common in the context of generative UI.

Known patterns of GenUI

  • Chat:

    Pieces of generated UI appear one by one, sorted by time, in a vertically scrollable area, mixed with user input.

  • Canvas:

    A space for collaboration with an agent.

  • Dashboard:

    Pieces of generated UI are organized by meaning rather than by time, and stay pinned where the user expects to see them.

  • Wizard:

    Pieces of generated UI are shown one by one, to collect the information needed for a certain task.

NoAI information

Information categorized as not accessible by AI, for example credit card details.

Application owners decide which information is not accessible by AI, and the answer is different in different contexts. For example, in some products medical history should never go to the AI, while in others the AI relies on medical history to help with diagnostics.

The term matters in the GenUI context, because end users want to clearly see which of their input may go to the AI and which may not.


Public Concepts Transports (`docs/public/concepts/transports.md`)

Transports (Message Passing)

Transports deliver A2UI messages from agents to clients. A2UI is transport-agnostic: use any method that can send JSON.

The actual component rendering is done by the renderer,
and the agents are responsible for generating the A2UI messages.
Getting the messages from the agent to the client is the job of the transport.

How It Works

text
Agent β†’ Transport β†’ Client Renderer

A2UI defines a sequence of JSON messages. The transport layer is responsible for delivering this sequence from the agent to the client. A common transport mechanism is a stream using a format like JSON Lines (JSONL), where each line is a single A2UI message.

Available Transports

Transport Status Use Case
A2A Protocol βœ… Stable Multi-agent systems, enterprise meshes
AG-UI βœ… Stable Full stack React, Vue, Angular applications (CopilotKit)
REST API πŸ“‹ Planned Simple HTTP endpoints
WebSockets πŸ’‘ Proposed Real-time bidirectional
SSE (Server-Sent Events) πŸ’‘ Proposed Web streaming

A2A Protocol

The Agent2Agent (A2A) protocol provides secure,
standardized agent communication. An A2A extension provides easy integration with A2UI.

Benefits:

  • Security and authentication built-in.
  • Bindings for many message formats, auth, and transport protocols.
  • Clean separation of concerns.

If you are using A2A, this should be nearly automatic.

TODO: Add a detailed guide.

See: A2A Extension Specification

AG-UI

AG-UI translates A2UI messages to AG-UI events and handles transport and state sync automatically. It is commonly used for full stack React, Vue, and Angular applications. CopilotKit is AG-UI's creator and primary consumer.

See: Use A2UI with Any Agent Framework (Using AG-UI): Set up CopilotKit with your agent framework of choice and enable A2UI rendering.

Custom Transports

You can use any transport that sends JSON:

HTTP/REST:

javascript
// TODO: Add an example

WebSockets:

javascript
// TODO: Add an example

Server-Sent Events:

javascript
// TODO: Add an example

Next Steps


Public Ecosystem A2ui In The World (`docs/public/ecosystem/a2ui-in-the-world.md`)

A2UI in the World

A2UI is being adopted by teams at Google and partner organizations to build the next generation of agent-driven applications. Here are real-world examples of where A2UI is making an impact.

Production Deployments

Google Opal: AI Mini-Apps for Everyone

Opal enables hundreds of thousands of people to build, edit, and share AI mini-apps using natural languageβ€”no coding required.

How Opal uses A2UI:

The Opal team at Google has been a core contributor to A2UI from the beginning. They use A2UI to power the dynamic, generative UI system that makes Opal's AI mini-apps possible.

  • Rapid prototyping: Build and test new UI patterns quickly.
  • User-generated apps: Anyone can create apps with custom UIs.
  • Dynamic interfaces: UIs adapt to each use case automatically.

"A2UI is foundational to our work. It gives us the flexibility to let the AI drive the user experience in novel ways, without being constrained by a fixed front-end. Its declarative nature and focus on security allow us to experiment quickly and safely."

β€” Dimitri Glazkov, Principal Engineer, Opal Team

Learn more: opal.google


Gemini Enterprise: Custom Agents for Business

Gemini Enterprise enables businesses to build powerful, custom AI agents tailored to their specific workflows and data.

How Gemini Enterprise uses A2UI:

A2UI is being integrated to allow enterprise agents to render rich, interactive UIs within business applicationsβ€”going beyond simple text responses to guide employees through complex workflows.

  • Data entry forms: AI-generated forms for structured data collection.
  • Approval dashboards: Dynamic UIs for review and approval processes.
  • Workflow automation: Step-by-step interfaces for complex tasks.
  • Custom enterprise UIs: Tailored interfaces for industry-specific needs.

"Our customers need their agents to do more than just answer questions; they need them to guide employees through complex workflows. A2UI will allow developers building on Gemini Enterprise to have their agents generate the dynamic, custom UIs needed for any task, from data entry forms to approval dashboards, dramatically accelerating workflow automation."

β€” Fred Jabbour, Product Manager, Gemini Enterprise

Note: A2UI rendering in Gemini Enterprise currently requires self-serve agents registered using the Agent-to-Agent (A2A) path (a2aAgentDefinition). Managed agents deployed on Vertex AI Agent Engine and registered using the adkAgentDefinition path do not currently support A2UI rendering.

Learn more: Gemini Enterprise


Flutter GenUI SDK: Generative UI for Mobile

The Flutter GenUI SDK brings dynamic, AI-generated UIs to Flutter applications across mobile, desktop, and web.

How GenUI uses A2UI:

GenUI SDK uses A2UI as the underlying protocol for communication between server-side agents and Flutter applications. When you use GenUI, you're using A2UI under the covers.

  • Cross-platform support: Same agent works on iOS, Android, web, desktop.
  • Native performance: Flutter widgets rendered natively on each platform.
  • Brand consistency: UIs match your app's design system.
  • Server-driven UI: Agents control what's shown without app updates.

"Our developers choose Flutter because it lets them quickly create expressive, brand-rich, custom design systems that feel great on every platform. A2UI was a great fit for Flutter's GenUI SDK because it ensures that every user, on every platform, gets a high quality native feeling experience."

β€” Vijay Menon, Engineering Director, Dart & Flutter

Try it:


Google ADK: Agent Development Kit

The Agent Development Kit (ADK) is Google's open-source framework for building and deploying AI agents. The built-in developer UI, ADK Web, includes native A2UI rendering.

How ADK uses A2UI:

ADK integrated the A2UI v0.8 basic catalog to automatically render spec-compliant agent parts as native UI components directly in the chat. ADK also handles A2UI↔A2A message conversion, so agents built with ADK can send rich UI to any A2UI-capable client.

  • Built-in rendering: ADK Web renders A2UI components natively in the dev UI.
  • A2A integration: A2UI messages are converted between A2A DataPart metadata and ADK events.
  • Agent SDK: The A2UI Python agent SDK provides an ADK extension for generating A2UI from agents.

Try it:


Partner Integrations

AG-UI / CopilotKit: Full-Stack Agentic Framework

AG-UI provides the protocol and CopilotKit provides the primary full-stack framework for building agentic applications, with day-zero A2UI compatibility.

How they work together:

AG-UI excels at creating high-bandwidth connections between custom frontends and their dedicated agents. By adding A2UI support, developers get the best of both worlds:

  • State synchronization: AG-UI handles app state and chat history.
  • A2UI rendering: Render dynamic UIs from third-party agents.
  • Multi-agent support: Coordinate UIs from multiple agents.
  • Framework integrations: React, Vue, Angular, and other app surfaces through CopilotKit.

"AG-UI excels at creating a high-bandwidth connection between a custom-built front-end and its dedicated agent. By adding support for A2UI, we're giving developers the best of both worlds. They can now build rich, state-synced applications that can also render dynamic UIs from third-party agents via A2UI. It's a perfect match for a multi-agent world."

β€” Atai Barkai, Founder of CopilotKit and AG-UI

Learn more:


AG2: Multi-Agent Framework with Native A2UI

AG2 is a popular multi-agent framework, offering advanced orchestration of agents. Its A2UIAgent is a reference agent with native A2UI support, enabling AG2 agents to generate rich, interactive UI served over both A2A and AG-UI.

How AG2 uses A2UI:

A2UIAgent extends AG2's ConversableAgent with built-in A2UI capabilities β€” prompt engineering, schema validation with retry, and action handling β€” so developers can add generative UI to their agents without custom rendering code.

  • Validated output: Built-in schema validation and retry ensures reliable A2UI generation.
  • Dual transport: Serves the same UI over A2A (JSON-RPC) and AG-UI (SSE).
  • Cross-platform: One agent serves web, desktop, and mobile clients.
  • Custom catalogs: Extend the component catalog with domain-specific components.

"A2UIAgent brings the A2UI protocol to AG2, enabling agent expression with dynamic, rich, and interactive UIs. Reliable, client-agnostic rendering means our developers spend less time on integration and more time building great experiences."

β€” Mark Sze, Founding Engineer, AG2

Learn more:


Google's AI-Powered Products

As Google adopts AI across the company, A2UI provides a standardized way for AI agents to exchange user interfaces, not just text.

Internal agent adoption:

  • Multi-agent workflows: Multiple agents contribute to the same surface.
  • Remote agent support: Agents running on different services can provide UI.
  • Standardization: Common protocol across teams reduces integration overhead.
  • External exposure: Internal agents can be easily exposed externally (e.g., Gemini Enterprise).

"Much like A2A lets any agent talk to another agent regardless of platform, A2UI standardizes the user interface layer and supports remote agent use cases through an orchestrator. This has been incredibly powerful for internal teams, allowing them to rapidly develop agents where rich user interfaces are the norm, not the exception. As Google pushes further into generative UI, A2UI provides a perfect platform for server-driven UI that renders on any client."

β€” James Wren, Senior Staff Engineer, AI Powered Google


Community Projects

The A2UI community is building exciting projects:

Open Source Examples

Third-Party Integrations

Community Contributions

Have you built something with A2UI? Share it with the community!


Next Steps

For more information, see the following resources:


Using A2UI in production? Share your story on GitHub Discussions.


Public Ecosystem Community (`docs/public/ecosystem/community.md`)

Community

Welcome to the A2UI community! The project is building the future of agent-driven interfaces together.

Get Involved

A2UI is an open-source project licensed under Apache 2.0. Contributions are welcome from developers, researchers, and anyone interested in advancing agentic user interfaces.

Community Showcase

Built something with A2UI? Show it off!

The best way to share your work is to post in GitHub Discussions with a short description, a link to your code, and a demo video (4 minutes or less). Notable projects are featured in the docs and release notes.

Community-built renderers are listed on the Ecosystem Renderers page β€” check there for community implementations and instructions for adding your own.

Official samples in the repo are a great reference for what good A2UI projects look like:

Project Partners

A2UI is developed in collaboration with Google Opal, Flutter (GenUI SDK), Gemini Enterprise, AG-UI / CopilotKit, and the A2A team. For full details and case studies, see A2UI in the World.

If you have made significant contributions to A2UI, please submit your name to this list.

Code of Conduct

A2UI is committed to providing a welcoming and inclusive environment for everyone. All participants are expected to:

  • Be respectful and considerate.
  • Welcome newcomers and help them get started.
  • Focus on what's best for the community.
  • Show empathy towards others.

Unacceptable behavior will not be tolerated. Report concerns to the project maintainers.

Recognition

All contributions are appreciated! Contributors are recognized in:

  • GitHub's contributor graph.
  • Release notes.
  • Project acknowledgments.

Significant contributors may be invited to join the project's maintainer team.

Stay Updated

To stay informed about project updates:

  • Watch the GitHub repo for notifications.
  • Star the repo to bookmark and show support.
  • Follow releases to get notified of new versions.

Ways to Contribute

github.com/a2ui-project/a2ui

  • Report Issues: Found a bug? Open an issue.
  • Build Renderers: See the roadmap for planned frameworks.
  • Share Examples: Post with #A2UI on X/LinkedIn, start a discussion.
  • Improve Docs: PRs welcome in the docs/ directory.

Questions?

If you have questions:

Thank you for being part of the A2UI community!


Public Ecosystem Renderers (`docs/public/ecosystem/renderers.md`)

Ecosystem Renderers

Community and third-party A2UI renderer implementations.

NOTE
These renderers are maintained by their respective authors, not the A2UI team. Check each project for compatibility, version support, and maintenance status.

TIP
Looking for the official A2UI React renderer? See @a2ui/react β€” the core A2UI React renderer maintained by the A2UI team.

Community Renderers

Renderer Platform v0.8 v0.9 v1.0 Activity Links
easyops-cn/a2ui-sdk (@a2ui-sdk/react) React (Web) βœ… ❌ β€” GitHub Β· npm Β· Docs
lmee/A2UI-Android Android (Compose) βœ… ❌ β€” GitHub
sivamrudram-eng/a2ui-react-native React Native βœ… ❌ β€” GitHub
zhama/a2ui React (Web) βœ… ❌ β€” β€” npm
jem-computer/A2UI-react React (Web) βœ… ❌ β€” GitHub
BBC6BAE9/a2ui-swift Apple native (SwiftUI, UIKit, AppKit) βœ… βœ… β€” GitHub
a2ui-vue Vue (Web) βœ… βœ… β€” GitHub Β· npm Β· Docs
AGenUI/AGenUI iOS, Android, HarmonyOS ❌ βœ… β€” GitHub Β· Official Website
lynx-family/lynx-stack (@lynx-js/genui/a2ui) Lynx (Mobile, Web, Desktop) ❌ βœ… β€” GitHub Β· npm Β· Docs
BoteAI/a2ui (@boteai/a2ui-render) React (Web) βœ… βœ… β€” GitHub Β· npm
kokoro-ele/a2ui-ink (@evanyu/a2ui-ink) Terminal / CLI (Ink) ❌ βœ… β€” GitHub Β· npm
yessGlory17/generative-mui (@yessglory/generative-mui-react) React + Material UI (Web) ❌ βœ… β€” GitHub Β· npm
ChaliceForAuri/a2ui-svelte (svelte-a2ui) Svelte 5 (Web) ❌ βœ… βœ… GitHub Β· npm Β· Demo
Language-Lit/material3-expressive-a2ui (@language-lit/material3-expressive-a2ui) React + Material 3 Expressive (Web) ❌ βœ… β€” GitHub Β· npm Β· Demo & Docs
uny/a2ui-compose (dev.ynagai.a2ui:a2ui-material3) Compose Multiplatform (Android, iOS, desktop, macOS, web JS/wasmJs) ❌ ❌ βœ… GitHub Β· Maven Central Β· API docs

Notable Mentions

These projects are early-stage or experimental:

These projects are not directly A2UI renderers but are closely related and do support A2UI:

Project Platform Description Links
vercel-labs/json-render (@json-render/*) React, Vue, Svelte, Solid, React Native Generative UI framework by Vercel β€” uses its own JSON schema (not A2UI protocol) with Zod-based component catalogs. Supports streaming, 36 pre-built shadcn/ui components, and cross-platform rendering. GitHub Β· npm Β· Docs

Agent-Side Libraries

These projects produce A2UI rather than rendering it.

Project Language Description Links
Max-Health-Inc/prefab (@maxhealth.tech/prefab) TypeScript Server-side component library that emits A2UI from a typed component tree, with a diagnostic for anything the Basic Catalog cannot express. Also serves A2UI over MCP as an a2ui:// resource or an embedded resource. GitHub Β· npm Β· Docs

Ecosystem Utilities

  • @a2ui/markdown-it β€” Enables markdown rendering in the Text widgets of all renderers.

Highlights

easyops-cn/a2ui-sdk (@a2ui-sdk/react) is the most fully-featured community React renderer, with 11 published versions, Radix UI primitives, Tailwind CSS styling, and a dedicated docs site. It was announced on the A2UI discussions. For the official A2UI React renderer, see @a2ui/react.

lmee/A2UI-Android fills an important gap β€” it's currently the only Jetpack Compose renderer, covering Android 5.0+ with 20+ components, data binding, and accessibility support.

sivamrudram-eng/a2ui-react-native is the only React Native renderer, enabling A2UI on iOS and Android via a single codebase.

BBC6BAE9/a2ui-swift is a Swift-based, spec-faithful native Apple renderer for
A2UI. It supports SwiftUI, UIKit, and AppKit through a shared A2UISwiftCore
layer, and runs across iOS, iPadOS, macOS, tvOS, visionOS, and watchOS. The
project supports A2UI v0.8, v0.9, and v0.9.1, with a focus on close alignment
with the official A2UI specification and Apple-native platform behavior. It
prioritizes spec-level interoperability and native platform expression over
pixel-identical rendering across different platforms. Default rendering uses
native Apple controls and platform-appropriate interaction patterns aligned with
the Human Interface Guidelines, while apps can still customize styling and
behavior through A2UI catalogs, themes, and custom components.

AGenUI/AGenUI is a cross-platform native renderer for A2UI v0.9, supporting iOS, Android, and HarmonyOS through a shared C++ core. It is designed for high performance, extensibility, and cross-platform consistency. AGenUI fully implements A2UI v0.9 and provides runtime APIs for extending UI components and function calls. It also introduces a custom Catalog that extends the Basic Catalog with Table, Carousel, Web, RichText, and a Styles property for richer appearance and layout control, while staying aligned with A2UI’s extensible Catalog model. See the component demo, the corresponding open-source A2UI Generation Skill, and learn more on GitHub.

lynx-family/lynx-stack (@lynx-js/genui/a2ui) provides the ReactLynx renderer for A2UI v0.9. It consumes validated server-to-client A2UI messages through a MessageStore, renders approved ReactLynx components from caller-supplied catalogs, and forwards generated UI actions through onAction. The published @lynx-js/genui package exposes the A2UI renderer via the a2ui subpath, while the GenUI CLI supports build-time catalog artifact and A2UI system prompt generation.

yessGlory17/generative-mui (@yessglory/generative-mui-react) is a Material UI renderer for A2UI (v0.9.1). It maps all 18 Basic Catalog components one-to-one onto MUI and renders them inside the host app's existing `` β€” the surface has no theme of its own, so it inherits the host palette/typography and re-skins top-to-bottom when the theme changes. It splits into a framework-neutral core (@yessglory/generative-mui-core β€” Zod schemas, JSONL parser, JSON Pointer, a deterministic subscribable SurfaceStore, and provider-agnostic agent tool schemas) and the React/MUI adapter, with the one-way react β†’ core dependency enforced at lint time. Beyond the Basic Catalog it ships an opt-in Extended Catalog (charts via @mui/x-charts, Table, and more), two-way data binding, checks-driven validation, streaming resilience (incremental skeletons, append-only string deltas, cycle/depth and per-node error boundaries), and security guards (unregistered types never execute; sx/style/className from the agent are stripped; a ReDoS length cap on local regex).

ChaliceForAuri/a2ui-svelte (svelte-a2ui) is a Svelte 5 renderer built natively on runes, targeting the v1.0 Candidate spec (component property names shared with v0.9). It covers the full Basic Catalog β€” all 18 components, the complete 59-name Icon enum plus the {svgPath} variant, all 14 built-in functions, checks validation, and collection templates β€” and replays the specification's own contact-form fixture in its test suite. The protocol core is a pure reducer with structural sharing, held in $state.raw so unchanged components skip re-rendering entirely; the threat model assumes a hostile agent (catalog allowlist, on*/function stripping, prototype-pollution refusal, escape-then-markup for text, bounded recursion, callableFrom enforcement). Transports include HTTP JSONL/SSE, the A2A extension binding (application/a2ui+json DataParts, renderer metadata lifting), and AG-UI activities (including the middleware's a2ui-surface type), with a2uiRendererCapabilities advertised on every outbound message. Styling is entirely host-owned CSS custom properties. The project is Apache-2.0 licensed, and a hosted demo streams the booking-form scenario live.

Language-Lit/material3-expressive-a2ui (@language-lit/material3-expressive-a2ui) is a Material 3 Expressive renderer for A2UI v0.9.1, built on Google's @a2ui/web_core message processor. It maps all 18 Basic Catalog components one-to-one onto the Material 3 Expressive React design system, so agent-generated surfaces share one theme, one type scale, and one motion system with the rest of the host app. It ships a useA2ui hook and an A2uiSurface component, two-way data binding, checks-driven validation, streaming placeholders for children named before they arrive, actions with resolved context, and an extensible catalog. The same catalog also plugs into the official @a2ui/react surface. The package has zero runtime dependencies beyond its peers (React, the design system, and web_core), styles only through design tokens, and is MIT licensed. A scripted live demo streams four scenarios in the browser with no backend.

uny/a2ui-compose (dev.ynagai.a2ui:a2ui-material3) is a Compose Multiplatform renderer that draws Android, iOS, desktop (JVM), macOS and web (Kotlin/JS and Kotlin/Wasm) from a single commonMain. It targets the v1.0 specification only and carries no v0.8/v0.9 compatibility layer, so it is listed under v1.0 only β€” see the protocol version note for why. The Material 3 module covers all 18 Basic Catalog components (every component the specification's 43 examples name), the built-in functions, checks validation and collection templates; a2ui-core is a Compose-free module with the protocol types, JSON Pointer binding and function evaluation, and a2ui-compose sits between the two with no design system attached, for hosts that bring their own widgets. Markdown in Text is a parser-free subset by default, with lists, tables and code fences behind an optional a2ui-material3-markdown module. The renderer fetches nothing itself β€” images go through a host-provided loader, and links are reduced to their labels so nothing opens a URL past openUrl. Apache-2.0, published to Maven Central, with a gallery app that replays the specification's examples on desktop, wasmJs and native macOS.

Submitting a Renderer

If you have built an A2UI renderer, submit it to be listed here.

How to submit

To submit a renderer, follow these steps:

  1. Fork the a2ui-project/a2ui repository
  2. Edit this file (docs/ecosystem/renderers.md) β€” add a row to the Community Renderers table with your renderer's name, platform, npm package (if any), version support, and a link to the source
  3. Open a PR against a2ui-project/a2ui with a short description of your renderer
  4. Post in GitHub Discussions β€” let the community know what you built! A short demo video goes a long way.

Need inspiration? Browse the community samples in the repo β€” these cover Angular, Lit, and ADK-based agents and are a good starting point.

What makes a good community renderer?

A listing is more likely to be accepted and used if it meets the following criteria:

  • Has published source code (open-source preferred, MIT or Apache 2.0).
  • Clearly states which A2UI spec version(s) it supports (v0.8, v0.9, v1.0).
  • Covers the basic components from the other A2UI renderers: text, buttons, inputs, basic layout components, etc.
  • Includes a README with install instructions and a minimal usage example.
  • Is actively maintained β€” flag it as archived if you're no longer supporting it.

Community renderers don't need to be production-ready to be listed β€” experimental and early-stage projects are welcome in the Notable Mentions section.


Public Guides A2ui With Any Agent Framework (`docs/public/guides/a2ui-with-any-agent-framework.md`)

Use A2UI with Any Agent Framework & Harness

A2UI is a declarative UI format. AG-UI is the transport
that carries A2UI messages between an agent and an app. Use this guide to add
A2UI to an AG-UI app or harness backed by ADK, LangGraph, Mastra, Strands,
CrewAI, Google Chat, Slack, or any other agent framework or service that
supports AG-UI.