A2UI Architecture Explained: Stream-Oriented UI Generation for LLM Agents

A2UI is a stream-oriented, declarative UI framework that enables LLM agents to generate native user interfaces by emitting JSON message streams processed through a reactive client-side pipeline.

The google/A2UI repository implements a message-driven architecture where agents construct interfaces incrementally via JSON-Lines streams. This Agent-to-UI (A2UI) architecture separates message generation from rendering, enabling transport-agnostic communication between LLM backends and frontend clients. The high-level concepts are documented in docs/concepts/overview.md.

End-to-End A2UI Architecture and Data Flow

The A2UI architecture follows a linear pipeline from agent to native UI:


Agent (LLM) → A2UI Generator → Transport (SSE / WebSocket / A2A) 
                                         ↓
Client (Stream Reader) → Message Parser → Renderer → Native UI

The A2UI Generator formats UI descriptions as self-contained JSON objects according to the A2UI schema. These messages travel through any transport layer—Server-Sent Events, WebSockets, the A2A protocol, or plain HTTP—before reaching the client-side Message Processor.

According to docs/concepts/data-flow.md, this streaming approach allows the client to render UI components progressively as the agent generates them, rather than waiting for complete page definitions.

Message Schema Evolution (v0.8 vs. v0.9)

The A2UI architecture supports two active message protocol versions with distinct lifecycle approaches:

Version Surface Creation Component Update Data Model Update Surface Deletion
v0.8 (stable) Implicit via surfaceUpdate/beginRendering surfaceUpdate dataModelUpdate deleteSurface
v0.9 (draft) Explicit createSurface (catalog required) updateComponents updateDataModel deleteSurface

In v0.9, surfaces require explicit initialization with a catalog reference:

{
  "version": "v0.9",
  "createSurface": { 
    "surfaceId": "main", 
    "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" 
  }
}

The full message specifications live in docs/reference/messages.md, which defines the JSON schema for all server-to-client communications.

Declarative Component Architecture

A2UI adopts an adjacency-list model where components exist in a flat array and reference children by string id. This architecture eliminates hierarchy constraints during streaming, allowing LLMs to generate components in any order while the client resolves relationships post-receipt.

As documented in docs/concepts/components.md, each component definition includes:

  • A unique id for referencing
  • A component type from the registered catalog
  • Optional children arrays or single child references
  • Style and property definitions

Example v0.9 component tree:

{
  "version": "v0.9",
  "updateComponents": {
    "surfaceId": "main",
    "components": [
      { "id": "root", "component": "Column", "children": ["header", "body"] },
      { "id": "header", "component": "Text", "text": "Welcome", "variant": "h1" },
      { "id": "body", "component": "Card", "child": "content" },
      { "id": "content", "component": "Text", "text": { "path": "/message" } }
    ]
  }
}

Data Binding and Reactive Updates

Components declare dynamic values via JSON Pointer paths (e.g., { "path": "/user/name" }). When the server emits updateDataModel (v0.9) or dataModelUpdate (v0.8) messages, the client updates only the specified data subtree, triggering reactive re-renders in bound components.

This binding mechanism, detailed in docs/concepts/data-binding.md, enables real-time UI updates without requiring complete component re-transmission.

Client-Side Processing Architecture

The browser-side architecture centers on the Message Processor, implemented in renderers/lit/src/0.8/data/signal-model-processor.ts. This processor consumes JSONL streams and maintains an in-memory model using reactive signals.

Key implementation details:

  • Uses signal-utils (array, map, object, set) for fine-grained reactivity
  • Exposes surfaces as reactive objects compatible with Lit's rendering engine
  • Supports incremental message processing via processMessages()

Implementation in a Lit component:

import { v0_8 } from "@a2ui/lit";

class MyA2UIWidget extends LitElement {
  #processor = v0_8.Data.createSignalA2uiMessageProcessor();

  async handleMessages(messages: v0_8.Types.ServerToClientMessage[]) {
    this.#processor.clearSurfaces();
    this.#processor.processMessages(messages);
  }

  render() {
    const surfaces = this.#processor.getSurfaces();
    return html`
      ${repeat(
        surfaces,
        ([surfaceId]) => surfaceId,
        ([, surface]) => html`<a2ui-surface .surface=${surface}></a2ui-surface>`
      )}
    `;
  }
}

The processor relies on the generic A2uiMessageProcessor class from @a2ui/web_core/data/model-processor, enabling renderer-agnostic message handling.

Handshake and Capability Negotiation

Before streaming begins, the A2UI architecture requires a capability handshake. The client transmits its supported component catalog to the agent, ensuring the LLM generates only compatible UI elements.

The handshake implementation in tools/editor/client.ts demonstrates this initialization:

await this.#send({
  clientUiCapabilities: {
    dynamicCatalog: catalog,
  },
});

After handshake completion, the client can send multipart requests containing images and instructions:

import { A2UIClient } from "./client.ts";

const client = new A2UIClient();
await client.ready;

const response = await client.sendMultipart(
  undefined,
  "Create a simple login form"
);

Extensibility and Renderer Abstraction

The A2UI architecture supports pluggable renderers through the @a2ui/web_core abstraction layer. Developers can implement React, Vue, or custom native renderers while reusing the same message processor core.

Extension points include:

  • Custom components: Advertised via the dynamic catalog in the initial handshake
  • Transport layers: Any stream-capable protocol can carry A2UI messages
  • Data processors: The signal-based processor can be replaced with alternative reactive implementations

The reference implementation in tools/editor/editor.ts and consumer samples like samples/personalized_learning/src/a2ui-renderer.ts demonstrate production usage patterns.

Summary

  • A2UI architecture employs a stream-oriented pipeline where LLM agents emit JSON-Lines messages that clients render incrementally
  • The framework supports two protocol versions (v0.8 stable and v0.9 draft) with explicit surface lifecycle management in the newer version
  • Components use a flat adjacency-list structure with ID references, optimized for LLM generation patterns
  • Data binding uses JSON Pointer paths for reactive updates without full component re-transmission
  • Client-side processing relies on signal-based reactivity in signal-model-processor.ts to transform messages into UI
  • Initial handshake exchanges capability catalogs to ensure component compatibility between agent and client
  • The renderer abstraction in @a2ui/web_core enables framework-agnostic implementations while maintaining consistent message semantics

Frequently Asked Questions

How does A2UI handle real-time streaming of UI components?

A2UI processes UI generation as a JSON-Lines stream, allowing the client to parse and render components incrementally as the LLM generates them. The signal-model-processor.ts implementation uses reactive signals to update the DOM immediately when new messages arrive, without waiting for the complete stream to finish. This architecture supports Server-Sent Events, WebSockets, or any transport capable of delivering ordered JSON objects.

What is the difference between v0.8 and v0.9 in A2UI's message architecture?

Version 0.8 creates surfaces implicitly through surfaceUpdate or beginRendering messages, while v0.9 requires explicit surface creation via createSurface with a mandatory catalog reference. Version 0.9 also renames key message types (updateComponents instead of surfaceUpdate, updateDataModel instead of dataModelUpdate) to better reflect their atomic operations. Both versions maintain the same flat component adjacency-list structure and JSON Pointer data binding.

How do LLM agents know which UI components the client supports?

Before generating UI, agents receive a dynamic catalog through the initial handshake protocol defined in client.ts. The client sends its clientUiCapabilities including available component types, which constrains the agent's generation to compatible elements. This capability negotiation ensures that generated JSON messages reference only components the client can actually render.

Can A2UI work with frontend frameworks other than Lit?

Yes. While the reference implementation uses Lit (@a2ui/lit), the renderer abstraction in @a2ui/web_core provides framework-agnostic message processing. The A2uiMessageProcessor class handles the core state management, allowing developers to implement React, Vue, or vanilla JavaScript renderers that consume the same surface objects and data bindings emitted by the processor.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →