a2ui://¶
Expose a Helix agent to a frontend via the Google A2UI (Agent-to-UI) protocol — a streaming JSONL protocol for delivering rich declarative UIs (cards, columns, rows, text, icons) progressively from an agent to a client renderer. The consumer stands up an HTTP endpoint that streams A2UI messages over SSE on GET and accepts client-to-server userAction events on POST.
Reach for a2ui:// when your agent should render structured UI elements (forms, cards, action buttons) rather than plain text. For free-text streaming to a frontend, agui:// is simpler.
Consumer-only — CreateProducer returns an error.
URI shape¶
<host:port>is the listen address. If port is missing,4200is used.<path>is the HTTP path the server serves (default/).
Parameters¶
| Param | Type | Default | Producer/Consumer/Both | Description |
|---|---|---|---|---|
(path) host |
string | — | Consumer | Host:port to listen on. |
(path) path |
string | / |
Consumer | HTTP path. |
name |
string | helix-a2ui |
Consumer | Server identity (informational). |
corsOrigin |
string | * |
Consumer | Access-Control-Allow-Origin value. Set to a specific origin in production. |
HTTP surface¶
GET <path>?message=...&surface=...¶
Opens an SSE stream of A2UI JSONL messages.
message— required — the user prompt passed as the exchange body.surface— optional — surface ID for component updates. Defaults tomain.X-Helix-Sessionheader (or?session=query) — optional — opt into session continuity. The server echoes the resolved session id back on the same header.
The server responds with:
Content-Type: text/event-stream
Cache-Control: no-cache
data: {"beginRendering":{...}}
data: {"updateComponents":{...}}
...
POST <path>¶
Accepts a JSON ClientMessage with a userAction event (button click, form submission). The consumer creates a new exchange with the event as the body and these headers:
| Header | Description |
|---|---|
HelixA2UIEventType |
The event's type field. |
HelixA2UISurfaceID |
Target surface ID. |
HelixA2UIComponentID |
Component that fired the event. |
Pipeline body becomes the JSON response. Request bodies are capped at 10 MB.
Per-call exchange shape (GET path)¶
| Header | Description |
|---|---|
HelixStream |
"true" — downstream agent producers use streaming mode. |
HelixA2UISurfaceID |
Surface ID requested by the client. |
The pipeline can return one of four body shapes:
| Body type | Effect |
|---|---|
[]a2ui.Message |
Sent verbatim as SSE data: lines. |
*a2ui.Surface |
The surface's Messages() are sent. |
*llm.StreamReader |
Progressive surface: beginRendering is sent first, then updateComponents is re-sent with the accumulating text each chunk (column → card → text-static). |
chan string / <-chan string |
Same progressive flow, driven by a channel. |
| anything else (string body) | Wrapped in a single column/card/text-static surface. |
Errors yield an icon+text "Error" surface via writeSSEError.
Examples¶
Wire an agent through A2UI:
agents:
- id: form-builder
type: react
model: { provider: openai, model: gpt-4o }
workflows:
- id: a2ui
trigger:
uri: a2ui://0.0.0.0:4200/surface?corsOrigin=https://app.example.com
steps:
- target: agent://form-builder
Client calls:
…and renders the streamed JSONL messages with the A2UI JS/TS SDK.
Custom UI surface from a pipeline step (return a *a2ui.Surface):
workflows:
- id: dashboard
trigger: { uri: a2ui://0.0.0.0:4200/dashboard }
steps:
- transform:
starlang: |
%sl 2.0
output application/x.a2ui-surface
input payload
---
# build a surface in Starlang (helper builtin)
buildA2UISurface(payload)
User-action handler (POST):
workflows:
- id: a2ui-actions
trigger: { uri: a2ui://0.0.0.0:4201/actions }
steps:
- choice:
- when: header("HelixA2UIEventType") == "submit"
steps:
- target: direct://submit-handler
- otherwise:
- log: { message: "ignoring unknown a2ui event" }
Behavior notes¶
- Two HTTP methods on one path —
GETopens the SSE stream;POSThandlesuserAction.OPTIONSreturns CORS preflight (204). - Server-sent events only — no chunked HTTP fallback; the server requires
http.Flusher. - Session persistence — when the engine has a session store configured, state in
helix.session.stateround-trips between calls under the resolved session id. - Stream buffering —
X-Accel-Buffering: nois set so reverse proxies (nginx) do not buffer SSE chunks.
Source¶
pkg/component/a2ui/server.go
See also¶
- agui — simpler CopilotKit-flavored protocol (plain-text streaming, no rich UI).
- agent — the underlying agent runtime.
- mcp-server, a2a-server — sibling protocol servers.