Skip to content
Public preview · the console is open

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):

RendererAgent
Unitgadgettool
CatalogclientCapabilities.gadgetsagentCapabilities.tools
Givessomething to render withsomething 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 hooks — useGeolocation, 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.