The contract is the product
The popular mental model of generative UI is a code generator with taste: you ask an AI for an interface, it writes React, the React runs. Call it “AI writes React.” It is an appealing model because every piece of it already exists. Models write competent component code today. If that were the whole problem, generative UI would have shipped the day the first coding model did.
It did not ship that day, because the code was never the hard part. The hard part is everything around the code: how an agent that has no DOM, no event loop, and no idea what a browser is can put an interface in front of a person, learn what that person did, and act on it. “AI writes React” has no answer for this. A generated component with no wire back to the agent is a screenshot with extra steps.
So here is the model this publication exists to argue for: generative UI is an agent describing a surface, and a host rendering that surface under a contract. The host is the application the person is already sitting in, such as a chat client or an IDE, which mounts the generated component in a sandboxed iframe. The interesting object is not the component code. It is the description — and the machinery that holds both sides to it.
What actually crosses the wire
Watch what happens in ggui, the open generative-UI protocol this publication is built around, when an agent shows a user an interface. The agent never emits JSX. It negotiates a contract: a declaration of what data the surface starts with, what deliveries it accepts while live, what the user can do to it, and what state the agent is allowed to observe. Four flat maps, keyed by name:
| Spec | Direction | Answers |
|---|---|---|
propsSpec |
agent → client | what data the UI starts with |
streamSpec |
agent → client | what real-time deliveries it accepts |
actionSpec |
client → agent | what user interactions it emits |
contextSpec |
client → agent | what observable client state the LLM sees |
Here is what one declared interaction looks like on the wire, from a todo-list contract:
"actionSpec": {
"addTodo": {
"schema": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
},
"nextStep": "todo.add" // advisory: the tool the agent will likely call
}
}
When the user clicks the button this action backs, the click travels as
an envelope over the session’s live channel. The server validates the
payload against actionSpec.addTodo.schema, appends the event to the
session’s consume buffer, and only then acknowledges it back to the
surface with its sequence number. The agent drains it with one tool
call. The names are load-bearing: they appear in every envelope, and
they are how producer and consumer agree on which declared slot a
delivery targets. Every spec is optional, and absence means the
component does not participate on that plane.
The component code exists, of course. Something has to run in the
iframe. But notice its position in the system: it is derived output,
generated to satisfy the contract, cached and reused whenever a new
render’s contract and design variance both match a stored pair. In
ggui’s render response the stable identifier is the blueprintId,
equal across renders that reused the same component. The code is
fungible; the contract is the load-bearing axis of its identity. If you
want to know what a generative UI system really is, ask what it caches
by.
The agent holds no rendering code either
The same inversion holds on the agent side. A ggui agent is a runtime,
an MCP configuration block, and a system prompt describing its role.
That is the complete list. No SDK import, no event handler, no polling
loop written by the agent’s author: a render whose contract declares
actions returns a nextStep naming ggui_consume, a single blocking
call that returns when the user acts. The loop lives in the runtime;
the agent just follows the tool’s own instruction. The event that comes
back carries actionData (what the user did) and uiContext (the
contract’s context slots at the moment they did it). The agent reasons
about that, in the names it agreed to in the contract, never about DOM
nodes.
A confession belongs here: an official @ggui-ai/mcp-client package
existed once. It was retired on 2026-05-19 because shipping a
brand-specific client library violated the principle it was meant to
serve. The protocol’s only client contract is MCP itself. Any
MCP-compatible runtime gets the same interface by pointing at the same
endpoint. When your interop story requires your SDK, you do not have an
interop story; you have a product with a long setup guide.
What keeps the contract small
A contract like this survives only if it refuses to absorb things that do not belong on it. The test ggui applies to every candidate field is one question: can the agent observe this from the wire alone? If yes, it is data, and it belongs on one of the four specs. If no, it is behavior, and it lives in the generated component code: scroll position, focus rules, debounce cadence, toasts, animations, clipboard writes. The agent knows the list changed; the component decides to scroll the new item into view. The agent receives the submission; the component decides which invalid field to focus.
This split is what keeps the description renderer-neutral. A React renderer, a native one, and a hand-written harness can read the same contract and choose their own behaviors. The moment a scroll command or a toast field leaks onto the wire, every renderer must honor it, and the agent is suddenly dictating presentation mechanics it cannot even observe. If the agent must know a behavior happened — say, the user pressed copy — the contract carries the signal as a declared action, and the clipboard write stays in the component.
“Contract” is a load-bearing word
Any system can print the word contract in its docs. The reason to take it seriously is enforcement, and this is where most generative UI framings go quiet. ggui’s internal bar for the word requires four things of every boundary that claims it: named parties, explicit per-party obligations, a defined failure mode per obligation, and violations an operator can see without a debugger. Three of four is not a contract. A pile of MUST strings in docstrings is not a contract.
Concretely, on the render path:
- The props key set is closed at every level. A render whose props
carry a key the contract never declared, an extra
idor a renamed field, fails with a typedcontract_violationthe agent receives on the call itself and can fix by correcting the values and retrying the same call. The contract stands. - Deliveries to undeclared stream channels are rejected by the server’s validators, not silently dropped.
- An action schema that is not a subset of its target tool’s registered
input schema fails the render as a
schema_mismatch_errorbefore any user ever sees the surface. - The server owes the agent delivery. A user’s action is validated, appended to the session’s consume buffer, and only then acknowledged back to the surface — a gesture cannot silently vanish between the click and the agent’s next turn.
Every one of those is an obligation with a named failure you can observe from outside. That is the difference between a wire format and a contract: the wire format tells you what shapes exist; the contract tells you who owes what to whom, and what happens, visibly, when they don’t pay.
Why this is the product
Step back from ggui and the claim generalizes. A generative UI system built on “AI writes React” is a feature: it lives inside one vendor’s renderer, its outputs are as trustworthy as this week’s model, and no third party can implement the other side of it, because there is no other side. A generative UI system built on a described surface under an enforced contract is a different kind of object. It can be specified and conformance-tested. It can be implemented twice, by parties who have never spoken — and where the implementations disagree, a test kit adjudicates. This is also why MCP Apps matters more than any single renderer: it standardizes how a host mounts an agent-delivered surface, which is what lets a contract like this travel across MCP-Apps-compliant hosts instead of living inside one vendor’s chat window.
There is an economic reading too. Models will keep getting better at writing components, and every improvement makes the generated code cheaper and more disposable. The contract is the only part of the system that appreciates: every surface rendered against it, every host that mounts it, every blueprint cached under it makes the description worth more. You do not build a business on the part that depreciates.
That is also the honest yardstick for reading anything in this space, ggui included. When you evaluate a generative UI system, skip the demo and ask three questions. Where is the contract written down? Who are the parties, and what does each one owe? And when an obligation breaks, what do you see? If the answers are “the prompt,” “it’s flexible,” and “the model usually handles it” — you are looking at a code generator with taste. The taste may be excellent. It is still not the product.