A Gadget is a private, sandboxed instance of an AI-built application; users can modify its code on demand without affecting others, and Blueprints are reusable templates capturing the gadget's code and structure—shareable as .gadget archives or published for discovery. Blueprints are owned by the gadget's creator, stored across three backing stores (Gadget DO, User DO, Workers KV), and accessed publicly by ID without authentication; Blueprints explicitly exclude runtime state (storage, chat history, live credentials). A Durable Object (DO) is a Cloudflare Workers primitive that provides a single-instance, stateful computation with its own persistent storage — guaranteeing that all requests to a given DO are handled serially by one instance worldwide. Workers KV is Cloudflare's globally distributed key-value store with eventual consistency semantics — reads may lag behind writes across regions, making it suitable for high-read, low-write public data such as Blueprint lookups. Operational Transformation (OT) is a concurrency-control technique that imposes ordering and conflict-resolution on concurrent edits, as opposed to last-write-wins semantics.
A Gadget is a private instance of an AI-built application that runs in its own sandbox — each user gets their own copy, not a shared SaaS instance.[1] Gadget sandboxing provides two security properties: it prevents cross-user data leakage from app bugs, and it makes it safe for users to modify gadget code on demand via the agent.[1] Workspaces support multiple gadgets, each accessible via the sidebar.[2] AgentGadgetInfo.isDefault marks the workspace's default gadget — the gadget that tools operate on when their gadget-name parameter is omitted. Only workspaces migrated from single-gadget days (or created from a blueprint) have one.[3] Cross-gadget hook targeting allows a hook registered in one gadget to route events to or trigger behavior in any sibling gadget within the same workspace, enabling coordination across gadgets. In workshop-backend, agent.ts and overseer.ts support hooks targeting any gadget within a workspace — hooks are no longer scoped solely to the gadget that registered them. The frontend export menu disables the export button while an export is in progress, preventing duplicate submissions. Gadgets can declare their own export entrypoint; gadget-export.ts, browser-export.ts, and browser-export-runtime.ts resolve it via a browser-based runtime, replacing a previous monolithic export path. In overseer.ts and gadget-export.ts, entrypoint resolution is the designated failure surface for exports — both missing export entrypoints and missing files are handled with explicit error paths. SandboxedGatekeeperApp.tsx was updated for embedded iframe modal presentation; any gatekeeper that renders a sandboxed UI must be validated against the updated embedding contract. Workspace file state is persisted in src/git-store.ts; the Git repository structure is the canonical source of truth for workspace content. Backend sync reconciliation in agent.ts and overseer.ts uses Operational Transformation (OT) rather than last-write-wins semantics, imposing ordering and conflict-resolution constraints on concurrent edits across backend and frontend collaboration. The frontend code editor is CodeMirror-based, implemented in CodeEditor.tsx and CodeDiffEditor.tsx, with accompanying styles in CodeDiffEditor.css and tests in CodeDiffEditor.test.tsx.
Blueprints are the Cloudflare OS equivalent of templates — they specify a whole application (code and structure), not just document content, and can be created from existing Gadgets and shared.[1] A single Gadget can have multiple Blueprints, potentially at different code versions (e.g. a "stable" and a "latest" blueprint of the same gadget).[4] A Blueprint is always owned by the gadget's owner, regardless of which collaborator creates it. Bundled (format) blueprints have no owning user at all.[4] A Blueprint captures source code, binding requirements, and metadata, but explicitly does NOT capture SQLite storage contents, AI chat history, edit history, or live credentials — only the shape (type, gatekeeper name, URL pattern) of each binding.[4]
Blueprint IDs are 128-bit random hex strings generated server-side, except for bundled (format) blueprints which carry stable, readable IDs like format.document.[4] A blueprintId must never be changed after deployment — installs and promotions are keyed on it, and renaming orphans the old entry.[5] When a Blueprint is updated to reflect newer code, its version number increments and old code versions are retained in R2 storage to avoid race conditions during concurrent instantiation.[4]
Blueprint data flows one-way through three stores: Gadget DO (blueprints collection, authoritative) → User DO (blueprints collection, denormalized for listing) → Workers KV (BLUEPRINTS namespace, public-facing lookup).[4] Blueprint code content is stored in an R2 bucket (BLUEPRINT_CONTENT) keyed by <blueprintId>/<version> as a Yjs V2-encoded gzip-compressed document (full state, not incremental updates). Old versions are retained on update; all versions are cleaned up on deletion.[4] The dirty flag on a Blueprint's Gadget DO record is set to true before propagation begins and cleared only after all writes succeed. If a failure leaves it set, the UI shows a warning with a "Retry" button.[4] The Gadget DO holds the authoritative blueprints collection; the User DO holds a denormalized copy for listing purposes.
The .gadget export/import file format is a binary container with an 8-byte magic number (0xec2e2d3a2300e317), 4-byte format version (1), 4-byte JSON metadata length, 8-byte raw content length, JSON-encoded BlueprintMetadata, and raw blueprint content bytes.[4] The .gadget archive does NOT include ownerId, gadgetId, or screenshot bytes; imported archives clear any screenshot marker because screenshots are stored separately from the archive content.[4] Blueprint import validation caps JSON metadata at 64 KiB and the stored snapshot payload at 32 MiB to prevent unbounded allocation in the worker from malformed archives.[4] Blueprint import/export streams content bytes directly to and from R2 using pipeTo() rather than buffering the whole archive in memory on the server.[4]
Blueprints are publicly accessible via https://<host>/blueprint/<blueprint-id> — anyone with the link can view metadata without authenticating, but creating a Gadget from a blueprint requires authentication.[4] PublicApi.getBlueprint(id) fetches blueprint metadata by ID without authentication — knowing the ID is sufficient, since a blueprint is treated as public data. Returns null if the blueprint doesn't exist.[6] PublicApi.downloadBlueprint(id) returns a ReadableStream<Uint8Array> of a .gadget archive containing BlueprintMetadata plus the current code snapshot — not the full KV record.[6] Library entries come in two forms: "saved by reference" (via addBlueprintToLibrary(), stores cached metadata but blueprint remains owned by the original publisher — removing it only deletes the personal library entry) and "uploaded" (via importBlueprint() from a .gadget archive — removing one deletes the imported blueprint content as well).[4] Pinning a public blueprint that is not already in the user's library adds it to the library first, then pins it.[4]
A "format" is a blueprint promoted by admins (AdminConfig.formats) so it appears in the composer's + menu. A blueprint can declare BlueprintMetadata.output with a grouping id, noun/plural, and icon from the closed OUTPUT_ICONS set — this is presentation only and grants nothing special.[4] Admin format overrides (FormatCuration.overrides) are applied on every instantiation path — renames reach gadgets the agent builds as well as ones made from the menu.[4] Only gadget-backed published blueprints are featureable by admins; uploaded/imported library blueprints are intentionally excluded from featuring.[4] The featured blueprint state is split: the authoritative featured bit lives in the owning user's blueprints record inside their User DO, while the AdminSettings durable object (a singleton via getByName("")) mirrors the current public metadata and writes a KV snapshot consumed by AuthenticatedApi.listFeaturedBlueprints().[4] Blueprint binding annotations (friendly name, description, suggest value) are configured in the Blueprint modal in the gadget editor header; the annotation is stored on GatekeeperRecord as the blueprintAnnotation field.[4]
packages/workshop-backend/format-blueprints/ holds the deployment's built-in output-format blueprints as committed data (.gadget archive + .json sidecar). scripts/build-format-blueprints.mjs generates src/generated/format-blueprints.ts from this directory; build, types:check, and test all run the generator first.[5] Agent skill documentation in .agents/skills/write-gatekeeper/ (AGENTS.md, SKELETON.md, SKILL.md) designates the Vite+ task build pattern as the canonical reference for authoring new gatekeepers and modifying the build pipeline. Each built-in format blueprint is stored as a plain source directory containing a blueprint.json manifest and human-readable source files (README, client.js, server.js) under a files/ subdirectory — enabling direct source editing and version-control diffing instead of working with opaque .gadget archives. The three built-in format blueprints shipped with the deployment are workspace-docs, workspace-sheets, and workspace-slides.
packages/workshop-backend/src/feature-flags.ts exports resolveUiFeatureFlags, which evaluates all UI feature flags concurrently using Promise.all, passing userId as the evaluation context to the Flagship binding's getBooleanValue. When env.DEV is true, all flags are set to DEV_UI_FEATURE_FLAGS without remote evaluation.[7] If the FLAGS Flagship binding is absent at runtime, resolveUiFeatureFlags logs a warning with event feature-flags.binding.missing and falls back to DEFAULT_UI_FEATURE_FLAGS.[7] If an individual flag evaluation throws, the error is logged with event feature-flags.evaluate.failed and the flag's configured default value is used — evaluation never propagates exceptions.[7]
Frontend components in workshop-frontend that subscribe to account state must dispose pending subscriptions on unmount; failing to do so allows stale callbacks to fire after unmounting, causing state-update-after-unmount bugs and memory leaks. A responsive-layout refactor across packages/workshop-frontend covers AppShell, sidebar, modal surfaces, blueprint pages, chat, gadget editor, settings, share, and onboarding flows — mobile viewports are a first-class target for all new frontend surfaces. The useDialogSelectPortalContainer hook in packages/workshop-frontend provides a dedicated portal container for dialog-based select dropdowns that must escape a stacking context — this is the canonical pattern for modal and popover layering in the frontend. In packages/workshop-frontend, BlueprintLandingPage.tsx and GatekeeperModal.tsx were updated to resolve a z-index/portal-container conflict where the model selector dropdown rendered beneath the gatekeeper modal overlay.
The agent skill documentation in .agents/skills/write-gatekeeper/ (AGENTS.md, SKELETON.md, SKILL.md) has been updated to reflect the Vite+ task build pattern as the canonical reference for new gatekeepers and build pipeline modifications.
Sources