A2UI Protocol: How Agents Stream Layout Data Instead of HTML to Native UI Components
DEV Community

A2UI Protocol: How Agents Stream Layout Data Instead of HTML to Native UI Components

Most agent UIs stream markdown or generate HTML strings. A2UI streams layout data that native framework adapters translate into platform components.

The agent emits structured events describing buttons, cards, and forms. The client renders them using React, Flutter, SwiftUI, or Jetpack Compose without a WebView or eval(). This separation creates strict capability boundaries.

The agent cannot inject arbitrary code. It composes from a fixed catalog of components defined by the application. The protocol is event-based and optimized for low-latency streaming, making it viable for real-time agent interactions across web and mobile platforms.

Why Layout Data Instead of Code

Generative UI typically means an LLM writes JSX or HTML, then the client renders it. This works for demos but creates three production problems:

  • Security boundary : Executing agent-generated code requires sandboxing or trusted execution contexts.
  • Cross-platform cost : An agent that generates React code cannot target Flutter or SwiftUI without retraining or prompt engineering.
  • Visual consistency : Generated code may not respect design systems, accessibility rules, or platform conventions.

A2UI solves this by treating UI as data. The agent outputs a schema-validated stream of layout primitives. The client maps those primitives to native components. The agent never sees platform-specific rendering logic. The client never executes agent-generated code.

This is the same separation used in server-driven UI frameworks like Litho or Epoxy, but extended to agent-generated content.

Protocol Shape

A2UI uses an event-based protocol. The agent emits a stream of JSON events. Each event describes a UI operation: adding a component, updating state, or signaling completion.

Core Event Types

Event Type Purpose Payload
component_add Append a component to the layout tree Component type, props, parent ID
component_update Modify existing component props Component ID, updated props
state_sync Synchronize application state State key, value
stream_end Signal completion of the UI stream Optional metadata

The agent sends these events over HTTP streaming, WebSocket, or server-sent events. The client maintains a layout tree and applies events incrementally.

Example Wire Format

JSONL format (one JSON object per line):

{"type": "component_add", "id": "card-1", "component": "Card", "props": {"title": "Flight Options", "variant": "elevated"}, "parent": "root"}
{"type": "component_add", "id": "button-1", "component": "Button", "props": {"label": "Book Now", "action": "submit_booking"}, "parent": "card-1"}
{"type": "stream_end"}

The client receives these events, validates them against the component catalog schema, and renders them using the native framework adapter.

Component Catalog and Capability Boundaries

The application defines a component catalog. This is a schema that lists available components and their props. The agent can only compose from this catalog. If the agent references an undefined component or passes invalid props, the client rejects the event.

This enforces capability boundaries at the protocol level. The agent cannot:

  • Render arbitrary HTML or execute JavaScript
  • Access components outside the catalog
  • Bypass validation rules defined in the schema

The catalog is versioned. Agents and clients negotiate protocol versions during handshake. This allows safe evolution of the component library without breaking existing agents.

Sample Catalog Schema

const catalog = { components : { Card : { props : { title : { type : 'string' , required : true } , variant : { type : 'enum' , values : [ 'flat' , 'elevated' ] } } } , Button : { props : { label : { type : 'string' , required : true } , action : { type : 'string' , required : true } , disabled : { type : 'boolean' , default : false } } } } } ;

The agent SDK validates outgoing events against this schema before transmission. The client validates incoming events before rendering. Both sides reject invalid data.

Framework Adapters

A2UI provides native adapters for React, Angular, Lit, Flutter, SwiftUI, and Jetpack Compose. Each adapter translates the protocol's layout data into platform-specific components.

React Adapter Flow

  • Event ingestion : The adapter subscribes to the event stream.
  • Tree construction : It builds a virtual component tree from component_add and component_update events.
  • Reconciliation : React's reconciler diffs the tree and updates the DOM.
  • Event handling : User interactions trigger callbacks that send events back to the agent.

The adapter maintains a mapping from A2UI component names to React components. When the agent emits a Button component, the adapter renders the application's <Button> component with the provided props.

Flutter Adapter Flow

Flutter's adapter works similarly but uses a StreamBuilder to rebuild the widget tree on each event. The adapter maintains a WidgetTree model that mirrors the A2UI layout tree. Each event triggers a rebuild of the affected subtree.

Type Safety

TypeScript adapters use generated types from the catalog schema. This provides compile-time validation of component props. If the catalog defines Button.label as a string, the TypeScript adapter enforces that type when rendering.

Flutter and Swift adapters use code generation to produce strongly typed widget builders from the catalog schema.

Streaming and Latency

The protocol is optimized for low-latency streaming. Agents can emit events as soon as they decide on a component, without waiting for the full layout to be computed. This enables progressive rendering.

For example, an agent booking a flight might:

  1. Emit a loading spinner immediately
  2. Stream flight options as they are retrieved from the search API
  3. Emit a booking form once the user selects a flight
  4. Update the form with validation errors in real time

Each event is independent. The client applies them incrementally. The user sees the UI build up in real time, rather than waiting for a full page load.

Backpressure Handling

The protocol supports backpressure. If the client cannot keep up with the event stream, it signals the agent to slow down. This prevents memory exhaustion on mobile devices or slow networks.

The agent SDK exposes a canSend() method that checks the client's readiness before emitting the next event.

State Management

A2UI separates layout state from application state. Layout state is the component tree. Application state is the data the agent and client share (user selections, form inputs, API responses).

The state_sync event synchronizes application state. When the user selects a flight, the client sends a state_sync event to the agent. The agent updates its internal state and may emit new layout events in response.

This separation allows the agent to reason about state changes without re-rendering the entire UI. Only the affected components update.

Integration with LangGraph and MCP

A2UI integrates with LangGraph for multi-step agent workflows. A LangGraph agent can emit A2UI events at each workflow step, building up the UI as the workflow progresses.

For example, a customer support agent might:

  1. Emit a search form (initial state)
  2. Stream search results (after user submits query)
  3. Emit a detail view (after user selects a result)
  4. Stream resolution options (after analyzing the issue)

Each step emits A2UI events. The client renders them incrementally. The user sees a cohesive flow without page transitions.

MCP (Model Context Protocol) integration allows A2UI agents to call external tools and render the results. An agent might call a weather API via MCP, then emit a WeatherCard component with the response data.

Observability and Debugging

The protocol is self-describing. Each event includes metadata: timestamp, agent ID, session ID. Clients can log the event stream for replay and debugging.

The A2UI Composer tool ( https://a2ui-project.github.io/composer/ ) allows developers to:

  • Manually construct event streams
  • Preview how different frameworks render the same events
  • Validate catalog schemas
  • Test error handling and backpressure

This is critical for debugging agent behavior. Instead of inspecting generated code, you inspect the event stream. You can replay a session, modify events, and see how the UI changes.

Failure Modes

Failure Cause Mitigation
Invalid component reference Agent emits unknown component name Client rejects event, logs error, agent retries with fallback
Schema validation failure Agent sends props that violate catalog schema Client rejects event, agent receives error response, adjusts output
Stream interruption Network failure or agent crash Client displays last known good state, retries connection
Backpressure overflow Agent emits events faster than client can render Client buffers events, signals backpressure, agent slows down
Version mismatch Agent uses newer catalog than client supports Handshake fails, client requests compatible version or rejects connection

The protocol is designed for graceful degradation. If the agent emits an invalid event, the client rejects it but continues processing subsequent events. The UI remains functional even if parts of the layout fail to render.

Deployment Considerations

A2UI agents typically run server-side. The client connects via HTTP streaming or WebSocket. This allows:

  • Centralized agent logic : No need to deploy agent models to mobile devices.
  • Dynamic catalog updates : The server can update the component catalog without client updates.
  • Audit and compliance : All agent interactions are logged server-side.

For offline scenarios, the agent can run on-device using a lightweight model. The catalog is bundled with the app. The protocol remains the same, but the transport is in-process rather than over the network.

Scaling

Each client session maintains a persistent connection to the agent. This requires connection pooling and load balancing. WebSocket connections are stateful, so sticky sessions or session affinity is required.

For high-scale deployments, consider:

  • Connection multiplexing : Use HTTP/2 or HTTP/3 to reduce connection overhead.
  • Event batching : Group multiple events into a single transmission to reduce round trips.
  • Edge deployment : Run agents at the edge to reduce latency for geographically distributed users.

Technical Verdict

Use A2UI when you need strict capability boundaries for agent-generated UIs, or when the same agent must target multiple platforms (web, iOS, Android). The protocol's separation of layout data from rendering logic makes it safer than code generation and more portable than framework-specific solutions.

Avoid A2UI if your UI is static or if you already have a working server-driven UI framework. The protocol adds complexity: you must define and version a component catalog, implement framework adapters, and manage event streaming. For simple chat interfaces or markdown-based agents, the overhead is not justified.

A2UI shines when agents compose complex, multi-step UIs that must render natively across platforms. The 17,000+ stars and production adapters for six frameworks suggest real adoption beyond experimentation.

Source Links

  • A2UI GitHub Repository
  • A2UI Protocol Specification
  • A2UI Composer (Interactive Demo)
  • Supported Renderers
Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.