Caio Theodoro Caio Theodoro
← Blog · Feb 2026 · 11 min read

The Wire Format Problem in Generative UI

The wire format for model-generated UI is not a detail — it decides parsing reliability, streaming latency, and token cost, and most teams pick one by accident.

UX ML

Ask for a Q4 revenue dashboard and the model generates the full component tree. Say "update the revenue figure to $1.5M" and it generates the full tree again, at approximately the same size and approximately the same interface, one string different. A multi-panel analytics dashboard might be 150 output tokens, so every follow-up that reprints the tree spends 150 tokens to change one. For conversational UI, where you iterate, that is an order of magnitude over what the interaction logically requires. A mutation protocol would fix it, and it is the largest unsolved problem in the current architecture. I will get to it. First the format, because that is what makes the reprint so expensive.

Where text loses the structure

The model already knows what a chart looks like. Axis labels go on x and y, a bar chart is an array of category-value pairs, a table has typed columns and valued rows. It has read enough of the web to absorb the structural vocabulary of interfaces. The hole is the boundary between model and application, which is still text.

Standard chat output is a markdown description of the thing you asked for. "Here is a summary of your Q4 performance:" and then a paragraph, or a markdown table if the model is feeling generous. The application gets a string and either renders the markdown as-is, limited to what markdown can represent, or parses it with some combination of regex and hope.

Ask an analytics tool to "show me last quarter's revenue breakdown by region" and the useful answer is a chart, possibly a table, probably some callouts. A paragraph saying revenue was distributed across four regions, the Northeast at 34% of the total and the West at 28%, holds the same facts. You still cannot click, filter or hover anything in it. The model did the hard work of understanding the data and picking a visualization that surfaces the comparisons that matter, then serialized all of it into a format the renderer cannot consume, and the application reverses that imperfectly.

People tolerated this because reliable structured UI was hard to get. You could prompt for JSON, the failure rate was high, and production pipelines grew a lot of defensive code around it. Structure was a formatting preference in the prompt, and preferences are not reliably honored.

The registry as the contract

OpenUI is one DSL among others. What matters in it is a substitution: the component library becomes the contract between application and model, instead of a stylesheet that sits after the model. Define a StatCard with a typed schema — title is a string, value is a string, trend is an enum of up, down or flat — and you have also defined what the model may emit when it wants a stat card.

Structured outputs made the same move for LLM pipelines, putting the trust boundary at inference. The model cannot hallucinate a component that is not in the registry, because the parser validates against it, and an invalid prop is rejected before the renderer sees it. A StatCard with a missing value fails at the parser, not at render time in front of a user.

The system prompt changes meaning too. It becomes a specification of the available components, generated from the registry, and the model's output is an instance of that schema, which the renderer then runs.

Constrained decoding did for LLM pipelines what the registry does here. Before it, a 3% parse failure rate in a five-step pipeline meant a 14% end-to-end failure rate (arithmetic on a named 3%, not a production incident log), and the code around the model calls grew larger than the calls themselves, with retry logic and fallback handlers piled around output normalization. Once the output is guaranteed at the inference layer, parse failures as a category cease to exist, and what remains is semantic — wrong values, poor reasoning, the kind of error evaluation and better prompting can address. The renderer receives a typed element tree instead of a string that might be JSON or a prose description of it, so generated components compose with the rest of the application without each consumer defensively handling malformed input.

Tokens

Before purpose-built DSLs the usual wire format was JSON. A dashboard with three stat cards, a bar chart and a data table is verbose in a specific way: field names, nesting, quotation marks and structural tokens repeat at every level. One stat card is {"type": "StatCard", "title": "Revenue", "value": "$1.2M", "trend": "up"}, and that is before the container, the array it sits in, the parent Grid and the key-value syntax around every field name.

A line-oriented, positional-argument syntax is much smaller. The same card is s1 = SC("Revenue", "$1.2M", "up"). Arguments map to props by position in schema order, identifiers are defined on assignment and can be referenced before they exist, and every line parses independently, so the renderer can start before the model finishes. OpenUI Lang is one encoding that does this. Other compact DSLs can, if they keep the constraint and the line discipline.

A format using 67% fewer tokens than JSON for the same UI amounts to a 2x claim on throughput, time to first render and per-query cost. That figure and the 150-to-4 one below are design arithmetic, with no production A/B behind them.

Consider a model with a 4096-token output budget that spends 60% of it on JSON structure. Roughly 1600 tokens remain for UI content. With a compact format it has 3500. That gap decides whether a multi-panel dashboard fits or the model has to truncate.

Default elision trims it further. Props at their default value are not emitted. The schema defines defaults for every optional prop, the model is instructed to omit any prop that matches its default, and the parser recovers trend, variant and size from those defaults. So a stat card with the default variant, default size and a flat trend carries two arguments, title and value. Protobuf does this at the binary level and HTML and CSS do it for optional attributes and inherited properties, so in a token-priced output format it is a cost rule. Add short aliases for frequent components, learned from the system prompt, and a grid of three stat cards is four lines. The model ends up specifying the interface in the most compact notation that preserves full fidelity.

Streaming and skeletons

The naive pipeline generates the whole UI description, parses it, then renders: a spinner, then the finished interface. That is strictly worse than a text response, where tokens stream in. Streaming feels responsive at equal total latency, and a blank-to-finished transition feels slow even when it takes fewer milliseconds.

JSON stands in the way. A partial JSON object is not valid JSON, so you buffer the whole response or write an incremental parser, which is non-trivial. Line-oriented formats with a top-down tree are streaming-compatible by construction — each completed line names a component and its props, and the renderer instantiates it on the spot. Forward references let the model declare the root layout first, and if it is told to generate the most visually prominent components first, the most important content appears earliest. A streaming renderer that shows above-the-fold content within the first 10% of the token budget feels faster than one that shows nothing until generation is done, which makes component order in the system prompt a UX performance instruction.

Each component in the registry can also define a skeleton, a placeholder that occupies the right layout space without real values. If the model opens the stream with a first-line declaration like INTENT dashboard 3-stats chart, the renderer instantiates skeletons for every named component before a single real value exists. The layout shows within the first few tokens, and definitions replace skeletons as they arrive, the way progressive image loading shows shape before pixels. A blank screen that fills in at 200 tokens/s feels worse than a structured loading state populating at the same rate. Users report that interfaces that build themselves feel faster than ones that appear all at once, even at identical or slightly higher latency.

Caching the schema

Text-only system prompts are mostly instructions: behavior, things to avoid, persona. Generative UI prompts are mostly schema, a substantial document that rarely changes. Anthropic and OpenAI both cache the prefix of the system prompt, so if the first N tokens match an earlier request they come out of cache at a fraction of normal input cost.

The prompt falls into three layers. The syntax specification (grammar, positional-argument rules, streaming guidelines) is entirely static. The component library for the active shard changes when the library does and is stable within a session. Per-request context, meaning conversation history, preferences and any data to thread in, is dynamic and uncached. The first two typically make up 80-90% of the prompt's token count, so effective input cost per turn converges on the dynamic layer alone. At tens of thousands of interactions a day that is a material reduction.

Sharding adds more. A library of 45 components does not need all 45 definitions per request — someone asking about data visualization needs no form components, and someone filling out a form needs no charts. A lightweight classifier on the incoming message routes to a component shard, and the active prompt shrinks by 60-70% for well-classified requests. The shard prompt can itself be cached once per session per shard. Together, caching and sharding can bring input cost down to a few percent of the naive implementation.

Patches

Back to the 150 tokens. The proposal is that after the first render the client holds the component tree in memory and later turns emit targeted patches: PATCH s1.value "$1.5M". The parser would apply the patch to the live tree, only the affected components would re-render, and output would drop from 150 tokens to 4. This protocol is the most significant missing piece in the current architecture.

The model has to see the current tree to patch it. One option is to serialize the tree back into context every turn, which adds input tokens and is simple. The other is to send a screenshot of the rendered interface as vision input, which is more token-efficient for complex trees and gives the model an accurate picture of what you see. Told in natural language what changed, the model looks at the image and emits a minimal patch for the relevant component. Tree serialization overhead goes to zero.

At 4 tokens a follow-up, filtering, sorting, drilling down and toggling views, which currently trigger full regeneration, become streaming interactions. You wait for a few tokens instead of a full tree, which is the difference between a UI that updates on submit and one that updates on change. Without the protocol, conversational UI is expensive in a way that limits how much iteration the system can support.

What the registry does not give you

The registry guarantees structural validity and says nothing about whether the model chose well. A model can pick the wrong visualization, fill a chart with values that do not support the claim, or lay out an answer to a different question, and the output is valid, renders correctly, and misleads. Better prompts, context and models help, and so does evaluation of whether the generated interface is useful.

The library caps quality as well. If its components are too coarse, too fine-grained, semantically overlapping or inconsistently specified, output stays valid and the interface stays poor, because the model can only choose from what it has and will use whatever fits imperfectly. Very large trees strain the model's coherence and the parser's handling of forward references and data slot bindings. What can be generated in one pass is less than the DSL can express. For very complex interfaces the answer is probably decomposition into smaller generation tasks composed at the application layer.

Streaming renderers already match the latency profile of text-based systems, and with prompt caching the economics are viable at the output volumes that matter. Component design is the other part still open: how to define a registry expressive enough for useful interfaces and constrained enough that the model reliably picks the right component. Too large a library produces uncertainty and poor selection, too narrow a one valid but limited interfaces, and the evaluation criteria for choosing between them are not yet well established. If you ship generative UI before the mutation protocol exists, every follow-up is a full-tree reprint, so measure tokens spent on structure versus UI and price the 150-token second turn.