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
idfor referencing - A
componenttype from the registered catalog - Optional
childrenarrays or singlechildreferences - 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.tsto transform messages into UI - Initial handshake exchanges capability catalogs to ensure component compatibility between agent and client
- The renderer abstraction in
@a2ui/web_coreenables 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →