Skip to content
Sneak peek — you found hosted ggui early · official launch soon

Gadgets — deterministic islands

read as .md

A generated interface is LLM-written React. That works for layout, forms, and state — but the moment a UI needs a real map, a checkout flow, or the camera, “generate it” stops being an option. Nobody wants an LLM improvising a payments integration, and no model reliably reinvents Leaflet’s lifecycle quirks on demand.

Gadgets are how ggui resolves that tension: the LLM composes third-party capability but never authors it. A gadget is a human-written, versioned, integrity-pinned wrapper around a browser library, exposed to generated code as a React hook with one uniform contract. The generated component calls useLeafletMap(...) the same way it calls useState — everything inside that hook is deterministic, reviewed code.

Every third-party library enters the system through the same factory (createGguiGadget), so every gadget presents the same hook shape: call it with options, get back { value, status, start, … }. The generator never sees raw library APIs — not Leaflet’s imperative L.map() lifecycle, not Stripe’s redirect dance. It sees one hook convention, which means:

  • Cold generations get it right more often. The model pattern-matches against one shape it has seen many times, not N library idioms it has seen unevenly.
  • A new gadget costs the model nothing to learn. The wrapper’s teaching text — description (what), usage (when), example (a concrete call) — travels with the descriptor, and the generator reads it at synthesis time. Docs for the LLM are part of the spec, not an afterthought: a wrapper without them is rejected at module load.

Gadgets are the renderer-side half of a symmetric design (glossary):

Renderer Agent
Unit gadget tool
Catalog clientCapabilities.gadgets agentCapabilities.tools
Gives something to render with something to act with

Both catalogs are declared per app and bounded by the operator, not the agent. An agent cannot smuggle a capability into a render by asking for it: a contract may only reference gadgets the operator registered on the app, and a render naming an unregistered one is rejected with gadget_not_registered before any code runs.

Generated code runs in a sandboxed iframe with a closed Content-Security-Policy. Each gadget widens that sandbox declaratively, not implicitly:

  • Provenance — the wrapper bundle is fetched by exact package + version, and marketplace-published bundles carry a sha384 subresource-integrity pin stamped at publish time. What loads is byte-identical to what was published.
  • Network — a gadget declares the origins it talks to (connect: ["https://tile.openstreetmap.org"]); the renderer unions exactly those into the iframe’s CSP. A map gadget can fetch tiles; it cannot phone anywhere else, and neither can the generated code around it.
  • Secrets — wrappers that need a token (Mapbox, Stripe) declare the GGUI_PUBLIC_APP_* keys they consume, and the operator supplies values through the app’s public-env channel. Keys never appear in generated code.

The result: the only non-generated code in a render is code an operator explicitly registered, at a pinned version, with a declared network footprint.

A gadget’s version is part of the blueprint cache key. Bump a wrapper and every cached generation built against the old version stops matching automatically — no stale UI silently calling a changed hook contract. This is the same discipline that makes blueprint reuse safe in general: the cache key captures everything the compiled component depends on.

Browser capabilities that need no third-party code ship as STDLIB hooksuseGeolocation, useCamera, useClipboardWrite, useClipboardPaste, useNotifications, useFilePicker, useMicrophone. They follow the same hook contract but require no operator registration; the generator can reach for them whenever the ask calls for one.

  • Not a template. A gadget renders one capability (a map, a chart, a checkout); the generated component decides where it sits, what feeds it, and what happens around it. Composition stays generative; capability stays deterministic.
  • Not agent-side. Gadgets never execute in the agent’s process and never see the conversation. Anything the agent should do is a tool.
  • Not a behavior escape hatch. The contract describes data flowing between agent and UI; gadgets extend what the component code can do. Neither is a channel for smuggling the other — see Sessions & the event model for the placement rule.
  • Gadgets SDK — author a wrapper: the createGguiGadget factory, teaching-text budgets, bundle + types publishing, operator registration.
  • Marketplace — publish and install gadgets across apps.
  • Blueprints and the cache — how versioned capability feeds exact-match reuse.