a2ui· @zero/a2ui · 16 kinds · 1 registry · 1 reducer
an agent composes the brand from a flat list
A2UI is the streaming-UI sibling of A2A: the agent emits a flat array of typed nodes (each with an id and a children-by-id list). the renderer reconstructs the tree, so the agent can append nodes mid-stream without re-sending the entire surface. ZERO restricts the component vocabulary to 16 kinds — every kind maps to a primitive in @zero/ui. there is no "anything goes," there is no inline style, and there is no HTML escape hatch.
as of
00 · livePOST /api/a2ui-stream · paced deltas · primitives reduced into a treewatch the agent compose a surface in front of you
as of
01 · vocabulary16 kinds · closed by constructionevery kind maps to a brand primitive
| group | kind | maps to |
|---|---|---|
| layout | stack | vertical flex column |
row | horizontal flex row | |
card | @zero/ui · Card | |
sectionTitle | brand-voice h2 | |
| content | text | plain text node |
mark | @zero/ui · Mark | |
chip | @zero/ui · Chip | |
hash | @zero/ui · Hash | |
receipt | @zero/ui · Receipt | |
statCell | @zero/ui · StatCell | |
| action | button | @zero/ui · Button |
approvalGate | @zero/ui · ApprovalGate | |
toolCallCard | @zero/ui · ToolCallCard | |
asyncTask | @zero/ui · AsyncTask | |
planSurface | @zero/ui · PlanSurface | |
streamingCursor | @zero/ui · StreamingCursor |
as of
02 · stream8 deltas · build the tree step by stepeach delta is a single, idempotent operation
| i | op | id / parent | kind | tree size after |
|---|---|---|---|---|
| 01 | append | header · parent root | sectionTitle | 2 nodes |
| 02 | append | row-stats · parent root | row | 3 nodes |
| 03 | append | s-pnl · parent row-stats | statCell | 4 nodes |
| 04 | append | s-ref · parent row-stats | statCell | 5 nodes |
| 05 | append | card-1 · parent root | card | 6 nodes |
| 06 | append | r-trade · parent card-1 | receipt | 7 nodes |
| 07 | append | cta · parent root | button | 8 nodes |
| 08 | append | sig · parent root | hash | 9 nodes |
upsert semantics
appending a node whose id already exists upserts the node (it replaces in place, no duplication). this lets the agent incrementally refine a node's props as new information arrives, without rewriting the whole subtree.as of
03 · registrythe kind-to-primitive registry · single source of truthadding a kind requires three changes, in order
| step | file | change |
|---|---|---|
| 01 | packages/a2ui/src/types.ts | add the kind to A2UiKind union and A2UI_KINDS array |
| 02 | packages/a2ui/src/registry.tsx | add a renderer in REGISTRY · must map to a brand primitive |
| 03 | governance/adr/NNN-*.md | open an ADR · bump the protocol schema if the change is not additive |
| 04 | packages/a2ui/src/__tests__/a2ui.test.tsx | add a render test for the new kind · CI gate green |
as of
04 · covenantclosed vocabulary · no escape hatches · brand by constructionthe brand cannot leak through the renderer
why no html escape hatch
an "html" kind would let any agent render any markup, which would let any agent ship arbitrary CSS, which would let any agent break the covenant in 50 ways. the closed registry keeps the contract enforceable: every primitive that lands on the page is one of the 16 brand-validated kinds, with the same accessibility, motion, and spacing contracts as anything elsewhere on the surface.| posture | value |
|---|---|
| raw html | refused |
| inline style | refused |
| arbitrary class | refused |
| third-party renderer | refused |
| agent-supplied script | refused |
| kind in registry | required |
| props validated by primitive | required |
closed vocabularybrand-safe
◆ a2u001a