---
title: UI Generator
description: The bounded harness that turns a data contract into a compiled React component — workflow, check, derive, repeat.
---

The UI generator runs when a [blueprint match](/architecture/overview/#generation-pipeline) misses and ggui has to build a component from scratch. It takes a **data contract** (`PropsSpec` + `ActionSpec` + `StreamSpec` + `ContextSpec`) and returns a compiled, contract-typed React module — typically a handful of LLM turns; cache hits via blueprints are what deliver the ~100ms path.

## The harness

A **harness** is a per-contract execution plan derived from the contract's risk and axes. It bundles three things:

- **Workflow** — the topology of LLM phases and tasks. Today the dispatcher always picks `single_pass`; `staged` and `staged_concurrent` are registered as reserved-future topologies for risk-tier routing that has not yet shipped.
- **Prompt + boilerplate** — the system prompt and fragment set that tell the LLM _how_ to author against `@ggui-ai/design`, the active [gadgets](/glossary/#gadget-renderer-side-capability), and the contract.
- **Check leg** — the post-conditions the output must satisfy.

Generation runs the workflow to produce source, runs the check leg, and — if checks fail — derives a revised harness and re-runs, bounded by `maxIterations`.

```
workflow → source → check → pass? ─► return
                       │
                       └─ fail → derive (swap fragments / upgrade workflow / adjust prompt) → loop
```

## Workflows

| Workflow            | Status                              | Shape                                       |
| ------------------- | ----------------------------------- | ------------------------------------------- |
| `single_pass`       | **Live** — only dispatched workflow | One impl turn.                              |
| `staged`            | Reserved (registered, not routed)   | Plan → execute.                             |
| `staged_concurrent` | Reserved (registered, not routed)   | Plan → parallel skeleton tasks → integrate. |

`pickWorkflow` is deliberately conservative — every classification today routes to `single_pass`. The staged topologies are wired up so a future risk-tier router can dispatch to them without a schema change, but changing the picker is a first-class experiment with its own bench gate. All three feed the same check + derive loop; the workflow only changes how source is produced, not how it's validated.

## Plain-text impl loop (no tool-call ceremony)

The impl phase is structured so the LLM doesn't waste turns on deterministic ceremony:

1. The LLM receives **everything pre-injected** — primitives docs, design tokens, the data contract with examples, gadget capability cards.
2. The LLM writes the component as **plain text**. No tool calls required.
3. The system **auto-runs** `self_check` and `compile_component` on the emitted source.
4. Failures are fed back as a structured diff for the next turn to fix.

This is what keeps healthy generations to 3–5 turns. Turns ≥ 6 is a triad-misalignment signal, not a turn-budget problem.

## Check leg

Every workflow runs the same checks before returning:

- **TypeScript** — the emitted source is compiled against the real `@ggui-ai/design` type definitions via the TypeScript compiler API on a virtual filesystem. Catches wrong prop types, missing required props, invalid imports, and `strictNullChecks` violations.
- **Render smoke** — `ReactDOMServer.renderToString()` with contract-derived sample props. Catches `undefined.toLowerCase()`-class runtime errors before the component reaches the renderer.
- **Per-axis assertions** — axis-specific checks derived from the contract (e.g., does an `interactive` axis include a focusable element? does a `submit` action have a matching form?).

If any leg fails, the harness derives a revised configuration and loops. If the final iteration still fails, generation returns `ok: false` with `reason: "max-iterations"` and the last source, compiled output, and check result attached for diagnostics — the caller decides whether to surface a fallback, retry with a different harness, or report the failure.

## Multi-provider transport

The harness is provider-agnostic. Only the LLM transport differs:

- **Claude** (Anthropic) — raw API or Claude Agent SDK.
- **OpenAI** — Responses API or OpenAI Agents SDK.
- **Google** (Gemini) — GenAI API or Google ADK.

Workflow, check, and derive are identical across providers. Switching providers does not change what gets generated, only the per-turn cost and latency profile.

A deployment pins its model in `ggui.json#generation.model` (`provider:model`, e.g. `anthropic:claude-haiku-4-5-20251001`); the `GGUI_GENERATION_MODEL` env var overrides the manifest (precedence: `GGUI_GENERATION_MODEL` env > `ggui.json#generation.model` > per-provider default), and an agent may override per render via `ggui_render({infra: {model}})`. `generation.keySource: 'own' | 'managed'` declares whose provider key funds generation — self-hosted deployments always use their own key.

## Output shape

A successful generation returns:

- A **compiled component module** (TSX source compiled to JS).
- A **typed data contract** — the same `PropsSpec` / `ActionSpec` / `StreamSpec` / `ContextSpec` that was the input, now frozen as the runtime wire shape.
- A **blueprint candidate** — the contract (hashed via RFC 8785 to `contractHash`) plus its variance (`variantKey`), which the registry stores under a stable `blueprintId` for the next handshake's cache match.

The contract is enforced again at the MCP handler boundary so that what the agent renders matches what the component was generated to render.

→ See [Architecture overview](/architecture/overview/) for where the generator fits in the render pipeline, and the [Glossary](/glossary/) for `harness`, `blueprint`, `gadget`, `contract`.