Skip to content

A2UI Protocol v0.9 (Draft)

Living Document

This specification is automatically included from specification/v0_9/docs/a2ui_protocol.md. Any updates to the specification will automatically appear here.

Draft Status

Version 0.9 is currently in draft status. For production use, consider v0.8 (Stable).

See also: - v0.8 Protocol Specification (Stable) - Evolution Guide: v0.8 → v0.9


A2UI Protocol Logo

A2UI (Agent to UI) Protocol v0.9

A Specification for a JSON-Based, Streaming UI Protocol.

Version: 0.9 Status: Draft Created: Nov 20, 2025 Last Updated: Dec 3, 2025

A Specification for a JSON-Based, Streaming UI Protocol

Introduction

The A2UI Protocol is designed for dynamically rendering user interfaces from a stream of JSON objects sent from an A2A server. Its core philosophy emphasizes a clean separation of UI structure and application data, enabling progressive rendering as the client processes each message.

Communication occurs via a stream of JSON objects. The client parses each object as a distinct message and incrementally builds or updates the UI. The server-to-client protocol defines four message types:

  • createSurface: Signals the client to create a new surface and begin rendering it.
  • updateComponents: Provides a list of component definitions to be added to or updated in a specific surface.
  • updateDataModel: Provides new data to be inserted into or to replace a surface's data model.
  • deleteSurface: Explicitly removes a surface and its contents from the UI.
  • watchDataModel: Configures how and when the client sends data model updates to the server.

Changes from previous versions

Version 0.9 of the A2UI protocol represents a philosophical shift from previous versions. While v0.8 was optimized for LLMs that support structured output, v0.9 is designed to be embedded directly within a model's prompt. The LLM is then asked to produce JSON that matches the provided examples and schema descriptions.

This "prompt-first" approach offers several advantages:

  1. Richer Schema: The protocol is no longer limited by the constraints of structured output formats. This allows for more readable, complex, and expressive component catalogs.
  2. Modularity: The schema is now refactored into separate, more manageable components (e.g., [common_types.json], [standard_catalog.json], [server_to_client.json]), improving maintainability and modularity.

The main disadvantage of this approach is that it requires more complex post-generation validation, as the LLM is not strictly constrained by the schema. This requires robust error handling and correction, so the system can identify discrepancies and attempt to fix them before rendering, or request a retry or correction from the LLM.

See the evolution guide for a detailed explanation of the differences between v0.8 and v0.9.

Protocol Overview & Data Flow

The A2UI protocol uses a unidirectional stream of JSON messages from the server to the client to describe and update the UI. The client consumes this stream, builds the UI, and renders it. User interactions are handled separately, typically by sending events to a different endpoint, which may in turn trigger new messages on the UI stream.

Here is an example sequence of events (which don't have to be in exactly this order):

  1. Create Surface: The server sends a createSurface message to initialize the surface.
  2. Update Surface: The server sends one or more updateComponents messages containing the definitions for all the components that will be part of the surface.
  3. Update Data Model: The server can send updateDataModel messages at any time to populate or change the data that the UI components will display.
  4. Render: The client renders the UI for the surface, using the component definitions to build the structure and the data model to populate the content.
  5. Dynamic Updates: As the user interacts with the application or as new information becomes available, the server can send additional updateComponents and updateDataModel messages to dynamically change the UI.
  6. Delete Surface: When a UI region is no longer needed, the server sends a deleteSurface message to remove it.
sequenceDiagram participant Server participant Client Server->>+Client: 1. createSurface(surfaceId: "main") Server->>+Client: 2. updateComponents(surfaceId: "main", components: [...]) Server->>+Client: 2. updateDataModel(surfaceId: "main", actorId: "agent-1", updates: [...], versions: {...}) Note right of Client: 3. Client renders the UI for the "main" surface Client-->>-Server: (UI is displayed) Client-->>-Server: (UI is displayed) Note over Client, Server: Time passes, user interacts, or new data arrives... Server->>+Client: 4. updateComponents or updateDataModel (Dynamic Update) Note right of Client: Client re-renders the UI to reflect changes Client-->>-Server: (UI is updated) Server->>+Client: 5. deleteSurface(surfaceId: "main") Note right of Client: Client removes the UI for the "main" surface Client-->>-Server: (UI is gone)

The Protocol Schemas

A2UI v0.9 is defined by three interacting JSON schemas.

Common Types

The [common_types.json] schema defines reusable primitives used throughout the protocol.

  • DynamicString / DynamicNumber / DynamicBoolean / DynamicStringList: The core of the data binding system. Any property that can be bound to data is defined as a Dynamic* type. It accepts either a literal value, a path string (JSON Pointer), or a FunctionCall (function call).
  • ChildList: Defines how containers hold children. It supports:

  • array: A static array of string component IDs.

  • object: A template for generating children from a data binding list (requires a template componentId and a data binding path).

  • id: The unique identifier for a component. Defined here so that all IDs are consistent and can be used for data binding.

  • weight: The relative weight of a component within a Row or Column. This corresponds to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column. Defined here so that all weights are consistent and can be used for data binding.

Server to Client Message Structure: The Envelope

The [server_to_client.json] schema is the top-level entry point. Every line streamed by the server must validate against this schema. It handles the message dispatching.

The Standard Catalog

The [standard_catalog.json] schema contains the definitions for all specific UI components (e.g., Text, Button, Row) and functions (e.g., required, email). By separating this from the envelope, developers can easily swap in custom catalogs (e.g., material_catalog.json or cupertino_catalog.json) without rewriting the core protocol parser.

Custom catalogs can be used to define additional UI components or modify the behavior of existing components. To use a custom catalog, simply include it in the prompt in place of the standard catalog. It should have the same form as the standard catalog, and use common elements in the [common_types.json] schema.

Envelope Message Structure

The envelope defines five primary message types, and every message streamed by the server must be a JSON object containing exactly one of the following keys: createSurface, updateComponents, updateDataModel, deleteSurface or watchDataModel. The key indicates the type of message, and these are the messages that make up each message in the protocol stream.

createSurface

This message signals the client to create a new surface and begin rendering it. This message MUST be sent before the first updateComponents message that references this surfaceId. One of the components in one of the components lists MUST have an id of root to serve as the root of the component tree.

Properties:

  • surfaceId (string, required): The unique identifier for the UI surface to be rendered.
  • catalogId (string, required): A string that uniquely identifies the catalog (components and functions) used for this surface. It is recommended to prefix this with an internet domain that you own, to avoid conflicts (e.g., https://mycompany.com/1.0/somecatalog).

Example:

{
  "createSurface": {
    "surfaceId": "user_profile_card",
    "catalogId": "https://a2ui.dev/specification/v0_9/standard_catalog.json"
  }
}

updateComponents

This message provides a list of UI components to be added to or updated within a specific surface. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list. This message may not be sent until after a createSurface message that references this surfaceId has been sent.

Properties:

  • surfaceId (string, required): The unique identifier for the UI surface to be updated. This is typically a name with meaning (e.g. "user_profile_card"), and it has to be unique within the context of the GenUI session.
  • components (array, required): A list of component objects. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list.

Example:

{
  "updateComponents": {
    "surfaceId": "user_profile_card",
    "components": [
      {
        "id": "root",
        "component": "Column",
        "children": ["user_name", "user_title"]
      },
      {
        "id": "user_name",
        "component": "Text",
        "text": "John Doe"
      },
      {
        "id": "user_title",
        "component": "Text",
        "text": "Software Engineer"
      }
    ]
  }
}

updateDataModel

This message is used to send or update the data that populates the UI components. It allows the server to change the UI's content without resending the entire component structure.

Properties:

  • surfaceId (string, required): The unique identifier for the UI surface this data model update applies to.
  • actorId (string, required): The ID of the actor that sent the update.
  • updates (array, required): A list of DataUpdate objects (path, value, hlc, and optional pos).
  • versions (object, required): A VersionVector map (actorId -> hlc).

Example:

{
  "updateDataModel": {
    "surfaceId": "user_profile_card",
    "actorId": "agent-1",
    "updates": [
      {
        "path": "/user/name",
        "value": "Jane Doe",
        "hlc": "2026-01-12T16:34:29.000Z:0001:agent-1"
      },
      {
        "path": "/user/title",
        "value": "Software Engineer",
        "hlc": "2026-01-12T16:34:29.000Z:0002:agent-1"
      }
    ],
    "versions": {
      "agent-1": "2026-01-12T16:34:29.000Z:0002:agent-1"
    }
  }
}

deleteSurface

This message instructs the client to remove a surface and all its associated components and data from the UI.

Properties:

  • surfaceId (string, required): The unique identifier for the UI surface to be deleted.

Example:

{
  "deleteSurface": {
    "surfaceId": "user_profile_card"
  }
}

watchDataModel

This message configures how and when the client sends data model updates to the server. It allows restricting updates to specific paths and setting update modes (e.g., immediate, on timeout, or only on user action).

Properties:

  • surfaceId (string, required): The unique identifier for the UI surface to be configured.
  • configurations (array, required): A list of configuration rules.
  • path (string, required): The data path to configure.
  • mode (string, required): One of onAction or onChanged.
  • timeoutMs (integer, optional): The duration in milliseconds to wait before sending updates when in onChanged mode. Defaults to 0 (immediate).

Nested Path Precedence:

If multiple configurations apply to the same data (e.g., one for / and one for /user/name), the most specific path (the longest matching path) takes precedence. For example, if / is set to onAction and /user/name is set to onChanged (with 0ms), changes to /user/name will be sent immediately, while changes to /user/bio will wait for a user action.

Example:

{
  "watchDataModel": {
    "surfaceId": "user_profile_card",
    "configurations": [
      {
        "path": "/",
        "mode": "onAction"
      },
      {
        "path": "/user/name",
        "mode": "onChanged",
      }
    ]
  }
}

Example Stream

The following example demonstrates a complete interaction to render a Contact Form, expressed as a JSONL stream.

{"createSurface":{"surfaceId":"contact_form_1","catalogId":"https://a2ui.dev/specification/v0_9/standard_catalog.json"}}
{"updateComponents":{"surfaceId":"contact_form_1","components":[{"id":"root","component":"Card","child":"form_container"},{"id":"form_container","component":"Column","children":["header_row","name_row","email_group","phone_group","pref_group","divider_1","newsletter_checkbox","submit_button"],"justify":"start","align":"stretch"},{"id":"header_row","component":"Row","children":["header_icon","header_text"],"align":"center"},{"id":"header_icon","component":"Icon","name":"mail"},{"id":"header_text","component":"Text","text":"# Contact Us","variant":"h2"},{"id":"name_row","component":"Row","children":["first_name_group","last_name_group"],"justify":"spaceBetween"},{"id":"first_name_group","component":"Column","children":["first_name_label","first_name_field"],"weight":1},{"id":"first_name_label","component":"Text","text":"First Name","variant":"caption"},{"id":"first_name_field","component":"TextField","label":"First Name","value":{"path":"/contact/firstName"},"variant":"shortText"},{"id":"last_name_group","component":"Column","children":["last_name_label","last_name_field"],"weight":1},{"id":"last_name_label","component":"Text","text":"Last Name","variant":"caption"},{"id":"last_name_field","component":"TextField","label":"Last Name","value":{"path":"/contact/lastName"},"variant":"shortText"},{"id":"email_group","component":"Column","children":["email_label","email_field"]},{"id":"email_label","component":"Text","text":"Email Address","variant":"caption"},{"id":"email_field","component":"TextField","label":"Email","value":{"path":"/contact/email"},"variant":"shortText","checks":[{"call":"required","message":"Email is required."},{"call":"email","message":"Please enter a valid email address."}]},{"id":"phone_group","component":"Column","children":["phone_label","phone_field"]},{"id":"phone_label","component":"Text","text":"Phone Number","variant":"caption"},{"id":"phone_field","component":"TextField","label":"Phone","value":{"path":"/contact/phone"},"variant":"shortText","checks":[{"call":"regex","args":{"pattern":"^\\d{10}$"},"message":"Phone number must be 10 digits."}]},{"id":"pref_group","component":"Column","children":["pref_label","pref_picker"]},{"id":"pref_label","component":"Text","text":"Preferred Contact Method","variant":"caption"},{"id":"pref_picker","component":"ChoicePicker","variant":"mutuallyExclusive","options":[{"label":"Email","value":"email"},{"label":"Phone","value":"phone"},{"label":"SMS","value":"sms"}],"value":{"path":"/contact/preference"}},{"id":"divider_1","component":"Divider","axis":"horizontal"},{"id":"newsletter_checkbox","component":"CheckBox","label":"Subscribe to our newsletter","value":{"path":"/contact/subscribe"}},{"id":"submit_button_label","component":"Text","text":"Send Message"},{"id":"submit_button","component":"Button","child":"submit_button_label","primary":true,"action":{"name":"submitContactForm","context":{"formId":"contact_form_1","clientTime":{"call":"now","returnType":"string"},"isNewsletterSubscribed":{"path":"/contact/subscribe"}}}}]}}
{"updateDataModel":{"surfaceId":"contact_form_1","actorId":"agent-1","updates":[{"path":"/contact","value":{"firstName":"John","lastName":"Doe","email":"john.doe@example.com","phone":"1234567890","preference":["email"],"subscribe":true},"hlc":"2026-01-12T16:34:29.000Z:0001:agent-1"}],"versions":{"agent-1":"2026-01-12T16:34:29.000Z:0001:agent-1"}}}

Component Model

A2UI's component model is designed for flexibility, separating the protocol's structure from the set of available UI components.

The Component Object

Each object in the components array of a updateComponents message defines a single UI component. It has the following structure:

  • id (string, required): A unique string that identifies this specific component instance. This is used for parent-child references.
  • weight (number, optional): The relative weight of this component within a Row or Column, corresponding to the CSS flex-grow property.
  • component (string, required): Specifies the component's type (e.g., "Text").
  • Component Properties: Other properties relevant to the specific component type (e.g., text, url, children) are included directly in the component object.

This structure is designed to be both flexible and strictly validated.

The Component Catalog

The set of available UI components and functions is defined in a Catalog. The standard catalog is defined in [standard_catalog.json]. This allows for different clients to support different sets of components and functions, including custom ones. The server must generate messages that conform to the catalog understood by the client.

UI Composition: The Adjacency List Model

The A2UI protocol defines the UI as a flat list of components. The tree structure is built implicitly using ID references. This is known as an adjacency list model.

Container components (like Row, Column, List, and Card) have properties that reference the id of their child component(s). The client is responsible for storing all components in a map (e.g., Map<String, Component>) and recreating the tree structure at render time.

This model allows the server to send component definitions in any order, as long as all necessary components are present before rendering is triggered.

There must be exactly one component with the ID root in the component tree, acting as the root of the component tree. Until that component is defined, other component updates will have no visible effect, and they will be buffered until a root component is defined. Once a root component is defined, the client is responsible for rendering the tree in the best way possible based on the available data, skipping invalid references.

flowchart TD subgraph "Server Stream" A("updateComponents
components: [root, title, button]") end subgraph "Client-Side Buffer (Map)" C("root: {id: 'root', component: 'Column', children: ['title', 'button']}") D("title: {id: 'title', component: 'Text', text: 'Welcome'}") E("button: {id: 'button', component: 'Button', child: 'button_label'}") end subgraph "Rendered Widget Tree" F(Column) --> G(Text: 'Welcome') F --> H(Button) end A -- "Parsed and stored" --> C A -- "Parsed and stored" --> D A -- "Parsed and stored" --> E

Data Model Representation: Binding, Scope, and Interpolation

This section describes how UI components represent and reference data from the Data Model. A2UI relies on a strictly defined relationship between the UI structure (Components) and the state (Data Model), defining the mechanics of path resolution, variable scope during iteration, and interpolation.

Path Resolution & Scope

Data bindings in A2UI are defined using JSON Pointers (RFC 6901). How a pointer is resolved depends on the current Evaluation Scope.

The Root Scope

By default, all components operate in the Root Scope.

  • Paths starting with / (e.g., /user/profile/name) are Absolute Paths. They always resolve from the root of the Data Model, regardless of where the component is nested in the UI tree.

Collection Scopes (Relative Paths)

When a container component (such as Column, Row, or List) utilizes the Template feature of ChildList, it creates a new Child Scope for each item in the bound array.

  • Template Definition: When a container binds its children to a path (e.g., path: "/users"), the client iterates over the array found at that location.
  • Scope Instantiation: For every item in the array, the client instantiates the template component.
  • Relative Resolution: Inside these instantiated components, any path that does not start with a forward slash / is treated as a Relative Path.

  • A relative path firstName inside a template iterating over /users resolves to /users/0/firstName for the first item, /users/1/firstName for the second, etc.

  • Mixing Scopes: Components inside a Child Scope can still access the Root Scope by using an Absolute Path.

Example: Scope Resolution

Data Model:

{
  "company": "Acme Corp",
  "employees": [
    { "name": "Alice", "role": "Engineer" },
    { "name": "Bob", "role": "Designer" }
  ]
}

Component Definition:

{
  "id": "employee_list",
  "component": "List",
  "children": {
    "path": "/employees",
    "componentId": "employee_card_template"
  }
},
{
  "id": "employee_card_template",
  "component": "Column",
  "children": ["name_text", "company_text"]
},
{
  "id": "name_text",
  "component": "Text",
  "text": { "path": "name" }
  // "name" is Relative. Resolves to /employees/N/name
},
{
  "id": "company_text",
  "component": "Text",
  "text": { "path": "/company" }
  // "/company" is Absolute. Resolves to "Acme Corp" globally.
}

String Interpolation

A2UI supports embedding dynamic expressions directly within string properties (any property defined as a DynamicString in the catalog). This allows for mixing static text with data model values and function results.

Syntax

Interpolated expressions are enclosed in ${...}. To include a literal ${ in a string, it must be escaped as \${.

Data Model Binding

Values from the data model can be interpolated using their JSON Pointer path.

  • ${/user/profile/name}: Absolute path.
  • ${firstName}: Relative path (resolved against the current collection scope).

Example:

{
  "id": "user_welcome",
  "component": "Text",
  "text": "Hello, ${/user/firstName}! Welcome back to ${/appName}."
}

Client-Side Functions

Results of client-side functions can be interpolated. Function calls are identified by the presence of parentheses ().

  • ${now()}: A function with no arguments.
  • ${formatDate(${/currentDate}, 'yyyy-MM-dd')}: A function with positional arguments.

Arguments can be Literals (quoted strings, numbers, or booleans), or Nested Expressions.

Nested Interpolation

Expressions can be nested using additional ${...} wrappers inside an outer expression to make bindings explicit or to chain function calls.

  • Explicit Binding: ${formatDate(${/currentDate}, 'yyyy-MM-dd')}
  • Nested Functions: ${upper(${now()})}

Type Conversion

When a non-string value is interpolated, the client converts it to a string:

  • Numbers/Booleans: Standard string representation.
  • Null/Undefined: An empty string "".
  • Objects/Arrays: Stringified as JSON to ensure consistency across different client implementations.

Two-Way Binding & Input Components

Interactive components that accept user input (TextField, CheckBox, Slider, ChoicePicker, DateTimeInput) establish a Two-Way Binding with the Data Model.

The Read/Write Contract

Unlike static display components (like Text), input components modify the client-side data model immediately upon user interaction.

  1. Read (Model -> View): When the component renders, it reads its value from the bound path. If the Data Model is updated via updateDataModel, the component re-renders to reflect the new value.
  2. Write (View -> Model): When the user interacts with the component (e.g., types a character, toggles a box), the client immediately updates the value at the bound path in the local Data Model.

Reactivity

Because the local Data Model is the single source of truth, updates from input components are reactive.

  • If a TextField is bound to /user/name, and a separate Text label is also bound to /user/name, the label must update in real-time as the user types in the text field.

Server Synchronization

It is critical to note that Two-Way Binding is local to the client.

  • User inputs (keystrokes, toggles) do not automatically trigger network requests to the server.
  • The updated state is sent to the server only when a specific User Action is triggered (e.g., a Button click).
  • When a action is dispatched, the context property of the action can reference the modified data paths to send the user's input back to the server.

Example: Form Submission Pattern

  1. Bind: TextField is bound to /formData/email.
  2. Interact: User types "jane@example.com". The local model at /formData/email is updated.
  3. Action: A "Submit" button has the following action definition:

    "action": {
      "name": "submit_form",
      "context": {
        "email": { "path": "/formData/email" }
      }
    }
    
  4. Send: When clicked, the client resolves /formData/email (getting "jane@example.com") and sends it in the action payload.

Data Model Updates: Synchronization and Convergence

While the sections above describe how components reference data, this section defines how the Data Model itself is updated and synchronized in the dataModelChanged (Renderer -> Agent) and updateDataModel (Agent -> Renderer) messages. To support reliable bidirectional data synchronization between the Renderer and the Agent, A2UI employs a Conflict-free Replicated Data Type (CRDT) approach based on a Last-Write-Wins Element-Map (LWW-Map).

In this model, the Renderer remains the primary source of truth for the UI state, but both the Renderer and the Agent may issue updates. Conflicts are resolved deterministically without a central coordinator, using metadata attached to every change.

NOTE: Because these data syncing primitives and resolutions are complex, the LLM shouldn't be asked to create these directly. Instead, it is recommended that the LLM be given a JSON representation of the data structure in its context, and instructions to mutate it with JSON patches. Code in the agent can then translate the LLM's JSON patches into the A2UI data model update equivalents. The agent can also keep track of the state of its copy of the data model.

Hybrid Logical Clocks (HLC)

Every update to the data model must be timestamped with a Hybrid Logical Clock (HLC) string. HLCs ensure partial ordering of events and provide a tie-breaking mechanism for concurrent updates that can be sorted lexicographically.

The HLC format is a string: <ISO-8601-Timestamp>:<Counter>:<ActorID> Example: 2024-05-20T14:30:05.123Z:0001:agent_alpha

Comparison Rule: HLCs are compared lexicographically. An update with a higher HLC string always supersedes an update with a lower HLC string for the same data path.

Fractional Indexing

For ordered lists within the data model, A2UI uses Fractional Indexing (also known as Lexicographical Indexing). Instead of numeric array indices, each item is assigned a pos string. No actual arrays are used, since they contain mutable indexes that are difficult to maintain in a distributed system. A list is represented as a JSON object with stable IDs as keys, and a pos property within each object to determine the sort order. The pos values don't need to be contiguous, and can be any string that can be sorted lexicographically.

  • Ordering: Items are sorted by their pos string value.
  • Insertion: To insert between position "a" and "b", a new string "an" is generated.
  • Conflict Resolution: If two actors insert at the same position, the HLC of the operation determines the final state, ensuring all participants see the same order.

JSON Pointers

The "path" field in the data model updates is a JSON Pointer to a value in the data model, but since data model updates can't use regular JSON arrays (they use fractional indexing instead), they don't support integers as path segments. The root of the data model is represented by "/", and replacing this path will replace the entire data model.

Tombstones

To ensure deletions propagate correctly in a distributed system, A2UI uses Tombstones. When a value is deleted, it is not immediately removed from the synchronization log. Instead, its value in the synchronization log is unset (but leaving a record with an HLC), indicating that it is a "tombstone". A "tombstone" with a higher HLC wins over a "set" with a lower HLC.

Convergence Logic (The "Last-Write-Wins" Rule)

When a participant (client or server) receives an update for a specific path:

  1. If incoming.hlc > local.hlc:

    • Set local.value to incoming.value (which may be undefined if it is a tombstone).
    • Update local.hlc to incoming.hlc.
    • If incoming.hlc == local.hlc:

    • This should ideally only happen if the update is identical. If values differ, use a secondary tie-breaker (e.g., the higher actorId wins).

    • If incoming.hlc < local.hlc:

    • Ignore the update (it is stale).

Client-Side Logic & Validation

A2UI v0.9 generalizes client-side logic into Functions. These can be used for validation, data transformation, and dynamic property binding.

Registered Functions

The client registers a set of named Functions (e.g., required, regex, email, add, concat) in a FunctionCatalog. The server references these functions by name. This avoids sending executable code.

Input components (like TextField, CheckBox) can define a list of checks. Each failure produces a specific error message that can be displayed when the component is rendered. Note that for validation checks, the function must return a boolean.

"checks": [
  {
    "call": "required",
    "args": { "value": { "path": "/formData/zip" } },
    "message": "Zip code is required"
  },
  {
    "call": "regex",
    "args": {
      "value": { "path": "/formData/zip" },
      "pattern": "^[0-9]{5}$"
    },
    "message": "Must be a 5-digit zip code"
  }
]

Example: Button Validation

Buttons can also define checks. If any check fails, the button is automatically disabled. This allows the button's state to depend on the validity of data in the model.

{
  "component": "Button",
  "text": "Submit",
  "checks": [
    {
      "and": [
        {
          "call": "required",
          "args": { "value": { "path": "/formData/terms" } }
        },
        {
          "or": [
            {
              "call": "required",
              "args": { "value": { "path": "/formData/email" } }
            },
            {
              "call": "required",
              "args": { "value": { "path": "/formData/phone" } }
            }
          ]
        }
      ],
      "message": "You must accept terms AND provide either email or phone"
    }
  ]
}

Standard Component Catalog

The [standard_catalog.json] provides the baseline set of components and functions.

Component Description
Text Displays text. Supports simple Markdown.
Image Displays an image from a URL.
Icon Displays a system-provided icon from a predefined list.
Video Displays a video from a URL.
AudioPlayer A player for audio content from a URL.
Row A horizontal layout container.
Column A vertical layout container.
List A scrollable list of components.
Card A container with card-like styling.
Tabs A set of tabs, each with a title and child component.
Divider A horizontal or vertical dividing line.
Modal A dialog that appears over the main content triggered by a button in the main content.
Button A clickable button that dispatches an action.
CheckBox A checkbox with a label and a boolean value.
TextField A field for user text input.
DateTimeInput An input for date and/or time.
ChoicePicker A component for selecting one or more options.
Slider A slider for selecting a numeric value within a range.

Usage Pattern: The Prompt-Generate-Validate Loop

The A2UI protocol is designed to be used in a three-step loop with a Large Language Model:

  1. Prompt: Construct a prompt for the LLM that includes:

    • The desired UI to be generated.
    • The A2UI JSON schema, including the component catalog.
    • Examples of valid A2UI JSON.
  2. Generate: Send the prompt to the LLM and receive the generated JSON output.

  3. Validate: Validate the generated JSON against the A2UI schema. If the JSON is valid, it can be sent to the client for rendering. If it is invalid, the errors can be reported back to the LLM in a subsequent prompt, allowing it to self-correct.

This loop allows for a high degree of flexibility and robustness, as the system can leverage the generative capabilities of the LLM while still enforcing the structural integrity of the UI protocol.

Standard Validation Error Format

If validation fails, the client (or the system acting on behalf of the client) should send an error message back to the LLM. To ensure the LLM can understand and correct the error, use the following standard format within the error message payload:

  • code (string, required): Must be "VALIDATION_FAILED".
  • surfaceId (string, required): The ID of the surface where the error occurred.
  • path (string, required): The JSON pointer to the field that failed validation (e.g. /components/0/text).
  • message (string, required): A short one-sentence description of why validation failed.

Example Error Message:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "surfaceId": "user_profile_card",
    "path": "/components/0/text",
    "message": "Expected stringOrPath, got integer"
  }
}

Client-to-Server Messages

The protocol also defines messages that the client can send to the server, which are defined in the [client_to_server.json] schema. These are used for handling user interactions and reporting client-side information.

action

This message is sent when the user interacts with a component that has an action defined, such as a Button.

Properties:

  • name (string, required): The name of the action.
  • surfaceId (string, required): The ID of the surface where the action originated.
  • sourceComponentId (string, required): The ID of the component that triggered the action.
  • timestamp (string, required): An ISO 8601 timestamp.
  • context (object, required): A JSON object containing any context provided in the component's action property.

dataModelChanged

This message is sent by the client to update the server's data model. This typically happens when watchDataModel has enabled onChanged mode for specific paths, or when a user action has triggered an update for a path set to onAction mode.

Properties:

  • surfaceId (string, required): The ID of the surface.
  • actorId (string, required): The ID of the actor that sent the update.
  • updates (array, required): A list of DataUpdate objects.
  • versions (object, required): A VersionVector map.

Client Capabilities

Client capabilities are sent by the client to inform the server of its capabilities, including supported catalogs (components and functions). In A2UI v0.9, these are sent as part of the A2A metadata envelope in every message, rather than as a first-class A2UI message. This ensures the server always has the client's latest capabilities without needing a separate handshake.

The a2uiClientCapabilities object in the metadata follows the [a2ui_client_capabilities_schema.json] schema.

Properties:

  • supportedCatalogIds (array of strings, required): URIs of supported catalogs.
  • inlineCatalogs: An array of inline catalog definitions provided directly by the client (useful for custom or ad-hoc components and functions).

error

This message is used to report a client-side error to the server.