# Cloudflare OS — full corpus # LLM Wiki An open-source template for building LLM-powered knowledge bases, following [Andrej Karpathy's "LLM Wiki" pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f). You provide raw sources. The LLM reads them, writes structured wiki pages, cross-links everything, and maintains it over time. You never edit the wiki directly — you curate sources and ask questions. ## How It Works The system has three layers: ``` raw/ Sources you collect (articles, transcripts, notes, PDFs) wiki/ LLM-written & maintained pages (summaries, concepts, entities, syntheses) CLAUDE.md Schema that tells the LLM how to structure everything ``` Three operations drive the workflow: | Operation | Trigger | What happens | |-----------|---------|--------------| | **Ingest** | "ingest raw/my-source.txt" | LLM reads the source, creates a summary page, creates/updates concept and entity pages, adds cross-links, updates the index and log | | **Query** | Ask any question | LLM searches the wiki, synthesizes an answer with citations, optionally creates a synthesis page for novel insights | | **Lint** | "lint" or "health check" | LLM audits all pages for orphans, contradictions, missing links, incomplete sections, and low-confidence claims — fixes what it can, reports the rest | ## Quick Start 1. **Clone this repo** ```bash git clone https://github.com/YOUR_USERNAME/llm-wiki.git my-knowledge-base cd my-knowledge-base ``` 2. **Customize CLAUDE.md** for your domain - Update the Purpose section with your topic - Replace the placeholder tagging taxonomy with your own categories - Adjust confidence level descriptions if needed - Everything else (workflows, page formats, linking rules) works as-is 3. **Drop sources into `raw/`** - Text files, transcripts, articles, notes — any plain text - These are immutable once added; the LLM never modifies them 4. **Tell the LLM to ingest** ``` ingest raw/my-first-source.txt ``` The LLM will create summary pages, concept pages, entity pages, cross-links, and update the index. 5. **Ask questions** ``` What are the key differences between X and Y? ``` The LLM answers from the wiki, citing specific pages. 6. **Run health checks** ``` lint ``` The LLM audits the wiki and fixes issues. ## Directory Structure ``` . ├── CLAUDE.md # Schema — the LLM's instructions ├── raw/ # Your source documents (immutable) └── wiki/ ├── index.md # Master catalog of all pages ├── log.md # Append-only activity log ├── dashboard.md # Dataview dashboard (Obsidian) ├── analytics.md # Charts View analytics (Obsidian) ├── flashcards.md # Spaced repetition cards ├── summaries/ # One page per source document ├── concepts/ # Concept and framework pages ├── entities/ # People, tools, organizations, etc. ├── syntheses/ # Cross-cutting analyses and comparisons ├── journal/ # Research/session journal entries │ └── template.md # Journal entry template └── presentations/ # Marp slide decks ``` ## Enhancements This template includes several extras beyond the core wiki pattern: ### Dataview Dashboard (`wiki/dashboard.md`) Live queries that surface low-confidence pages, recent updates, concepts by tag, and pages with the most sources. Requires the [Dataview](https://github.com/blacksmithgu/obsidian-dataview) Obsidian plugin. ### Charts View Analytics (`wiki/analytics.md`) Visual analytics with pie charts, bar charts, and word clouds. Requires the [Charts View](https://github.com/caronchen/obsidian-chartsview-plugin) Obsidian plugin. ### Mermaid Diagrams Use Mermaid code blocks in any wiki page to create flowcharts, sequence diagrams, or concept maps. Native support in Obsidian and GitHub. ### Marp Slides (`wiki/presentations/`) Create slide decks from markdown using [Marp](https://marp.app/). Drop presentation files in this directory. ### Research Journal (`wiki/journal/`) Track your research sessions, experiments, or applied work with the included template. The LLM can reference journal entries when answering queries. ### Spaced Repetition (`wiki/flashcards.md`) Flashcards in the format used by the [Spaced Repetition](https://github.com/st3v3nmw/obsidian-spaced-repetition) Obsidian plugin. Ask the LLM to generate flashcards from any wiki page. ### MCP Server This repo works with Claude Code's MCP server capabilities. Point an MCP-compatible client at this repo and the LLM can read/write the wiki programmatically. ## Customizing for Your Domain The schema in `CLAUDE.md` is domain-agnostic. To adapt it: 1. **Purpose** — Describe your knowledge domain in one paragraph 2. **Tagging taxonomy** — Replace placeholder categories with your own (e.g., for a cooking KB: `cuisine`, `technique`, `ingredient`, `equipment`) 3. **Confidence levels** — Adjust the descriptions to match your domain's evidence standards 4. **Entity types** — Update the entity page description to match what entities mean in your domain (people, tools, companies, etc.) 5. **Journal template** — Customize `wiki/journal/template.md` for your workflow Everything else — page format, linking conventions, workflows, rules — is universal and works across domains. ## Example Domains This template works for any knowledge-intensive topic: - **Research notes** — papers, experiments, methodologies - **Book analysis** — themes, characters, author techniques - **Competitive analysis** — companies, products, market trends - **Course notes** — lectures, readings, key concepts - **Personal development** — frameworks, habits, book summaries - **Technical documentation** — APIs, architectures, design patterns - **Hobby deep-dives** — any subject you want to master ## License MIT # Cloudflare OS Knowledge Base An LLM-maintained knowledge base on **Cloudflare OS** (github.com/cloudflare/cloudflare-os, os.cloudflare.app) — an open-source AI productivity 'operating system' on Cloudflare Workers: an agent chat UI with company knowledge, sandboxed 'gadget' apps, and the Gatekeepers security framework. Pinned to the v2 August-2026 early-access release (snapshot 2026-08-05). ## Concepts - [[concepts/cloudflare-os-overview|Cloudflare OS Overview]] - [[concepts/getting-started|Getting Started with Cloudflare OS]] - [[concepts/architecture|Cloudflare OS Architecture]] - [[concepts/agent-chat-and-company-knowledge|Agent Chat and Company Knowledge]] - [[concepts/gadgets|Gadgets]] - [[concepts/blueprints|Blueprints]] - [[concepts/gatekeepers|Gatekeepers]] - [[concepts/writing-a-gatekeeper|Writing a Gatekeeper]] - [[concepts/sharing|Sharing]] - [[concepts/observers|Observers]] - [[concepts/mcp-support|MCP Support]] - [[concepts/oauth-signin|OAuth Sign-In]] - [[concepts/public-server-and-deployment|Public Server and Deployment]] - [[concepts/ai-gateway-billing|AI Gateway Billing]] ## Gatekeeper Connectors - [[entities/gatekeepers-catalog|Gatekeepers Catalog]] — start here - [[entities/gatekeeper-cloudflare|Gatekeeper: Cloudflare]] - [[entities/gatekeeper-confluence|Gatekeeper: Confluence]] - [[entities/gatekeeper-email|Gatekeeper: Email]] - [[entities/gatekeeper-github|Gatekeeper: GitHub]] - [[entities/gatekeeper-google|Gatekeeper: Google]] - [[entities/gatekeeper-homeassistant|Gatekeeper: Home Assistant]] - [[entities/gatekeeper-mcp-portal|Gatekeeper: MCP Server Portal]] - [[entities/gatekeeper-mcp|Gatekeeper: MCP]] - [[entities/gatekeeper-notion|Gatekeeper: Notion]] - [[entities/gatekeeper-scheduler|Gatekeeper: Scheduler]] - [[entities/gatekeeper-slack|Gatekeeper: Slack]] - [[entities/gatekeeper-spotify|Gatekeeper: Spotify]] - [[entities/gatekeeper-supabase|Gatekeeper: Supabase]] - [[entities/gatekeeper-zoominfo|Gatekeeper: ZoomInfo]] --- title: "Agent Chat and Company Knowledge" type: concept tags: [agents, overview, user, foundational] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-agents-md.md", "raw/github_doc-readme-md.md", "raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Agent Chat and Company Knowledge ## Definition The agent chat UI is the first of Cloudflare OS's three pillars (see [[concepts/cloudflare-os-overview]]): a chat interface where a user asks an agent to do tasks, and the agent is "preloaded with knowledge about how your company operates" (raw/github_doc-readme-md.md). The Cloudflare blog describes the same thing as "an agent workspace grounded in context and skills your organization curates, with an isolated runtime where agents can write and run code" (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). ## How It Works **The agent is a general multi-purpose Code Mode agent.** The README is explicit that the Cloudflare OS agent "is actually a fully multi-purpose agent that can perform arbitrary tasks; like other popular coding agents, you don't have to code with it." It can build [[concepts/gadgets|gadgets]], but it can also skip the gadget and just perform tasks directly. It is a [Code Mode](https://blog.cloudflare.com/code-mode/) agent, meaning it performs tasks by writing and immediately executing snippets of code, and it can be connected to external resources through [[concepts/gatekeepers|Gatekeepers]] (raw/github_doc-readme-md.md). **Model choice.** Users can choose their LLM — the README says Cloudflare OS "works with many major AI model providers and self-hosted models, with more providers being added all the time," and claims that because of the platform's tightly-integrated, simplified nature, the coding agent "often performs better and faster with fewer tokens than a general-purpose coding agent would," even using the same underlying models (raw/github_doc-readme-md.md). See [[concepts/architecture]] for the `pi-ai`/AI Gateway routing machinery behind this, and [[concepts/ai-gateway-billing]] for cost control. **How company knowledge is preloaded — the Context Library.** AGENTS.md documents `packages/gatekeeper-context`, described as "the Context Library — a gatekeeper whose account provides a singleton read session + a management UI, for authoring collections of context documents that agents read as observations." Collections have one of two visibilities: - **private** — owned by a single account, readable/writable only by that account. - **public** — created and edited only by deployment admins, readable by everyone, and auto-enabled for all users (raw/github_doc-agents-md.md). This gatekeeper's `GatekeeperVendor` entrypoint (bound as `GATEKEEPER_CONTEXT`) declares `autoProvisionsAccount` and mints a `ContextAccount` via `createAccount()` with no user identity required — the account keys its own private data by a generated `accountId`. The account exposes an agent read session (`getSession()`), collection discovery metadata (`getAgentCatalog()`), and a management UI (`startAppUi({isAdmin})`), the latter a single-file React SPA (raw/github_doc-agents-md.md). Because this gatekeeper can auto-provision an account, it is an example of the "ambient gatekeeper" pattern: AGENTS.md explains that for auto-provisioning gatekeepers, a deployment admin picks a per-vendor mode in the admin Gatekeepers panel — **disabled** / **optional** / **enabled** (default **optional**) — where `enabled` auto-provisions the account for every user (forced, hidden from the Connectors list), `optional` lets each user opt in, and `disabled` offers it to no one. An account that declares an agent **singleton** is auto-provided to the owner's workspaces as an **ambient gatekeeper record**, folded into each chat's env as a named chat binding that the agent reads during `executeCode`, with each read recorded as an observation (raw/github_doc-agents-md.md). **How tasks are run.** The blog post enumerates the kinds of work a workspace does: researching and answering questions by writing code to search/filter/join/analyze information rather than pulling a whole dataset into the model's context window; creating docs, slides, and spreadsheets that don't have to be static files — they can stay connected to live data and update as sources change, while still exporting to familiar formats or services like Google Drive; creating collaborative, connected apps (see [[concepts/gadgets]]) when a document isn't enough; and running deterministic workflows — turning a known sequence of steps into mostly-deterministic code, invoking a model only where judgment is genuinely useful, triggerable on demand, on a schedule, or on an event in a connected system (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). **Collaborating with AI inside a built app.** The README describes a further mechanism: every app built with Cloudflare OS automatically has an agent-friendly API, because client and server portions of a gadget must communicate over Cap'n Web RPC. This means a user doesn't need to build an MCP server or a custom agent loop for their own tool — the agent can call the same server method a human user's client calls, "as if it were a local call" (raw/github_doc-readme-md.md). **Access starts at nothing.** Both the README and the blog stress that agents (and gadgets) do not ambiently receive access to configured external accounts. The README: "Each agent, and each Gadget, by default has access to nothing... Instead, you must *introduce* each agent (or Gadget) to any particular resources you want it to access," for instance by pasting a link to a GitHub repository, or by an agent requesting an introduction that a human can grant or deny (raw/github_doc-readme-md.md). The blog frames the same rule at the code level: a granted resource is exposed to generated code as a typed capability binding (e.g. `env.PROJECT`), and "the credential remains completely isolated from the agent and any generated code" (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). See [[concepts/gatekeepers]] for the mechanism enforcing this. ## Key Parameters - **Code Mode agent**: performs tasks by writing and executing code snippets, not solely by calling fixed tools (raw/github_doc-readme-md.md). - **Context Library gatekeeper** (`packages/gatekeeper-context`): the concrete mechanism for company-knowledge preload, with private vs. public collection visibility (raw/github_doc-agents-md.md). - **Ambient gatekeeper modes**: disabled / optional (default) / enabled, controlling whether an auto-provisioning gatekeeper's account is forced on every user, opt-in, or unavailable (raw/github_doc-agents-md.md). - **Capability introductions**: the unit of granting an agent access to a resource; can be user-initiated (paste a link) or agent-requested (raw/github_doc-readme-md.md). - **Deterministic workflows**: on-demand, scheduled, or event-triggered code paths that call a model only where judgment adds value (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). See [[concepts/observers]] for the event/trigger mechanism. ## When To Use - Explaining to a non-technical stakeholder what "the agent already knows how our company works" actually means mechanically (Context Library collections, not fine-tuning or a fixed system prompt). - Deciding whether a task belongs in ad hoc agent chat, a generated document/gadget, or a scheduled deterministic workflow — the blog's four use-case categories (research, docs, apps, workflows) are a reasonable decision framework (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). - Understanding why a freshly-connected external account doesn't automatically become usable by every agent — by design, it requires an explicit introduction (raw/github_doc-readme-md.md). ## Risks & Pitfalls - Sourced material does not fully specify the agent chat UI's session/thread model, multi-turn memory boundaries, or exactly how much of a public Context Library collection is injected per turn versus fetched on demand — treat those mechanics as **not verified** in this KB; only the read-session/observation model documented in AGENTS.md is confirmed (raw/github_doc-agents-md.md). - The capability-introduction model differs from "most agent harnesses, where MCP servers are configured upfront, making broad access to all your services ambiently available to the agent in every chat" (raw/github_doc-readme-md.md) — porting mental models from a plain MCP-based agent setup will overestimate what a freshly-started Cloudflare OS agent can reach. - Public Context Library collections can only be created/edited by deployment admins (raw/github_doc-agents-md.md) — a non-admin user cannot contribute to the shared company-knowledge layer directly, only to their own private collections. ## Related Concepts - [[concepts/cloudflare-os-overview]] — the agent chat pillar in context. - [[concepts/gatekeepers]] — the capability/introduction mechanism this page describes. - [[concepts/gadgets]] — where agent-built apps live and expose their own agent-callable API. - [[concepts/architecture]] — the pi-ai/AI Gateway model-routing layer behind "choose your LLM." - [[concepts/observers]] — event-driven triggers for deterministic workflows. - [[concepts/ai-gateway-billing]] — cost attribution for agent inference. ## Sources - raw/github_doc-agents-md.md - raw/github_doc-readme-md.md - raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md --- title: "AI Gateway Billing" type: concept tags: [billing, auth, operator, advanced, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-docs-ai-gateway-billing-md.md"] confidence: medium cfos_snapshot: "2026-08-05" --- # AI Gateway Billing ## Definition AI Gateway billing is an optional Cloudflare OS flow that gives each user a **free daily allowance** of AI usage and, once exhausted, bills further usage against the user's own **Cloudflare AI Gateway credits**. It is off by default (`ENABLE_CLOUDFLARE_LIMITS` unset), in which case usage is unlimited — the expected setting for self-hosted, single-organization deployments. It's the metering/payment layer that makes [[concepts/public-server-and-deployment|public, multi-user deployments]] financially sustainable without the platform operator paying for every user's inference. ## How It Works Each user gets a free allowance of LLM calls per UTC day (default 100), counted on their own `UserDurableObject` via `consumeDailyLlmCall`/`checkDailyLlmCount`. Before every user-initiated agent turn, the overseer calls `checkUsageAndBalance`, which branches four ways: - **Connected, balance ≥ $2** → allowed, routed through the user's *own* account so usage bills their Cloudflare credits — even while free-tier allowance remains. The platform is never charged for funded users, and their daily counter is left untouched. - **Otherwise, within the free tier** → allowed, served via the platform's configured AI Gateway (this also covers connected users whose balance is below $2, including $0). - **Free tier exhausted, no Cloudflare account connected** → blocked, with a prompt to connect. - **Free tier exhausted, connected but balance below $2** → blocked, with a prompt to add credits. The balance shown to users is read *live* from the user's Cloudflare AI Gateway billing (`/ai-gateway-billing/credit_balance`), cached for 5 minutes. Topping up happens entirely in the [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/ai/ai-gateway) — the platform itself never holds money. **Connecting Cloudflare.** Billing is tied to the Cloudflare [[concepts/gatekeepers|gatekeeper]]: OAuth tokens live in that gatekeeper's own connection, and the billing flow obtains a usable token via `getUsableAccessToken()`. A user connects Cloudflare either by [[concepts/oauth-signin|signing in]] with it directly, or via a "Connect Cloudflare" button that runs the normal gatekeeper connect flow (`AuthenticatedApi.connectAccount("cloudflare")`). If the grant sees exactly one Cloudflare account it's auto-selected; with several, the user is prompted to choose. Billing is account-level (Unified Billing) — inference routes through that account's auto-created "default" AI Gateway. ## Key Parameters ``` ENABLE_CLOUDFLARE_LIMITS=true PUBLIC_BASE_URL=https://your-host AUTH_GATEKEEPERS=cloudflare # allow Cloudflare sign-in/connect (plus any others) CLOUDFLARE_OAUTH_CLIENT_ID=... CLOUDFLARE_OAUTH_CLIENT_SECRET=... CF_AI_GATEWAY=your-gateway CF_AI_GATEWAY_PROVIDERS=anthropic,openai,google CF_AI_GATEWAY_ACCOUNT_ID=... CF_AI_GATEWAY_API_TOKEN=... CF_AI_GATEWAY_WAI_DIRECT=true # optional: Workers AI straight to REST endpoint, no gateway cost logs ``` Optional, with sensible defaults: ``` DAILY_LLM_CALL_LIMIT=100 # free-tier LLM calls per user per UTC day MINIMUM_CLOUDFLARE_BALANCE=2 # min connected-account balance (USD) to proceed via BYOK ``` The Cloudflare dashboard OAuth endpoints and scopes are **hardcoded** in the Cloudflare gatekeeper (`packages/gatekeeper-cloudflare/src/oauth.ts`): - auth: `https://dash.cloudflare.com/oauth2/auth` - token: `https://dash.cloudflare.com/oauth2/token` - scopes: `offline_access aig.read aig.run aig.write user-details.read account-settings.read` - redirect URI: `${PUBLIC_BASE_URL}/gatekeeper/cloudflare/oauth` The Gateway API token needs **Run and Read** permissions — Run so Gadgets can execute models, Read so per-log costs can be retrieved for user-visible accounting. **Storage.** The free-tier daily counter lives on each `UserDurableObject` (no separate binding). OAuth tokens live only in the connected Cloudflare *gatekeeper* account; each `UserDurableObject` stores just lightweight billing state — a selected account id, a cached credit balance, and the daily counter — no tokens. **Code layout** (`packages/workshop-backend/src/ai-gateway-billing/`): `config.ts` (flag/minimum-balance readers), `limits/config.ts` + `limits/usage-checker.ts` (`checkUsageAndBalance`/`getUsageInfo`), `cloudflare/account-service.ts` (CF REST accounts/balance), `cloudflare/connection-service.ts` (token retrieval, account selection, balance caching, BYOK routing). Client-side: `ServerConfigContext.cloudflareLimitsEnabled`; `components/billing/` (`UsageSettings`, `OutOfCreditsModal`, `AccountSelectionModal`). ## When To Use Enable this when running Cloudflare OS as a shared or public service where an operator cannot or should not absorb unlimited per-user AI inference cost — e.g. any deployment reachable by users outside a single already-trusted organization. Leave it disabled for straightforward self-hosted "Your Company OS" deployments where usage is expected to stay within an organization's own trust and cost boundaries. ## Risks & Pitfalls - Off by default means a fresh self-hosted deployment has **unlimited** AI usage per user unless this is deliberately turned on — an operator opening such a deployment more broadly than intended could face unexpected inference costs. - The platform "never holds money" — all top-ups go through the Cloudflare dashboard directly, so there is no in-product payment flow to audit or reason about; users unfamiliar with the Cloudflare dashboard may find the top-up step unintuitive. - Requires `ENABLE_CLOUDFLARE_LIMITS`, `AUTH_GATEKEEPERS` including `cloudflare`, and full Cloudflare OAuth app credentials all to be configured correctly together — a partial configuration silently leaves users unable to connect or blocked with the wrong prompt. - The Gateway API token needs both Run and Read permissions; a token missing Read permission would allow inference but break the user-visible cost accounting. ## Related Concepts - [[concepts/oauth-signin]] — the mechanism for connecting the Cloudflare gatekeeper account this billing flow depends on. - [[concepts/public-server-and-deployment]] — the deployment mode this flow is designed for. - [[concepts/gatekeepers]] — billing is implemented entirely through the Cloudflare gatekeeper's connected-account token. - [[entities/gatekeeper-cloudflare]] — the specific connector involved. ## Sources - raw/github_doc-docs-ai-gateway-billing-md.md --- title: "Cloudflare OS Architecture" type: concept tags: [overview, foundational, developer, advanced] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-agents-md.md", "raw/github_doc-plans-pi-impl-md.md", "raw/github_doc-readme-md.md", "raw/github_doc-packages-backend-utils-readme-md.md", "raw/github_doc-packages-mcp-shared-readme-md.md", "raw/github_doc-packages-workshop-frontend-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Cloudflare OS Architecture ## Definition Cloudflare OS is built entirely on [Cloudflare Workers](https://workers.cloudflare.com) as its substrate, making heavy use of Durable Objects, Dynamic Workers, and Facets (raw/github_doc-readme-md.md). It is organized as a pnpm monorepo of packages that map onto a traditional operating-system's components — the second sense of "operating system" from [[concepts/cloudflare-os-overview]], where the platform manages and schedules AI agent and gadget workloads much as a kernel manages processes. ## How It Works **Built on Workers, by the Workers team.** The README states plainly that Cloudflare OS is built by the people who built Workers itself, and uses cutting-edge runtime features — in fact, Dynamic Workers, Facets, and other features were added to the Workers Runtime specifically to support Cloudflare OS. Every workspace is its own Durable Object; every gadget runs in a Dynamic Worker Facet; Gatekeepers also install facets into each workspace to manage access to remote services. Because `workerd` (the Workers runtime) is itself open source, Cloudflare OS is not locked to Cloudflare's platform — it can run entirely on top of `workerd` on your own servers (raw/github_doc-readme-md.md; see [[concepts/getting-started]] for the current status of that self-hosting path). **The OS-for-AI-workloads sense, concretely.** The README's OS analogy table maps: kernel → `packages/workshop-backend`; device drivers → `packages/gatekeeper-*`; shell → `packages/workshop-frontend`; processes → gadgets; executables → blueprints; users → users; ACLs → shared permissions; and, with no traditional-OS analog, agents (raw/github_doc-readme-md.md). The backend is called out as legitimately doing much of what a real OS kernel does: connecting users to programs and devices (gadgets and Gatekeepers) while implementing security through sandboxing and access control. The README's core architectural argument for treating agents as a first-class OS concern is that they cannot simply be treated as users — they must be accountable to a human user while having their own restricted permissions — and that capability-based security, not access control lists, is the right model for this (raw/github_doc-readme-md.md). See [[concepts/gatekeepers]] for how that capability model is enforced. **Monorepo package structure.** AGENTS.md documents the project's own contributor-facing map of packages (raw/github_doc-agents-md.md): - `packages/workshop-frontend` — the Gadgets Workshop UI: a pure single-page app running entirely client-side, speaking to the backend over an RPC API on a persistent WebSocket. Built with React, Kumo UI, Phosphor icons, and Vite. It supports two authentication modes selected at build time — password mode (default, with `/signup`) and Cloudflare Access mode (`VITE_CF_ACCESS_MODE=true`, relying on `CF_ACCESS_ISS`/`CF_ACCESS_AUD` on the backend for JWT verification) (raw/github_doc-packages-workshop-frontend-readme-md.md). - `packages/workshop-backend` — the Gadgets Workshop server, running on Cloudflare Workers. AGENTS.md calls this the **kernel**: "it defines the architecture and is held to a higher bar than UI/gatekeeper code," with reviewers reading every line of `workshop-backend` and of API changes in `workshop-shared`. It hosts `format-blueprints/`, the built-in output-format blueprints shipped with a deployment (see [[concepts/blueprints]]). - `packages/workshop-shared` — the shared RPC API definitions between client and server, using the Cap'n Web protocol (similar semantics to Cloudflare's Worker-to-Worker RPC, but able to run in a browser over WebSocket). - `packages/configurator-ui` — type-only component helpers for gatekeeper resource configurator UI modules. - `packages/gatekeeper-*` — one Cloudflare Worker per external-service integration (GitHub, Google, Slack, Notion, Confluence, Supabase, Spotify, ZoomInfo, Home Assistant, Email, Cloudflare, MCP, MCP Portal, Scheduler, Context — see [[concepts/gatekeepers]] and the per-connector entity pages). Each handles its own OAuth flow and provides sandboxed access to its external API. - `packages/mcp-shared` — a library (not a Worker) shared by the two MCP-speaking gatekeepers, `gatekeeper-mcp` and `gatekeeper-mcp-portal`. Its README states the design principle plainly: "Code lives here when two copies of it would eventually disagree and the disagreement would be a security bug" — tool classification, the scope grammar, the OAuth lifecycle, and approval-queue wiring all live here rather than being duplicated per-connector (raw/github_doc-packages-mcp-shared-readme-md.md). See [[concepts/mcp-support]]. - `packages/backend-utils` (`@gadgets/backend-utils`) — a shared library for Workers-runtime code, providing structured logging (`@gadgets/backend-utils/logger`), an observability-context helper requiring `nodejs_als`/`nodejs_compat`, and an optional error-reporting entry point (`reportIssue`) that dispatches bounded error events to a private `ERROR_REPORTER` binding, no-op when that binding is absent (raw/github_doc-packages-backend-utils-readme-md.md). - `packages/router` — the public origin of a deployed instance. It serves the workshop-frontend static assets and routes by path prefix: `/api/*` and `/blueprint-screenshot/*` to the backend, `/gatekeeper//*` to whichever gatekeepers are bound (discovered by scanning its own `GATEKEEPER_*` service bindings, so installing a gatekeeper is purely a binding change). The same worker doubles as the dev router for `pnpm dev-server` (raw/github_doc-agents-md.md). **Agent runtime substrate.** The agent loop itself is implemented on top of `pi-ai` and `pi-agent-core` (from Pi, credited in the README as making it "easy to support every LLM provider with one API" — raw/github_doc-readme-md.md). `plans/pi-impl.md` documents a migration of the backend's agent loop from the Vercel AI SDK onto this pi stack: the low-level `runAgentLoopContinue` API (an awaited event sink), sequential tool execution, and a per-request `ModelHandle` abstraction that wraps model routing (direct provider calls, Cloudflare AI Gateway on the platform's account, or "BYOK" routing through the user's own AI Gateway) and folds in AI Gateway log-route/cost-accounting metadata (raw/github_doc-plans-pi-impl-md.md). This is the mechanism behind "use any model, and control what it costs" described in the announcement blog: every inference call runs through Cloudflare AI Gateway, so an organization can decide which models are available, attribute spend per person/team/workspace, and set budgets and rate limits (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md — see [[concepts/ai-gateway-billing]]). **Admin-configurable "soft" customization vs. env-var-driven security config.** AGENTS.md draws a deliberate line: `AdminConfig` (agent instructions, banners/theme, which connectors/resources are offered) is a deployment's "soft" customization surface, editable by an admin through the `/admin` panel and persisted by an `AdminSettings` Durable Object. Authentication and authorization configuration (sign-in providers, password-login toggle) is *deliberately not* part of `AdminConfig` — it stays env-var driven specifically so it "can't be changed by a compromised admin session" (raw/github_doc-agents-md.md). See [[concepts/oauth-signin]]. ## Key Parameters - **Durable Objects**: every workspace is its own Durable Object (raw/github_doc-readme-md.md). - **Dynamic Workers + Facets**: every gadget runs in a Dynamic Worker Facet; Gatekeepers install facets into each workspace (raw/github_doc-readme-md.md). Runtime features added specifically to support this project. - **Cap'n Web RPC**: the client-server communication protocol used throughout, both between workshop-frontend and workshop-backend and between a gadget's client and server code (raw/github_doc-agents-md.md; see [[concepts/gadgets]]). - **`packages/workshop-backend`**: the kernel, held to the highest review bar in the codebase (raw/github_doc-agents-md.md). - **Release pipeline** (`scripts/release/`): `build-release.mjs` bundles every deployable worker byte-identically and produces a release manifest; `upload-release.mjs` mirrors it to R2 content-addressed (with an optional `--candidate` gate for verification before `promote-release.mjs` makes it live); the manifest is covered by a golden-file test (raw/github_doc-agents-md.md). - **`pi-ai` / `pi-agent-core`** (pinned `0.83.0`): the model-provider abstraction underlying the agent loop, replacing the Vercel AI SDK (raw/github_doc-plans-pi-impl-md.md). ## When To Use - Understanding why Cloudflare OS requires Cloudflare Workers primitives (Durable Objects, Dynamic Workers, Facets) rather than being a generic Node.js app — this is load-bearing to both the sandboxing story ([[concepts/gatekeepers]], [[concepts/gadgets]]) and the multiplayer/state story. - Orienting a new contributor or forker in the monorepo before making changes — AGENTS.md's package map is the authoritative index (raw/github_doc-agents-md.md). - Reasoning about model routing, cost, and BYOK behavior — see the `ModelHandle`/AI Gateway routing modes in `plans/pi-impl.md` and [[concepts/ai-gateway-billing]]. ## Risks & Pitfalls - `workshop-backend` is explicitly held to a higher review bar than other packages — AGENTS.md asks contributors to keep diffs there "small and elegant," to doc-comment every exported member of the shared API, and never to introduce a hand-written interface mirroring an RPC interface plus an `as unknown as` cast (raw/github_doc-agents-md.md). Treating it like an ordinary package risks review friction. - The pi-ai migration plan is explicit that "the project is in early alpha — minor behavioral regressions are acceptable and will be fixed as discovered," and that no parity test harness or compatibility shims for the old AI-SDK path should be built (raw/github_doc-plans-pi-impl-md.md). This is a useful signal about the project's general engineering posture at this snapshot, not just about that one migration. - A capability only becomes "ambient" (auto-injected into an agent's environment) through explicit user/admin configuration — AGENTS.md states a Gatekeeper "must never assert its own ambience" (raw/github_doc-agents-md.md), reinforcing the capability-based security model described in [[concepts/gatekeepers]]. - Self-hosting fully off Cloudflare's platform (pure `workerd` on your own servers) was marked "COMING SOON" at this snapshot, despite being architecturally supported (raw/github_doc-readme-md.md). ## Related Concepts - [[concepts/cloudflare-os-overview]] — the two senses of "operating system" this page elaborates on technically. - [[concepts/gatekeepers]] — the "device driver" layer and capability-based security model. - [[concepts/gadgets]] — the "process" layer running in Dynamic Worker Facets. - [[concepts/blueprints]] — the "executable" layer. - [[concepts/mcp-support]] — `mcp-shared`'s role behind the two MCP gatekeepers. - [[concepts/ai-gateway-billing]] — model routing and cost accounting built on this architecture. - [[concepts/getting-started]] — running this architecture locally or deploying it. ## Sources - raw/github_doc-agents-md.md - raw/github_doc-plans-pi-impl-md.md - raw/github_doc-readme-md.md - raw/github_doc-packages-backend-utils-readme-md.md - raw/github_doc-packages-mcp-shared-readme-md.md - raw/github_doc-packages-workshop-frontend-readme-md.md --- title: "Blueprints" type: concept tags: [blueprints, gadgets, sharing, developer, foundational] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-docs-blueprints-md.md", "raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Blueprints ## Definition A blueprint lets a user share a [[concepts/gadgets|gadget]]'s source code so others can create their own independent gadget instances from it — "a blueprint captures the code but not the chat history, SQLite storage, or credentials. Each gadget created from a blueprint gets its own bindings, storage, and chat history" (raw/github_doc-docs-blueprints-md.md). The README's operating-system analogy calls blueprints "executables," the counterpart to gadgets as "processes" (see [[concepts/architecture]]). It is explicitly analogous to a template: "the blueprint author publishes a reusable gadget design, and anyone with the link can stamp out their own copy, pointing it at their own resources" (raw/github_doc-docs-blueprints-md.md). ## How It Works **What a blueprint captures, and what it doesn't.** A blueprint captures: source code (a snapshot of the gadget's committed Yjs document, stripped of edit history — one insert operation per file, a minimal encoding); binding requirements (what type of connection each named binding needs — gatekeeper, AI model, or agent spawner — and how to configure it, with no credentials or live connections included); and metadata (title, description, optional screenshot, author, version, timestamps). It does **not** capture the gadget's SQLite storage contents, its AI chat/edit history, or any live connections or credentials — only the *shape* of each binding (raw/github_doc-docs-blueprints-md.md). **Binding types (three, matching the three kinds of gatekeepers).** `gatekeeper` — an external resource connection (e.g. Google Drive, a REST API); the blueprint records the adapter name and a URL pattern describing the expected resource, and the instantiating user picks a connected account and configures a matching resource. `aiModel` — a language-model binding; the blueprint may suggest a specific provider/model, but the instantiating user picks from their own configured models. `agentSpawner` — an agent-spawner binding; the blueprint carries over the spawner configuration (prompt types, env restrictions), and the user only chooses which model the spawner should use (raw/github_doc-docs-blueprints-md.md). **Binding annotations.** Before publishing, the author can add optional blueprint annotations per named binding: a friendly display name (defaulting to the current resource title, while the binding name itself stays the stable code-facing key), optional helper description text, and an optional suggested value (a specific resource URL or model name) that remains a suggestion, not a requirement. These are configured in the Blueprint modal in the gadget editor, and stored on the `GatekeeperRecord` as `blueprintAnnotation` (raw/github_doc-docs-blueprints-md.md). **Storage architecture: one-way propagation across three stores.** Gadget DO → User DO → Workers KV. The Gadget DO's `blueprints` collection is the authoritative source, storing `BlueprintGadgetRecord` (full metadata, the exported code version, and a `dirty` flag for tracking propagation failures). The User DO's `blueprints` collection is a denormalized copy for efficient listing (`BlueprintUserRecord`), letting a user audit/manage their blueprints even if the source gadget is later deleted. Workers KV (`BLUEPRINTS` namespace) is the public-facing lookup store, keyed by blueprint hex ID, and is what `PublicApi.getBlueprint()` reads. Blueprint **code content** itself lives separately, in an R2 bucket (`BLUEPRINT_CONTENT`), keyed `/`, as a Yjs V2-encoded full-state document; old versions are retained on update to avoid race conditions, and all R2 versions are cleaned up on deletion. The `dirty` flag is set before propagation begins and cleared only once all writes succeed; a failure leaves it set and the UI shows a "Retry" button (raw/github_doc-docs-blueprints-md.md). **Identifiers.** Each published blueprint gets a 128-bit random hex ID, generated server-side, and is shared via `https:///blueprint/`. Anyone with the link can view a blueprint's metadata (title, description, author, required bindings) without authenticating; creating a gadget from it requires authentication. A blueprint is always owned by the gadget's owner regardless of which collaborator creates the blueprint (raw/github_doc-docs-blueprints-md.md). Bundled blueprints (below) are the exception, carrying stable readable IDs instead of random hex ones, and have no owning user at all. **Instantiation flow, user-driven.** Opening `/blueprint/` shows the Blueprint Landing Page: `PublicApi.getBlueprint()` fetches metadata unauthenticated; the page shows title, description, screenshot, author, version, and a summary of required bindings; an unauthenticated visitor sees a "Log in to create a gadget" prompt; once authenticated, the user enters configure mode and assigns each required binding (pick a connected account + resource for gatekeeper bindings, pick a model for AI-model bindings, pick a model or none for agent-spawner bindings). Clicking "Create Gadget" calls `AuthenticatedApi.newGadgetFromBlueprint()`, which reads the blueprint from KV and its code from R2, creates a new Overseer Durable Object initialized via `initializeFromBlueprint`, creates gatekeepers from the user's binding assignments (pipelined for performance), and redirects to the new gadget. The new gadget is fully independent — its own storage, chat history, and bindings — and there is currently no mechanism for automatic updates from the blueprint back to already-created instances, "though the Yjs-based storage format could support this in the future" (raw/github_doc-docs-blueprints-md.md). **Instantiation by the agent.** The agent can also instantiate a blueprint as an additional gadget inside an existing workspace. A `listBlueprints` tool lists the blueprints available to the workspace owner — the deployment's standard formats first (marked preferred), then the user's own published blueprints, their library, and the deployment's featured set — as plain formatted text, since there is no search index and the model is expected to scan the list itself. Passing a `blueprintId` to the `createGadget` tool creates the new gadget from the blueprint's code (instead of empty); the gadget is provisional to the chat like any agent-created gadget (see [[concepts/gadgets]]), and the blueprint's files ride the same `changes` message as the creation, so accept/revert covers both together. Bindings are **not** auto-assigned on this path — the tool result describes what the blueprint expects, and the agent wires bindings up itself (via `setGadgetBinding`, requesting connections as needed) or asks the user to add AI-model/agent-spawner bindings from the Connections panel (raw/github_doc-docs-blueprints-md.md). **Export/import format — the `.gadget` file.** A blueprint can be downloaded from `/blueprint/` as a `.gadget` file and uploaded elsewhere into another Workshop instance. The format is a simple internal binary container: an 8-byte magic number `0xec2e2d3a2300e317`, a 4-byte format version (`1`), a 4-byte JSON metadata length, an 8-byte raw content length, the JSON-encoded `BlueprintMetadata`, then the raw content bytes (the same gzip-compressed Yjs snapshot already stored in R2). Metadata is capped at 64 KiB and the stored snapshot payload at 32 MiB, specifically so a malformed archive can't force unbounded allocation in the worker. Only `BlueprintMetadata` is included — not `ownerId`, `gadgetId`, or screenshot bytes (screenshots are stored separately and cleared on import). Import/export stream the content bytes directly to/from R2 via `pipeTo()` rather than buffering the whole archive in memory (raw/github_doc-docs-blueprints-md.md). Uploading a `.gadget` file creates a brand-new local blueprint ID on the target instance, stores the snapshot in that instance's own R2/KV, and records ownership under the importing user, while preserving the original author metadata (raw/github_doc-docs-blueprints-md.md). **Discovery: Explore, home page, and pinning.** The Explore page (`/explore`) surfaces admin-featured blueprints, meant to show users what's possible. The home page has a Blueprints tab listing both what a user has published and what's in their library. Users can pin blueprints to keep them at the top of that tab; pinning a public blueprint not yet in the library adds it to the library first. Library entries come in two forms: **saved by reference** (`addBlueprintToLibrary()` — a cached metadata copy pointing back at the original publisher's blueprint; removing it deletes only the local library entry) and **uploaded** (`importBlueprint()` from a `.gadget` archive — creates a genuinely new local blueprint on the current deployment; removing it deletes the imported content too) (raw/github_doc-docs-blueprints-md.md). **Featured blueprints and admin curation.** Deployments can configure admin usernames via the backend's `ADMINS` binding. Admins get `adminIsBlueprintFeatured()` and `adminSetBlueprintFeatured()`. Only gadget-backed *published* blueprints are featureable — uploaded/imported library blueprints are intentionally excluded. Featured state is split: the authoritative `featured` bit lives on the owning user's blueprint record in their User DO, while a singleton `AdminSettings` Durable Object mirrors current public metadata for featured blueprints and writes a KV snapshot that `listFeaturedBlueprints()` reads (raw/github_doc-docs-blueprints-md.md). **Formats: promoted blueprints that appear in the "+" menu.** A *format* is an ordinary blueprint the deployment has promoted (admin curation via `AdminConfig.formats`) so it shows up as e.g. "New Doc" or "New Slides" in the composer's `+` menu and in the list the agent is told to prefer — promotion changes nothing about the blueprint itself. A blueprint can declare `BlueprintMetadata.output`: a grouping `id`, a noun/plural ("Doc"/"Docs"), and an icon from a closed `OUTPUT_ICONS` set; any gadget instantiated from it inherits this, which is what the workspace tab, chat cards, and the Outputs page draw from. Declaring `output` is presentation-only and grants nothing — any user can publish a blueprint that calls itself a "Document"; being *offered* as a standard format is the separate, admin-curated decision, which an admin can also override per-field (`FormatCuration.overrides`), reaching gadgets the agent builds as well as ones made from the menu (raw/github_doc-docs-blueprints-md.md). **Bundled format blueprints — committed as data.** `packages/workshop-backend/format-blueprints/` holds the output-format blueprints a deployment ships out of the box, so a fresh deployment can write a doc or build a deck without anyone building one first. A blueprint here is two files sharing a stem: `.gadget` (the code and required bindings — "what the blueprint *does*") and `.json` (`blueprintId`, title, description, `output`, author, `revision` — "what a human *curates*"). The installer writes the sidecar's values over whatever the archive itself carries, so the archive's own embedded title/author are inert. `scripts/build-format-blueprints.mjs` bundles the directory into a generated module (`src/generated/format-blueprints.ts`); nothing wakes on deploy, so a fresh deployment is provisioned by its **first visitor**'s `/api` request, which installs any blueprint whose manifest fingerprint (title, description, author, revision, output) has changed. Each bundled blueprint is promoted only once ever — a later upgrade never undoes an admin's subsequent removal or overrides (raw/github_doc-docs-blueprints-md.md; raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). To update a bundled blueprint's code: build it in a real Workshop, export it, then run `pnpm import:format-blueprint ~/Downloads/Gadgets-Doc-v4.gadget format.document` — this rewrites the archive, bumps `revision` in the sidecar, rebuilds the generated module, and reports a diff, e.g.: ``` Updated workspace-docs.gadget (format.document) code 23668 -> 24489 bytes (7c5413e5a482) bindings (none) version 3 -> 4 revision 2 -> 3 (workspace-docs.json) ``` (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md) To add a new bundled format, `pnpm import:format-blueprint ~/Downloads/Brief.gadget --new acme-brief` writes the sidecar from the export and prints the fields worth hand-editing before deploy — chiefly `output`'s noun/plural/icon and `output.id`, which should be kept **generic** (e.g. `document`, not `acme-brief`) since the Outputs page groups by it (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). `blueprintId` defaults to the passed name and is the install key — reimporting the same id updates in place, while changing it after deployment promotes a second, separate format rather than renaming the first (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). A fork can ship an entirely different default set via `FORMAT_BLUEPRINTS_DIR=../../acme-formats pnpm build`, which *replaces* rather than adds to the bundled directory — deliberately, since this repo is usually consumed as a submodule and adding/deleting files in it directly would conflict on every update. Two lighter options need no build change: promoting your own ordinary published blueprints via the admin Formats panel (and disabling the bundled ones you don't want), or pointing `FORMAT_BLUEPRINTS_DIR` at an empty directory to ship none at all until an admin promotes something (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). **Orphaned blueprints.** A blueprint can outlive its source gadget — if the gadget is deleted, its blueprints remain accessible via KV and R2. A user manages these through `AuthenticatedApi.listOwnBlueprints()` (reads from the User DO) and deletes them via `deleteOrphanedBlueprint()`, which cleans up KV, R2, and the User DO record directly, bypassing the now-nonexistent Gadget DO (raw/github_doc-docs-blueprints-md.md). **Creation specs.** Each gatekeeper stores a `GatekeeperCreationSpec` recording how it was originally created — vendor ID for gatekeeper bindings, provider/model name for AI-model bindings, or the full spawner config for agent-spawner bindings. `collectBindingMetadata` combines this with the blueprint annotation to produce the `BlueprintBinding` records stored in a blueprint (raw/github_doc-docs-blueprints-md.md). ## Key Parameters - **128-bit random hex blueprint ID** vs. **stable readable bundled-format IDs** (e.g. `format.document`) (raw/github_doc-docs-blueprints-md.md). - **Three binding types**: `gatekeeper`, `aiModel`, `agentSpawner` (raw/github_doc-docs-blueprints-md.md). - **Three-store propagation**: Gadget DO → User DO → Workers KV, plus code content in R2 (raw/github_doc-docs-blueprints-md.md). - **`.gadget` archive**: magic number `0xec2e2d3a2300e317`, format version `1`, metadata cap 64 KiB, content cap 32 MiB (raw/github_doc-docs-blueprints-md.md). - **`dirty` flag**: tracks propagation-failure recovery via a UI "Retry" button (raw/github_doc-docs-blueprints-md.md). - **`revision`** (bundled blueprints only): the reinstall trigger tied to archive-byte changes, since the manifest fingerprint can't otherwise see raw content changes (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). - **`FORMAT_BLUEPRINTS_DIR`**: env var letting a fork point the bundled-format build at its own directory instead of this repo's (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). ## When To Use - Sharing a *design*, not a live document — use a blueprint (or its `.gadget` export) when you want recipients to get their own independent, freshly-resourced copy rather than collaborating on the same instance (contrast with [[concepts/sharing]], which shares the live gadget itself). - Standardizing how your organization starts new work — promoting an internal blueprint to a "format" puts it in the composer's `+` menu for everyone (raw/github_doc-docs-blueprints-md.md). - Forking the deployment's default document/slide set — ship your own `format-blueprints/` directory via `FORMAT_BLUEPRINTS_DIR` instead of editing the upstream submodule in place (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). ## Risks & Pitfalls - A blueprint deliberately excludes credentials and live connections — anyone instantiating one must reconnect and reconfigure every binding themselves; this is a security feature, not a gap, but it means blueprints are not a way to hand off "everything wired up and ready" (raw/github_doc-docs-blueprints-md.md). - Changing a bundled blueprint's `blueprintId` after a deployment has already installed it does not rename the existing format — it promotes a second format while the original stays in the New menu, updated by nothing thereafter (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). - Forgetting to bump `revision` after hand-editing a bundled blueprint's archive is described as "invisible" — everything still builds and deploys, but the old blueprint quietly stays installed; the `pnpm import:format-blueprint` script automates this specifically to avoid that mistake (raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md). - Declaring `output` on a blueprint is purely presentational and grants no special status — it can be a source of confusion since "any user can publish a blueprint calling itself a Document" whether or not it's actually offered as a deployment-standard format (raw/github_doc-docs-blueprints-md.md). - There is currently no mechanism to push updates from a blueprint back to gadgets already instantiated from it (raw/github_doc-docs-blueprints-md.md) — treat each instantiated gadget as permanently diverged from its source blueprint's future versions. ## Related Concepts - [[concepts/gadgets]] — the thing a blueprint is a template *of*. - [[concepts/sharing]] — sharing a live gadget instance, contrasted with sharing a blueprint template. - [[concepts/cloudflare-os-overview]] — blueprints as "executables" in the OS analogy. - [[concepts/architecture]] — `packages/workshop-backend` and its `format-blueprints/` directory. - [[concepts/getting-started]] — the built-in slides blueprint referenced in the "what to try" prompts. ## Sources - raw/github_doc-docs-blueprints-md.md - raw/github_doc-packages-workshop-backend-format-blueprints-readme-md.md --- title: "Cloudflare OS Overview" type: concept tags: [overview, foundational, user, developer, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-readme-md.md", "raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md", "raw/github_doc-agents-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Cloudflare OS Overview ## Definition Cloudflare OS is an open-source "operating system" for AI productivity, originally built for internal use at Cloudflare, where it is used daily by a large portion of the workforce across engineering, sales, and other functions (raw/github_doc-readme-md.md). It is not a traditional computer operating system. The README explicitly says the term "operating system" is used in two senses: (1) an operating system for *the company* to be productive with AI in a way that is safe, "so that the security team can sleep at night," and (2) an operating system for AI *workloads*, analogous to how a traditional OS manages compute workloads (raw/github_doc-readme-md.md). The project is open source specifically so other organizations can copy and customize it — the stated goal is not that a company runs "Cloudflare OS" but that it makes it "*Your Company* OS" (raw/github_doc-readme-md.md). ## How It Works Cloudflare OS provides three things, referred to in this KB as its three pillars (raw/github_doc-readme-md.md): 1. **An agent chat UI** where users ask agents to do tasks, preloaded with knowledge about how the company operates. See [[concepts/agent-chat-and-company-knowledge]]. 2. **Sandboxed application development** — asking agents to build "gadgets" (small personal apps) and safely sharing what's built with others. See [[concepts/gadgets]] and [[concepts/sharing]]. 3. **A security framework called Gatekeepers**, which applies guardrails to both agents and apps so that non-technical users can safely "go nuts" without anything bad happening. See [[concepts/gatekeepers]]. The Cloudflare blog announcement frames the same three pillars slightly differently as: an agent workspace grounded in company context and skills with an isolated runtime for agents to write and run code; a new security and governance framework for safe access to internal data and services; and a platform for personal, modifiable apps that people can build, share, and continue changing (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). These map directly onto the README's agent chat / Gatekeepers / gadgets framing. Cloudflare OS is described in the README as "actually analogous to an operating system on a technical level," with an explicit component-to-component mapping: `packages/workshop-backend` is the kernel, `packages/gatekeeper-*` are device drivers, `packages/workshop-frontend` is the shell, gadgets are processes, blueprints are executables, and agents are called out as something traditional OSes lack an analog for (raw/github_doc-readme-md.md). See [[concepts/architecture]] for the technical substrate this runs on. The blog post adds historical context: Cloudflare gave every employee access to a first version of Cloudflare OS in May of the release year, and thousands of people across every function used it daily for documents, slides, automating repeatable tasks, and building small apps. That first version's limitations — static apps rather than live software connected to internal systems, and a security gap where MCP access told the platform *which tools* an agent could call but not *which underlying resources* it had observed — motivated rebuilding the security and governance model into the platform itself rather than leaving it to each app or agent builder (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). ## Key Parameters - **The two senses of "operating system"**: company-productivity-and-safety OS vs. AI-workload-management OS (raw/github_doc-readme-md.md). - **Self-host thesis**: deploy into your own Cloudflare account, connect your own internal systems and Access policies, and customize the interface and add internal Gatekeepers "without changing the core product" — the goal is "Your Company OS," not adopting Cloudflare's own instance (raw/github_doc-readme-md.md; raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). - **Two repositories are released**: the Cloudflare OS core, and an example deployment repo reflecting how Cloudflare runs it internally, which consumes the core without patching it (raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md). The README separately points to a `cloudflare-os-starter` repo for more sophisticated deployment with custom gatekeepers/code changes (raw/github_doc-readme-md.md). - **v2, August 2026 early access**: this repository is described as "actually version 2, a complete rewrite" that took what was learned from v1 and put it on a new foundation. As of the August 2026 release it is "very capable, but still has many rough edges" — an explicit early-access disclaimer (raw/github_doc-readme-md.md). - **Built by the Workers team, on Workers**: Cloudflare OS is built by the people who built Cloudflare Workers, and makes heavy use of Durable Objects, Dynamic Workers, and Facets — several of which were added to the Workers Runtime specifically to support this project (raw/github_doc-readme-md.md). See [[concepts/architecture]]. ## When To Use - Evaluating whether to adopt Cloudflare OS as an internal AI platform for a company (not just for individual coding), where both "safe for the security team" and "flexible enough for non-technical users to build tools" both matter. - Understanding the product's own self-description before diving into any specific mechanism (gadgets, blueprints, Gatekeepers) — this page is the entry point the other five pages in this batch link back to. - As of the August 2026 snapshot, treat it as early access: expect rough edges, and expect some documented plans (e.g. [[concepts/architecture]]'s pi-ai migration, multi-gadget workspaces) describing in-flight or recently-landed work rather than long-stable behavior. ## Risks & Pitfalls - The "early access" warning is explicit and should not be glossed over: the README states there are "many rough edges" as of the August 2026 release (raw/github_doc-readme-md.md). - AGENTS.md describes the project as "building a platform for 'vibe coded' personal applications and AI agents that run inside a strong sandbox" (raw/github_doc-agents-md.md) — the security model (Gatekeepers, capability-based access) is not incidental but central to the pitch; deployments that skip it lose the "safe by default" premise. - At time of this snapshot the project states it is "not seeking outside contribution" beyond small, trivially-verified PRs (raw/github_doc-readme-md.md) — relevant to anyone planning to fork-and-upstream rather than fork-and-maintain. - Deploying to your own Cloudflare account via `workerd` on your own servers (rather than Cloudflare's managed platform) was, at this snapshot, marked "COMING SOON" with documentation and tooling still in progress (raw/github_doc-readme-md.md). ## Related Concepts - [[concepts/getting-started]] — running the stack locally or deploying it. - [[concepts/architecture]] — the Workers substrate and monorepo package structure. - [[concepts/agent-chat-and-company-knowledge]] — the agent chat UI pillar. - [[concepts/gadgets]] — the sandboxed app pillar. - [[concepts/gatekeepers]] — the security framework pillar. - [[concepts/blueprints]] — how gadgets become shareable templates. - [[concepts/sharing]] — how gadgets and their outputs are collaborated on. ## Sources - raw/github_doc-readme-md.md - raw/web_community-cloudflare-os-an-open-platform-for-agents-apps-and-work-the-.md - raw/github_doc-agents-md.md --- title: "Gadgets" type: concept tags: [gadgets, sharing, foundational, user, developer] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-plans-multi-gadget-md.md", "raw/github_doc-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Gadgets ## Definition A gadget is a small, personal application that a user asks an agent to build, running in its own sandbox rather than as a shared multi-tenant SaaS service. The README frames this as a fundamentally different model of software: "every user runs their own copy of the productivity apps they use." When you create a slide deck in Cloudflare OS, "you are not calling out to some SaaS software running in the cloud. The system creates a *private instance* of the slide deck software *just for you*" — that instance is the gadget, and it runs in a separate sandbox from everyone else's (raw/github_doc-readme-md.md). Gadgets are the second of Cloudflare OS's three pillars (see [[concepts/cloudflare-os-overview]]) and correspond to "processes" in the README's operating-system analogy (raw/github_doc-readme-md.md; see [[concepts/architecture]]). ## How It Works **Two profound effects of per-user private instances**, per the README: (1) it's structurally impossible for a shared bug in the slide-deck app to leak your slides to an attacker, because the sandbox controls all access to your private instance; and (2) you can freely ask your agent to modify the code if a feature is missing, and because of (1) it's "totally safe to do so" (raw/github_doc-readme-md.md). **Client/server split, communicating over Cap'n Web RPC.** When an agent builds an app, it writes two parts: client code that renders the UI in the browser, and server code that stores state and implements behavior. The client and server are required to communicate via Cap'n Web RPC — "low-boilerplate," so an agent basically defines a server method and calls it from the client as a normal function call. Because that server necessarily exposes an easy-to-understand API, the same API is directly callable by an agent — no separate MCP server or custom agent loop is needed for a human-built tool to also be AI-usable (raw/github_doc-readme-md.md). **Sandboxing mechanics.** Each gadget's server runs in a Dynamic Worker with internet access disabled by default; it can only reach specific external resources explicitly designated via Workers Bindings. The client code runs in a sandboxed iframe that can only talk to its own server, over a Cap'n Web RPC session carried by `postMessage()` to the parent frame — the iframe is otherwise blocked from reaching the internet directly, "to the maximum extent allowed by browsers," via Content-Security-Policy and iframe sandbox settings (raw/github_doc-readme-md.md). **Real-time multiplayer for free.** Every gadget is backed by a Durable Object, Cloudflare's stateful serverless primitive, which the README says makes real-time multiplayer collaboration easy enough that "the coding agent just implements it by default, without being asked" (raw/github_doc-readme-md.md). Sharing a gadget lets collaborators see each other's actions in real time, the same way an online office suite shares a document — see [[concepts/sharing]]. **Capability-based access, introduced not ambient.** By default a gadget (like an agent) has access to nothing; a resource must be explicitly introduced via a [[concepts/gatekeepers|Gatekeeper]] before the gadget can use it (raw/github_doc-readme-md.md). This is the same introduction model described in [[concepts/agent-chat-and-company-knowledge]]. **Multiple gadgets per workspace, as "workpieces."** `plans/multi-gadget.md` documents the design that moved Cloudflare OS from a one-gadget-per-workspace model to a multi-gadget one. Before this change, "workspace" and "gadget" were the same thing — a single Overseer Durable Object held one code history, one gatekeepers table, and one hard-coded facet (raw/github_doc-plans-multi-gadget-md.md). The plan introduces **workpieces**: a workspace contains numbered workpieces in a single shared ID namespace, of which v1 has two types — gadgets and gatekeepers — with the concept deliberately left open for future types (e.g. a gatekeeper-provided UI displayed in the workspace panel, or a mounted external object like a checked-out git branch exposed as an editable file tree) (raw/github_doc-plans-multi-gadget-md.md). Key mechanics of the multi-gadget design: - **Shared ID counter, separate tables.** Gadgets and gatekeepers each get their own storage collection, but both allocate IDs from one shared counter, so IDs are unique across both and facet names (`gadget${id}`, `gatekeeper${id}`) can never collide (raw/github_doc-plans-multi-gadget-md.md). - **Per-gadget Yjs roots.** Each gadget's files live in a named root of the workspace's shared Y.Doc (`ydoc.getMap(rootNameFor(id))`), keyed by the gadget's numeric workpiece ID — except the one migrated legacy gadget, which keeps the original unnamed root for zero-migration compatibility (raw/github_doc-plans-multi-gadget-md.md). - **Bindings live on the gadget, as edges.** A binding (a named connection from a gadget to a Gatekeeper or another workpiece) is recorded as `{target, blueprintAnnotation?}` on the gadget's own record, not on the Gatekeeper — so two gadgets can bind the same Gatekeeper with different names and different [[concepts/blueprints|blueprint]] annotations (raw/github_doc-plans-multi-gadget-md.md). - **Chats are workspace-scoped, not gadget-scoped**, specifically so one chat thread can create and wire multiple gadgets together in one conversation, e.g. "build me a slide deck gadget and wire the viewer gadget to it" (raw/github_doc-plans-multi-gadget-md.md). - **Zero gadgets is a valid state.** Once multi-gadget support ships fully, a workspace starts with no gadgets at all, and the agent creates the first one explicitly via a `createGadget` tool (raw/github_doc-plans-multi-gadget-md.md). - **`createGadget` is provisional and crash-safe.** A gadget an agent creates mid-chat is marked `pending: {chatId}` and only becomes durably visible to the rest of the workspace when the chat's changes are merged; if the chat's changes are reverted, the gadget is removed. This mirrors how code edits are already staged and accepted/reverted, and the plan describes specific crash-recovery logic (`reconcilePendingGadgets`) so a worker restart mid-creation can't leave an orphaned or duplicated gadget (raw/github_doc-plans-multi-gadget-md.md). - **Named bindings, not numeric ones, for agents.** A later part of the same plan ("Part 2: Named chat bindings") replaces numeric addressing (`env.gadgets[id].BINDING`) with a per-chat named binding map, so an agent addresses everything as `env.NAME` — workpiece IDs become "totally invisible to agents." Names are assigned either by the agent itself (e.g. required `bindingName` parameters on `createGadget` and `requestConnection`) or, for pasted resources, by a quick LLM call at the start of an agent turn, with a documented ID/title fallback if that call fails or times out (raw/github_doc-plans-multi-gadget-md.md). ## Key Parameters - **Dynamic Worker Facet**: the runtime unit a gadget's server executes in; internet access disabled by default (raw/github_doc-readme-md.md). - **Sandboxed iframe**: the runtime unit a gadget's client executes in, restricted to a Cap'n Web session with its own server (raw/github_doc-readme-md.md). - **Durable Object**: the stateful backing for a gadget, giving it a private SQLite database (per the announcement blog) and enabling real-time multiplayer (raw/github_doc-readme-md.md). - **Workpiece ID**: the shared numeric namespace covering both gadgets and gatekeepers in a workspace (raw/github_doc-plans-multi-gadget-md.md). - **`BindingRecord`**: `{target: WorkpieceId, blueprintAnnotation?}` — the edge record representing one named connection from a gadget to another workpiece (raw/github_doc-plans-multi-gadget-md.md). - **`defaultGadgetId`**: a back-compat singleton pointing at the legacy/auto-created gadget in a workspace, used to resolve gadget references left absent by older tool calls; never reassigned once set (raw/github_doc-plans-multi-gadget-md.md). - **`pending: {chatId, sequence?}`**: the marker distinguishing a gadget (or binding) that exists only provisionally within an open chat from one that has been merged into the workspace's mainline state (raw/github_doc-plans-multi-gadget-md.md). ## When To Use - Any time a user wants a piece of custom, private software (a document, a dashboard, a small game) without provisioning a shared SaaS account for it — "Make a collaborative whiteboard app" or "Make a tic tac toe game" from [[concepts/getting-started]] are exactly this case. - When a task needs both a durable, shareable artifact *and* the ability for an agent to keep modifying it later — a gadget, unlike a static export, stays live and editable by further agent prompts (raw/github_doc-readme-md.md). - When multiple related pieces of custom software need to be built and wired together in one conversation — the multi-gadget workspace model exists specifically to let one chat thread create and connect several gadgets at once (raw/github_doc-plans-multi-gadget-md.md). ## Risks & Pitfalls - The plan is explicit that some pieces of the multi-gadget design were left unimplemented as of the documented phasing: there is no UI-initiated "new gadget" affordance (gadget creation is agent-tool-only), no UI for exporting blueprints from a non-default gadget, and no way to share individual gadgets without sharing the whole workspace (raw/github_doc-plans-multi-gadget-md.md). Do not assume these exist without checking current source. - Deleting a gadget clears its Yjs root but does not stop stale writes from an old client, in-flight update, or later-merged chat branch from writing content back into the tombstoned root — the plan calls this "resurrection," and treats it as harmless *because* the registry (not doc content) is the enumeration source of truth: content in a root with no registry entry is never listed, loaded, executed, or rendered (raw/github_doc-plans-multi-gadget-md.md). - Deleting the workspace's `defaultGadgetId` gadget never reassigns the singleton — subsequent absent-ID references (old replayed tool calls, persisted restore params) fail with an explicit error rather than silently retargeting to a different gadget (raw/github_doc-plans-multi-gadget-md.md). - A sharing-taint (from `prohibitAllSharing`) is workspace-wide in v1: one tainted gadget locks down sharing of every gadget in the workspace, an explicitly accepted but user-visible consequence (raw/github_doc-plans-multi-gadget-md.md). See [[concepts/sharing]]. - Client sandboxing is "to the maximum extent allowed by browsers" (raw/github_doc-readme-md.md) — a phrasing that signals browser-level CSP/iframe-sandbox limits are a real boundary condition, not an absolute guarantee. ## Related Concepts - [[concepts/cloudflare-os-overview]] — gadgets as one of the three pillars. - [[concepts/gatekeepers]] — the sandboxing and capability-introduction mechanism gadgets rely on. - [[concepts/sharing]] — how a built gadget (or a blueprint of it) is shared with others. - [[concepts/blueprints]] — how a gadget's code becomes a reusable template. - [[concepts/architecture]] — the Dynamic Worker/Durable Object/Facet substrate gadgets run on. - [[concepts/agent-chat-and-company-knowledge]] — the agent that builds and operates gadgets. ## Sources - raw/github_doc-plans-multi-gadget-md.md - raw/github_doc-readme-md.md --- title: "Gatekeepers" type: concept tags: [gatekeepers, security, capability-based-access, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-agents-skills-write-gatekeeper-skill-md.md", "raw/github_doc-agents-skills-write-gatekeeper-skeleton-md.md", "raw/github_doc-packages-gatekeeper-github-storage-schema-md.md", "raw/github_doc-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Gatekeepers ## Definition Gatekeepers are Cloudflare OS's flagship security framework: guardrails applied to both agents and Gadgets so that non-technical users can "safely 'go nuts'" without a security team losing sleep over it. The README describes them as "like supercharged MCP servers" and, in the OS-as-operating-system analogy, as the equivalent of device drivers — the layer that connects users and programs (Gadgets, agents) to external devices (third-party services). Concretely, a Gatekeeper is a Cloudflare Worker that mediates *all* access between a Gadget (or an agent acting directly) and one external service. Every time an agent or Gadget is introduced to an external resource — a GitHub repo, a Google Doc, a Slack workspace — a Gatekeeper is what actually stands between it and that resource. ## How It Works A Gatekeeper implements a three-tier hierarchy (see `packages/workshop-shared/src/gatekeeper.ts` for canonical interfaces): - **Vendor** (`GatekeeperVendor`, a `WorkerEntrypoint`) — one per external service; the top-level entry point (`describe()`, `connectAccount()`, `getSupportedResources()`, `getTypeScriptTypes()`). - **User** (`GatekeeperUser`, a `WorkerEntrypoint` with `ctx.props`) — represents one human's authenticated connection to that service (OAuth tokens, etc.). - **Instance** (`Gatekeeper`, a Durable Object facet of the Overseer) — a per-resource, per-Gadget binding that hands the Gadget a `Session` API for that specific resource (e.g. one repo, one document). A Gatekeeper is defined by seven responsibilities: 1. **Auth management** — manages "connected accounts": OAuth (or similar) token storage, refresh, and revocation, typically in a `UserAccount` Durable Object. 2. **API design** — wraps the service's native API in a TypeScript, Cap'n Web RPC-compatible interface designed around *capability-based security*: object-oriented, one interface per logical resource, so authority is limited simply by limiting which objects/methods a caller can reach. 3. **Fine-grained resource granting** — lets a user grant an agent access to a specific resource (one repo, one doc) rather than only coarse "everything" access, while still allowing broad grants when that makes sense. 4. **Logging & approvals** — every action must be logged via the `ApprovalQueue` API. Every side-effecting action must go through `submitAction()` and cannot actually run until `applyAction()` is called; every read must call `authorizeObservation()` before data is returned. 5. **Caching** — remote content can be cached in the Gatekeeper's own Durable Object storage (`this.ctx.storage.kv` for synchronous KV, `this.ctx.storage.sql` for full SQLite) both for performance and to smooth over awkward upstream API shapes. 6. **Simulation** — actions that are submitted but not yet approved are simulated as if they'd already happened, so an agent can keep working (and queue up more actions) without blocking on synchronous approval, and a human can batch-approve later. 7. **Observer verification** — when a Gadget bound to this Gatekeeper is shared, each new collaborator ("observer") must be checked, via `getVerifier()` / `addObserver()` / `removeObserver()`, to confirm they could have accessed the same data themselves. See [[concepts/observers]]. The README highlights point 6 as a genuine advance over typical human-in-the-loop tooling: traditional approval flows are *synchronous* — the agent stops and waits, which in practice pushes people toward `--dangerously-skip-permissions`-style auto-approval. Gatekeepers instead let the agent submit an action, get back a *simulated* result immediately, and keep going; the human approves (or rejects) a batch of queued actions whenever it's convenient. Logistically, each Gatekeeper runs as its own Worker; the repository ships several (GitHub, Google, Slack, Notion, Confluence, Email, Home Assistant, Spotify, Supabase, ZoomInfo, Scheduler, Cloudflare, plus the MCP and MCP Server Portal connectors — see [[concepts/mcp-support]]). This dovetails with Cloudflare OS's broader capability model: by default, an agent or Gadget has access to *nothing*. A user must explicitly "introduce" it to a specific resource (pasting a link, using an "add resource" picker), or the agent can request an introduction that the user approves or denies. This is deliberately unlike typical agent harnesses where MCP servers are configured upfront and become ambiently available in every chat. ## Key Parameters - **Resource granularity** — what a `getSupportedResources()` / `getGatekeeperClassFor()` implementation exposes as grantable units (e.g. whole service vs. single repo vs. single issue). See [[concepts/writing-a-gatekeeper]] for how this is designed. - **ApprovalQueue actions** — `submitAction()` / `applyAction()` / `rejectAction()` / `revertAction()`, plus `authorizeObservation()` for reads. - **Observer strategy per resource type** — A (private-only), B (single-unit ACL check), C (data-set tracking), or D (low-stakes no-op). See [[concepts/observers]]. - **Hooks** — persistent push-notification registrations (`HookController` / `HookInitiator`) for services that emit events (inbound email, webhooks, chat messages); subject to the same approval model as everything else. ## When To Use Any time a Gadget or agent in Cloudflare OS needs to reach an external system — a SaaS API, a self-hosted service, or a Model Context Protocol server. If a connector for the target service doesn't already exist among the shipped Gatekeepers, a new one is authored following [[concepts/writing-a-gatekeeper]]. ## Risks & Pitfalls - Skipping `ApprovalQueue` wiring on any API method that touches the outside world "breaks the gatekeeper security model" — the write-gatekeeper skill calls this out explicitly as critical. - `getVerifier()` / `addObserver()` / `removeObserver()` are mandatory on every Gatekeeper (even read-only or push-only ones) — code won't type-check without them, because sharing is independent of whether a Gatekeeper has actions. - The `types.d.ts` shown to the agent must never leak approval-queue or gatekeeper-internal details (caching, DO storage, OAuth); doing so breaks the illusion that actions apply immediately. - As of the August 2026 early-access release, Cloudflare OS v2 as a whole (and by extension the Gatekeepers ecosystem) is explicitly flagged as having "many rough edges." ## Related Concepts - [[concepts/writing-a-gatekeeper]] — the developer workflow for authoring a new Gatekeeper. - [[concepts/observers]] — how sharing a Gadget re-verifies each collaborator against every Gatekeeper it uses. - [[concepts/sharing]] — the collaborator/share-link system that observers plug into. - [[concepts/mcp-support]] — Gatekeepers that bridge to Model Context Protocol servers instead of a single bespoke API. - [[concepts/oauth-signin]] — authentication Gatekeepers double as sign-in providers. - [[concepts/ai-gateway-billing]] — billing is implemented via the Cloudflare Gatekeeper's connected account. - [[entities/gatekeeper-github]], [[entities/gatekeeper-google]], [[entities/gatekeeper-slack]], [[entities/gatekeeper-notion]], [[entities/gatekeeper-confluence]], [[entities/gatekeeper-email]], [[entities/gatekeeper-homeassistant]], [[entities/gatekeeper-spotify]], [[entities/gatekeeper-supabase]], [[entities/gatekeeper-zoominfo]], [[entities/gatekeeper-scheduler]], [[entities/gatekeeper-cloudflare]], [[entities/gatekeeper-mcp]], [[entities/gatekeeper-mcp-portal]] — the concrete Gatekeeper connectors shipped in the repository. ## Sources - raw/github_doc-agents-skills-write-gatekeeper-skill-md.md - raw/github_doc-agents-skills-write-gatekeeper-skeleton-md.md - raw/github_doc-packages-gatekeeper-github-storage-schema-md.md - raw/github_doc-readme-md.md (Gatekeepers section) --- title: "Getting Started with Cloudflare OS" type: concept tags: [overview, deployment, user, developer, foundational] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Getting Started with Cloudflare OS ## Definition Getting started with Cloudflare OS means either running the whole stack locally to try it out, or deploying a real instance into your own Cloudflare account. The README documents both paths, plus a short list of example prompts meant to demonstrate what the product does out of the box (raw/github_doc-readme-md.md). ## How It Works **Run locally.** After installing pnpm, running: ``` pnpm run-local ``` then visiting `http://localhost:8787` runs the whole stack locally on `wrangler` and `workerd`. The README is explicit that this is "not meant for production use, but is a quick way to see what the product does" (raw/github_doc-readme-md.md). Data from this local run is stored in a subdirectory named `.wrangler` (raw/github_doc-readme-md.md). **Deploy to your own Cloudflare account.** The simplest deployment path is an online flow at `https://os.cloudflare.app/deploy` (raw/github_doc-readme-md.md). For more sophisticated deployment — with your own Gatekeepers and potentially code changes — the README points to a separate starter repository, `https://github.com/cloudflare/cloudflare-os-starter` (raw/github_doc-readme-md.md). See [[concepts/architecture]] for how this starter repo relates to the core monorepo, and [[concepts/public-server-and-deployment]] for deployment specifics. **Deploy to your own server with `workerd`.** As of this snapshot this path is marked "COMING SOON" in the README: Cloudflare OS can in principle run entirely on `workerd` (the open-source Workers runtime — the same thing `run-local` uses under the hood), but documentation and tooling for smoothly deploying it that way were still being worked on. The README suggests reading the low-level `workerd` config documentation (or pointing an agent at it) for anyone wanting to attempt this early (raw/github_doc-readme-md.md). **Developing (two-terminal mode).** For active development, the frontend and backend run as two separate commands in two terminals: ``` pnpm dev-server pnpm dev-client ``` then visiting `http://localhost:3000` (raw/github_doc-readme-md.md). This differs from `pnpm run-local`, which runs the whole stack as a single command against `http://localhost:8787`. ## Key Parameters - **`pnpm run-local`** → `http://localhost:8787` — single-command full stack via wrangler/workerd, not for production, data in `.wrangler/` (raw/github_doc-readme-md.md). - **`pnpm dev-server` + `pnpm dev-client`** → `http://localhost:3000` — split frontend/backend dev loop (raw/github_doc-readme-md.md). - **`https://os.cloudflare.app/deploy`** — the guided online deploy-to-your-own-account flow (raw/github_doc-readme-md.md). - **`https://github.com/cloudflare/cloudflare-os-starter`** — the deployment starter repo for more sophisticated deployments involving custom Gatekeepers or code changes (raw/github_doc-readme-md.md). - **Configuring external services**: many Gatekeepers require OAuth client credentials for third-party services before they'll connect; the README notes provider setup is often deliberately non-trivial since the intended OAuth audience is developers, and links a per-package README for each of the listed connectors (GitHub, Google, Cloudflare, Supabase, Notion, Confluence, Email Workers, Home Assistant, Slack, Spotify, ZoomInfo) (raw/github_doc-readme-md.md). See the entity page for each connector. ### What to try The README's suggested first prompts, given verbatim, each designed to exercise a different part of the system: - `"Make slides for my upcoming meeting with a customer."` — uses the built-in slides [[concepts/blueprints|blueprint]]. - `"Make a collaborative whiteboard app."` — creates a new [[concepts/gadgets|gadget]] from scratch. - `"Make a tic tac toe game."` followed by `"I'll be X and you be O. I've made my first move. Your turn."` — demonstrates the agent playing inside a gadget it built. - `"Make an issue dashboard for this GitHub repo."` (attach a repo; requires the GitHub integration to be configured) — demonstrates a [[entities/gatekeeper-github|GitHub]] [[concepts/gatekeepers|Gatekeeper]] connection. - `"Fix the typos in this Google Doc."` (attach a doc; requires the Google integration to be configured) — demonstrates a Google Gatekeeper connection and the agent acting directly on an external resource. (raw/github_doc-readme-md.md) ## When To Use - First-time evaluation: use `pnpm run-local` to see the product in minutes without touching a real Cloudflare account. - Committing to a real internal deployment: use the `os.cloudflare.app/deploy` flow for a simple setup, or the `cloudflare-os-starter` repo when custom Gatekeepers or code changes are anticipated. - Actively contributing to or modifying the core product: use the two-terminal `dev-server`/`dev-client` loop rather than `run-local`, since it separates frontend and backend for faster iteration. ## Risks & Pitfalls - `pnpm run-local` is explicitly not meant for production use — treating it as a production deployment path would be a misuse of what the README documents (raw/github_doc-readme-md.md). - Several of the "what to try" example prompts require a configured integration first (the GitHub and Google examples both say "requires that the [X] integration is configured") — trying them cold, without setting up the relevant Gatekeeper, will not demonstrate the intended behavior (raw/github_doc-readme-md.md). - Self-hosting on `workerd` outside Cloudflare's platform was, at this snapshot, still "COMING SOON" — attempting it means working from low-level `workerd` server config documentation rather than a supported guide (raw/github_doc-readme-md.md). - Many Gatekeepers need OAuth client credentials obtained from the third-party provider, which the README warns is intentionally not easy since these OAuth flows are meant for developers — budget setup time accordingly (raw/github_doc-readme-md.md). ## Related Concepts - [[concepts/cloudflare-os-overview]] — what the product is before you run it. - [[concepts/architecture]] — how the monorepo and Cloudflare Workers substrate underlie both `run-local` and real deployments. - [[concepts/gadgets]] — what the whiteboard/tic-tac-toe prompts actually create. - [[concepts/blueprints]] — what the slides prompt instantiates. - [[concepts/public-server-and-deployment]] — deploying beyond the quick-start flow. - [[concepts/oauth-signin]] — authentication for a deployed instance. ## Sources - raw/github_doc-readme-md.md --- title: "MCP Support" type: concept tags: [mcp, gatekeepers, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-mcp-readme-md.md", "raw/github_doc-packages-gatekeeper-mcp-portal-readme-md.md", "raw/github_doc-packages-mcp-shared-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # MCP Support ## Definition Cloudflare OS supports the [Model Context Protocol](https://modelcontextprotocol.io) (MCP) via two [[concepts/gatekeepers|Gatekeeper]] connectors that share a common library, `@gadgets/mcp-shared`: - [[entities/gatekeeper-mcp]] — a user pastes an arbitrary MCP server's endpoint URL; one Worker covers every possible MCP server, so a server needs no Gadgets-specific integration work to become usable. - [[entities/gatekeeper-mcp-portal]] — an administrator configures a single organizational MCP portal URL (e.g. a [Cloudflare MCP server portal](https://developers.cloudflare.com/cloudflare-one/access-controls/ai-controls/mcp-portals/)); everyone in the org then reaches every approved upstream server through it without ever typing an endpoint. `mcp-shared` is not itself a Worker — it's a library both connectors import, holding "code that would eventually disagree in a way that would be a security bug": tool classification, the scope grammar, the OAuth lifecycle, and approval-queue wiring. ## How It Works **Connecting.** For `gatekeeper-mcp`, the user supplies an endpoint URL (validated against a host blocklist; HTTPS required unless `MCP_ALLOW_INSECURE`), the gatekeeper opens a Streamable HTTP session and calls `initialize`; a server that completes the handshake unauthenticated is recorded as public. A `401` triggers the official MCP client's OAuth discovery chain: ``` 401 + WWW-Authenticate -> protected resource metadata (RFC 9728) -> authorization server metadata (RFC 8414) -> dynamic client registration (RFC 7591) -> authorization code + PKCE (RFC 7636) + resource indicator (RFC 8707) ``` with a fallback to conventional `/authorize`, `/token`, `/register` paths. Tokens are stored in an `McpAccount` Durable Object and refreshed proactively before expiry; a `401` mid-session does *not* trigger a refresh (it means the server rejected a token this Worker believed valid), so the account is instead marked as needing reconnection. For `gatekeeper-mcp-portal` there is no connect form — pressing "connect" goes straight to the portal's sign-in, using `MCP_PORTAL_AUTH`: `oauth` (same discovery chain, against the portal), `token` (presents `MCP_PORTAL_TOKEN`), or `none`. **Tools become typed methods.** Each server's own `inputSchema` generates a named method on the Gadget-facing session (e.g. `env.MCP_LINEAR.searchIssues({...})`), plus a `callTool(name, args)` fallback for tool names the RPC layer can't deliver as identifiers (`then`, `map`, `2fa`, case collisions like `list_issues`/`listIssues`). The agent discovers the API statically via `describeGatekeeper()`, which sends the full generated `.d.ts` with each method's tool description as JSDoc and whether it needs approval. **Approvals.** A tool the server annotates `readOnlyHint: true` is treated as an observation and returns immediately; every other call is queued via `submitAction()` and only reaches the server when the Overseer calls `applyAction()`. Because MCP annotations are optional and self-declared, every hint is compared with strict `=== true`/`=== false`, so an *unannotated* tool is always treated as an action requiring approval on either trust tier. **Trust tiers** govern how far a server's own annotation claims are believed: - **`byo`** ("bring your own") — a user typed the URL in. `readOnlyHint` classifies reads; nothing the server claims can auto-apply a *write*. Both `gatekeeper-mcp` (always) and `gatekeeper-mcp-portal` (by default) use this tier. - **`vetted`** — a deployment has asserted an endpoint's annotations can be relied on, so `destructiveHint: false` plus `idempotentHint: true` may drive auto-approval. Only `gatekeeper-mcp-portal` can reach this tier, and only with `MCP_PORTAL_TRUST_ANNOTATIONS=true` set, because a portal aggregates upstream servers whose annotations the administrator never individually reviewed. **Scoping.** Both connectors let a grant be scoped to "all tools" (including ones added later) or "named tools only" (anything else refused, including future additions). The portal additionally scopes every grant to exactly one upstream server behind it — recovered from the fact that the portal flattens tool names as `{server_id}_{original_name}` and exposes a `portal_list_servers` tool; `portal_*` tools themselves are never grantable, since they could let a Gadget widen which upstream servers its own session can reach. ## Key Parameters - `gatekeeper-mcp` config: `BASE_URL`, `MCP_CLIENT_NAME`, `MCP_ALLOW_INSECURE` (local dev only — disables the host blocklist and HTTPS requirement). - `gatekeeper-mcp-portal` config: `MCP_PORTAL_URL` (unset hides the connector entirely), `MCP_PORTAL_NAME`, `MCP_PORTAL_AUTH` (`oauth`/`none`/`token`), `MCP_PORTAL_TOKEN`, `MCP_PORTAL_TRUST_ANNOTATIONS`, `MCP_ALLOW_INSECURE`. - Fixed limits from `mcp-shared` (not configurable): 200 tools/server, 96 KiB UTF-8 catalog size, 4 KB tool description, 20 KB tool input schema, 50 `tools/list` pages, 1 MiB response body, 128 KB retained action result, 100 retained actions, 50 actions awaiting decision, 3 redirect hops, 10-minute connect link, 1-hour unfinished-connect expiry. - SSRF boundary: enforced by the `global_fetch_strictly_public` compatibility flag (rejects reserved IP ranges post-DNS-resolution, on every request and redirect hop), not by the endpoint hostname blocklist, which is only a legible connect-time refusal. ## When To Use Use `gatekeeper-mcp` to let end users connect any MCP server they know about (self-hosted, third-party, or ad hoc) without requiring per-server integration work. Use `gatekeeper-mcp-portal` when an organization already runs (or wants to run) a centralized MCP portal so Access can gate who connects and Cloudflare Gateway can log/inspect traffic, and so end users never need to know or type individual server endpoints. ## Risks & Pitfalls - **No simulation** — MCP has no way to predict a tool's effect, so a queued call is not reflected in later reads; the agent's turn suspends until the human decides. - **No revert** and **no hooks** — MCP describes no inverse operation, and `notifications/tools/list_changed` is session-scoped, not durable. - **No scoping below tool names** — MCP tools take arguments, not capabilities, so "this repo only" cannot be expressed; a named-tool list is the narrowest available grant. - **Only `tools/*`** is implemented — prompts, resources, sampling, and elicitation are out of scope (sampling/elicitation would let a server drive the agent). - **Tool-list changes are adopted, not pinned** — a changed catalog is logged and taken, since refusing new tools would break working Gadgets; a tool-scoped binding cannot widen this way, which is why "Choose tools" is preferred for anything that writes. - **Owner-only sharing** — `addObserver` refuses unconditionally on both connectors; being able to authenticate to a server is not evidence of being allowed to see what the *owner* read from it, and the Gadget runs on the owner's credentials throughout. To share the work, publish the Gadget as a Blueprint and let each collaborator connect their own server. See [[concepts/observers]] and [[concepts/sharing]]. - **Sharing UI reports late** — `GadgetMetadata.sharingProhibited` derives only from `prohibitAllSharing`, so creating a share key can appear to succeed and only fail when the recipient opens it; fixing this needs a kernel change. - **Honoring `readOnlyHint` on `byo` is a knowing departure** from MCP's own guidance (treat annotations as untrusted unless from a trusted server) — accepted because prompting on every read/list call would make the connector unusable, but it means a dishonest `byo` server *can* act on any call it's granted, including approved writes. - Applying an approved MCP call only guarantees *at most once*, not exactly once — MCP has no idempotency key and no inverse, so ambiguous failures (dropped connections, malformed replies, oversized bodies) are closed as failed-and-not-retryable rather than risking a double write. ## Related Concepts - [[concepts/gatekeepers]] — MCP support is implemented as two ordinary Gatekeepers. - [[concepts/writing-a-gatekeeper]] — the general connector-authoring workflow these two follow. - [[concepts/observers]] — both connectors opt out of per-resource observer strategies entirely (always refuse). - [[entities/gatekeeper-mcp]] - [[entities/gatekeeper-mcp-portal]] ## Sources - raw/github_doc-packages-gatekeeper-mcp-readme-md.md - raw/github_doc-packages-gatekeeper-mcp-portal-readme-md.md - raw/github_doc-packages-mcp-shared-readme-md.md --- title: "OAuth Sign-In" type: concept tags: [auth, gatekeepers, operator, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-docs-oauth-signin-md.md"] confidence: medium cfos_snapshot: "2026-08-05" --- # OAuth Sign-In ## Definition OAuth sign-in is provided by **authentication gatekeepers** — [[concepts/gatekeepers|Gatekeepers]] that advertise `providesAuth` and can return a provider-verified email address. Each authentication gatekeeper uses a *single* OAuth app for both signing a user in and, later, connecting that same gatekeeper's fuller capabilities (e.g. GitHub repo access, Gmail/Docs) — there is no separate "login app" vs. "gatekeeper app." It is an optional, additive feature: for each allowlisted auth-capable gatekeeper, a "Continue with …" button appears alongside the normal username/password form. With an empty allowlist, the Workshop behaves exactly as before (username/password, or Cloudflare Access) — off by default. ## How It Works **Identity is keyed by verified email.** Signing in via any allowlisted gatekeeper that yields the same verified email resolves to the same account: the `UserDurableObject` is addressed by `idFromName(email)` (the same scheme Cloudflare Access uses). Each gatekeeper must only return an email the provider has actually verified — Google's `email_verified`, a GitHub primary-and-verified email, the Cloudflare account email — otherwise it returns `null` and cannot be used to sign in. **Incremental scopes.** Sign-in requests only the minimal scopes needed to read the verified email (e.g. GitHub `read:user user:email`, Google `openid email profile`, Cloudflare `offline_access user-details.read`). The grant created for login is **transient** — it self-destructs shortly after the email is read, so signing in never leaves a broad authorization lying around. The fuller capability scopes (repos, Gmail/Docs, [[concepts/ai-gateway-billing|AI Gateway billing]]) are only requested later, when the user explicitly **connects** the gatekeeper via `connectAccount(vendorId, { scopes: "full" })`, which is what actually persists a usable connected account. **Sign-in flow:** 1. The client calls `PublicApi.startGatekeeperLogin(vendorId)`. The backend creates a short-lived `PendingLogin` Durable Object, hands the gatekeeper a `LoginConnectCallbackImpl`, and returns the gatekeeper's OAuth `url` plus an `attempt` stub (a capability wrapping the `PendingLogin` DO — no login id is exposed to the client). 2. The client opens `url` in a pop-up (the gatekeeper's self-closing OAuth window) and calls `attempt.wait()`, which blocks on the `PendingLogin` DO. 3. When the gatekeeper's OAuth flow finishes, it calls `complete(user)`; the callback reads `user.getAuthenticatedEmail()`, resolves/creates the email-keyed `UserDurableObject`, mints a session, and delivers an `":"` token to the `PendingLogin` DO, which resolves the awaiting RPC. 4. The client stores the token and authenticates as usual. Sign-in itself does **not** persist a connected account — the minimal-scope grant is read once and discarded by the gatekeeper. ## Key Parameters ``` PUBLIC_BASE_URL=https://your-host AUTH_GATEKEEPERS=cloudflare,google,github # which gatekeepers may sign users in (order = button order) # Optional: gatekeeper sign-in only (hide username/password). DISABLE_PASSWORD_AUTH=true ``` - OAuth app credentials (`CLIENT_ID`/`CLIENT_SECRET`) live on the individual **gatekeeper Workers**, not the backend. Each must register its own redirect URI: Google `${PUBLIC_BASE_URL}/gatekeeper/google/oauth`, GitHub `${PUBLIC_BASE_URL}/gatekeeper/github/oauth`, Cloudflare `${PUBLIC_BASE_URL}/gatekeeper/cloudflare/oauth`. - In local dev, `run-dev-server.js` seeds each gatekeeper's client id/secret from `GOOGLE_*` / `GITHUB_*` / `CLOUDFLARE_OAUTH_*` shell vars. - `PendingLogin` DO — a short-lived bridge with no durable storage of its own, reached via `ctx.exports` (no explicit binding); kept alive only by the in-flight `attempt.wait()` and evicted once login completes or the client disposes the stub. - Code layout: `auth/config.ts` (allowlist + password-auth toggle), `auth/auth-vendors.ts` (binding lookup helpers), `auth/login-flow.ts` (`PendingLogin` DO + `LoginConnectCallbackImpl`); client-side `ServerConfigContext` exposes `authVendors`/`passwordAuthEnabled`, rendered by `components/auth/OAuthButtons`. ## When To Use When a deployment wants to let users sign in with an existing identity provider (Google, GitHub, Cloudflare) instead of, or in addition to, built-in username/password accounts — typically as a prerequisite for running as a [[concepts/public-server-and-deployment|public, multi-user server]] or for enabling [[concepts/ai-gateway-billing]] (which requires a connected Cloudflare account). ## Risks & Pitfalls - `DISABLE_PASSWORD_AUTH=true` is ignored unless `AUTH_GATEKEEPERS` is non-empty, specifically to avoid locking every user out. - A gatekeeper that cannot guarantee a provider-verified email must return `null` rather than an unverified address — otherwise sign-in could be spoofed via an unverified email claim. - Sign-in and "connect" are deliberately separate: forgetting this distinction could lead to assuming a signed-in user already has a persisted, capability-granting connected account when they do not. ## Related Concepts - [[concepts/gatekeepers]] — authentication gatekeepers are ordinary Gatekeepers that additionally advertise `providesAuth`. - [[concepts/public-server-and-deployment]] — the deployment mode where OAuth sign-in is most relevant. - [[concepts/ai-gateway-billing]] — depends on a connected Cloudflare gatekeeper account, connected via this same OAuth mechanism. - [[entities/gatekeeper-google]], [[entities/gatekeeper-github]], [[entities/gatekeeper-cloudflare]] — the shipped authentication-capable gatekeepers. ## Sources - raw/github_doc-docs-oauth-signin-md.md --- title: "Observers" type: concept tags: [sharing, gatekeepers, security, developer, advanced, emerging] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-docs-observers-md.md", "raw/github_doc-agents-skills-write-gatekeeper-skill-md.md"] confidence: medium cfos_snapshot: "2026-08-05" --- # Observers ## Definition The observer system is the mechanism that enforces a core security invariant when a Gadget is shared: **if a Gadget can read information that has restricted access, then any user who cannot read that information themselves must also be prohibited from interacting with the Gadget.** An "observer" is any non-owner collaborator who can see data the Gadget has read. `docs/observers.md` is a historical implementation plan (largely AI-written, guided by a human-written introduction) for this mechanism; note the raw source frames it as a read-through *sharing-permissions* safeguard, not a general-purpose telemetry/monitoring system. It replaces an older, blunt all-or-nothing mechanism — the `prohibitAllSharing` flag on `ObservationDescription` — which, once set, locks a Gadget down from *all* sharing rather than allowing "shareable, but only with people who also have access." ## How It Works When Bob opens a Gadget that Alice has shared with him, he must specify one of his own connected accounts for each of the Gadget's Gatekeepers. Each relevant [[concepts/gatekeepers|Gatekeeper]] then verifies — via `Gatekeeper.addObserver()` — that Bob's account has sufficient privileges to directly read everything the Gadget has historically read through that Gatekeeper. If the checks pass, Bob is registered as an observer, recording his connected accounts. From then on, any *new* observation the Gadget makes through a Gatekeeper that at least one registered observer cannot make directly is **blocked**, throwing an exception (Alice can resolve this by revoking Bob's access). Bob's access is also re-checked every time he opens the Gadget — the invariant is not derived from stale state, it is enforced live against the current sharing graph. Key pieces of the design: - **Verifiers.** The overseer cannot itself reason about a vendor's identity/ACL model, so the prospective observer's *own* connected account mints an opaque `GatekeeperUserVerifier` via `GatekeeperUser.getVerifier()`. The overseer hands this back only to a Gatekeeper of the *same vendor*, so the Gatekeeper can trust whatever it learns by "unwrapping" it (typically by casting to a vendor-specific interface with non-standard methods and querying the service using the observer's own token). - **Forward exclusion.** For observations made *after* a user becomes an observer, a Gatekeeper can name observers who must not see a given observation via `ObservationDescription.excludeObservers`; the overseer must then either block the observation or guarantee those observers never see it. - **Role-scoped verification breadth.** `build` collaborators (full chat + code + all bindings) must be verified against *every* Gatekeeper the Gadget has; `use` collaborators (UI-only, no chat access) are verified only against *named bindings*, since that is all the `use` UI can invoke. - **Configuration flow.** `open()` gains an optional `ObserverConfigCallback`, invoked only when the opening user must choose connected accounts for uncovered Gatekeeper bindings. In the common case (owner, or an already-fully-configured observer) it is never called and `open()` resolves without an extra round trip. A private `ensureObserver()` helper selects in-scope Gatekeepers, determines uncovered bindings, prompts via the callback if needed, re-verifies *all* in-scope bindings (covered plus newly chosen) with fresh `addObserver` calls, and only persists the observer record once every check succeeds — a failed check triggers best-effort `removeObserver()` rollback and denies the open. - **Teardown.** When sharing changes (`removeCollaborator`, `revokeShareLink`, or a `build`→`use` downgrade) strip someone's access, their observer record is deleted and `removeObserver()` is called on affected Gatekeepers, best-effort. - **Authorization is keyed on the sharing table, not live sessions**, because a Gadget may store observed data and re-display it later even to an observer who opens much later; every enforcement decision checks whether the user is still *authorized in the sharing graph* (see [[concepts/sharing]]), never whether they currently have the Gadget open. ## Key Parameters Per-resource-type strategy for satisfying the `addObserver`/`removeObserver`/`getVerifier` contract (also documented from the Gatekeeper-author's side in [[concepts/writing-a-gatekeeper]]): - **A — Private-only.** `addObserver()` always throws; no per-observer access oracle exists or the resource is too sensitive to share (e.g. a personal Gmail mailbox, a ZoomInfo account). - **B — ACL check (single unit).** The binding is one atomic resource; `addObserver()` checks the observer's own access to it and throws otherwise (e.g. a GitHub repo, a Google Doc, a Notion page — issues/PRs/sub-pages inherit the parent's ACL). - **C — Data-set tracking.** The binding spans sub-resources with *distinct* ACLs and a per-observer oracle exists for each; the Gatekeeper's Durable Object logs which data sets have actually been observed, verifies every stored observer against each newly-touched set, and sets `excludeObservers` for any who fail (e.g. BigQuery datasets, a Notion or Linear workspace, a Confluence site/space). - **D — Low-stakes.** No tracking; `addObserver()`/`removeObserver()` are no-ops (e.g. Home Assistant, Spotify — personal, low-stakes services with no per-user ACL oracle to check against). - **N — N/A.** The Gatekeeper exposes no resources, so it's never an in-scope binding (e.g. the Cloudflare auth-only Gatekeeper). The "broad binding" lens for choosing **C** vs. **B**: use C only when *both* (1) the binding spans sub-resources with distinct ACLs, and (2) a per-observer access oracle exists to check each. Failing (1) means one ACL already covers everything (→ B); failing (2) means there's nothing to verify against (→ A or D). The `ObserverRecord` data model: `{ profileId, observerId, accountChoices: { [gatekeeperId]: accountId } }` — `observerId` is a random, opaque handle (deliberately *not* `profile.id`) passed to Gatekeepers as the stable identity handle, generated fresh if a user loses and regains access. ## When To Use This is not something an end user invokes directly — it activates automatically whenever a shared Gadget (see [[concepts/sharing]]) has one or more Gatekeeper bindings and a non-owner collaborator opens it. Gatekeeper authors must make an explicit per-resource-type strategy decision (A/B/C/D) as part of [[concepts/writing-a-gatekeeper]]; per the source plan, per-gatekeeper implementation of `addObserver`/`getVerifier`/`removeObserver` "must be completed before deploying the feature to prod." ## Risks & Pitfalls - **v1 scope is all-or-nothing per observer** — there is no per-thread enforcement; an observer either can or cannot see everything a Gadget has read through a given Gatekeeper, not selective slices of it. - **`prohibitAllSharing` still wins** — it remains a separate, authoritative lockdown mechanism; observer checks only matter once sharing is otherwise allowed. - **Underlying resource access revoked externally** is only caught at the observer's *next* open (consistent with the lazy-revocation model in [[concepts/sharing]]), not proactively. - **Performance** — `ensureObserver` does one `getVerifier` plus one `addObserver` call per in-scope Gatekeeper on every open; expensive Gatekeepers are expected to cache on their own side, since re-running `addObserver` every open is intentional (it's what catches revocation promptly). - **Owner is never an observer** and is excluded from the collaborators table entirely — `ensureObserver` runs only for non-owners. - **A dishonest or under-specified Gatekeeper implementation is the actual failure mode**: since the invariant is enforced entirely inside `addObserver()`/`excludeObservers`, a Gatekeeper that gets its strategy choice wrong (e.g. picking D where C was warranted) silently reintroduces the exact data leak this system exists to prevent. - Orphaned observer records (from a best-effort teardown call that failed) only ever cause superfluous *future* re-checks, never a leak — the live sharing-graph check in `authorizeObservation()` remains authoritative regardless of stale Gatekeeper-side memory. ## Related Concepts - [[concepts/sharing]] — the collaborator/permission-graph system that determines who is even eligible to become an observer. - [[concepts/gatekeepers]] — observer verification is one of the seven mandatory Gatekeeper responsibilities. - [[concepts/writing-a-gatekeeper]] — where a Gatekeeper author chooses and implements the A/B/C/D strategy. - [[concepts/mcp-support]] — both MCP-speaking Gatekeepers hard-code an owner-only sharing policy (`addObserver` refuses unconditionally) rather than implementing per-resource strategies. ## Sources - raw/github_doc-docs-observers-md.md - raw/github_doc-agents-skills-write-gatekeeper-skill-md.md (Observer verification section — corroborates the contract and strategy definitions) --- title: "Public Server and Deployment" type: concept tags: [deployment, operator, foundational, emerging] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-docs-public-server-md.md", "raw/github_doc-readme-md.md"] confidence: medium cfos_snapshot: "2026-08-05" --- # Public Server and Deployment ## Definition Cloudflare OS can run in two broad postures. By default, the Workshop uses built-in username/password accounts (or Cloudflare Access) and gives every user *unlimited* AI usage — the ideal shape for self-hosting inside a single trusted organization. It can instead run as a **public, multi-user service**: users sign in with Google, GitHub, or Cloudflare (see [[concepts/oauth-signin]]), every account gets a free daily AI allowance, and once that's exhausted the user connects their own Cloudflare account and tops up credits directly in the Cloudflare dashboard (see [[concepts/ai-gateway-billing]]). Separately from that mode toggle, the README describes the actual mechanics of getting any Cloudflare OS instance running at all: a quick local run, a hosted deploy flow to your own Cloudflare account, a starter repo for deeper customization, and (still pending) a documented path to running on your own servers via `workerd`. ## How It Works **Public/multi-user mode is not one switch — the pieces turn on independently:** | Configure | Effect | | --- | --- | | `AUTH_GATEKEEPERS=cloudflare,google,github` | Allowlists which connected gatekeepers may sign users in; each shows a "Continue with …" button alongside username/password. | | Each gatekeeper's OAuth credentials (on the gatekeeper Worker) | Required for that gatekeeper to actually authenticate; in dev, seeded from `GOOGLE_*` / `GITHUB_*` / `CLOUDFLARE_OAUTH_*` shell vars. | | `ENABLE_CLOUDFLARE_LIMITS=true` | Enables the free daily limit plus Cloudflare-credits top-up flow; billing reads a token from the connected Cloudflare gatekeeper. | | `DISABLE_PASSWORD_AUTH=true` | Hides username/password, leaving gatekeeper sign-in only (ignored unless `AUTH_GATEKEEPERS` is non-empty, to avoid lockout). | The primary account key is always the user's **verified email** — signing in with any allowlisted gatekeeper that yields the same verified email maps to the same account, exactly as in [[concepts/oauth-signin]]. For local development, required variables go in a root `.dev.vars` file (gitignored), automatically loaded by `pnpm run dev-server`. A minimal example sets `ENABLE_CLOUDFLARE_LIMITS`, `PUBLIC_BASE_URL`, `AUTH_GATEKEEPERS`, each gatekeeper's OAuth client id/secret, and the platform's AI Gateway variables (`CF_AI_GATEWAY`, `CF_AI_GATEWAY_PROVIDERS`, `CF_AI_GATEWAY_ACCOUNT_ID`, `CF_AI_GATEWAY_API_TOKEN`; optionally `CF_AI_GATEWAY_WAI_DIRECT=true` to send Workers AI straight to its REST endpoint with no gateway cost logs). Each gatekeeper's OAuth app must be registered with a matching redirect URI (e.g. `${PUBLIC_BASE_URL}/gatekeeper/github/oauth`). **Deployment paths (from the README):** - **Run locally** — `pnpm run-local`, then visit `http://localhost:8787`. Runs the entire stack on `wrangler`/`workerd`; data lands in a local `.wrangler` subdirectory. Explicitly "not meant for production use," but the fastest way to see the product. - **Deploy to your own Cloudflare account** — a hosted flow at `https://os.cloudflare.app/deploy`. - **More sophisticated deployment** (custom gatekeepers, code changes) — the `cloudflare-os-starter` repository (`https://github.com/cloudflare/cloudflare-os-starter`). - **Deploy to your own server using `workerd`** — marked **COMING SOON**. Cloudflare OS can in principle run entirely on `workerd` (the same runtime `run-local` uses under the hood, and itself open source), but documentation and tooling for smoothly deploying it on a self-managed server are still being built; in the meantime the low-level `workerd` config docs (or pointing an agent at them) are the suggested path. - **Configuring external services** — many Gatekeepers need OAuth client credentials from their respective third-party providers; each Gatekeeper package ships its own setup instructions (GitHub, Google, Cloudflare, Supabase, Notion, Confluence, Email Workers, Home Assistant, Slack, Spotify, ZoomInfo). - **Developing** — run frontend and backend separately (`pnpm dev-server`, `pnpm dev-client`), then visit `http://localhost:3000`. ## Key Parameters - `AUTH_GATEKEEPERS`, `ENABLE_CLOUDFLARE_LIMITS`, `DISABLE_PASSWORD_AUTH`, `PUBLIC_BASE_URL` — the independent public-mode toggles. - `CF_AI_GATEWAY`, `CF_AI_GATEWAY_PROVIDERS`, `CF_AI_GATEWAY_ACCOUNT_ID`, `CF_AI_GATEWAY_API_TOKEN`, `CF_AI_GATEWAY_WAI`, `CF_AI_GATEWAY_WAI_DIRECT` — the platform AI Gateway used for the free tier (shared configuration surface with [[concepts/ai-gateway-billing]]). - Local dev requires `pnpm run dev-server -- --use-workers-ai-binding` when `CF_AI_GATEWAY*` is set, so the webFetch tool's document-to-Markdown conversion still has a `WORKERS_AI` binding (inference itself no longer needs it). ## When To Use Use the public/multi-user mode when opening a Cloudflare OS deployment to people outside a single trusted, already-provisioned organization — e.g. a broader community or customer base — where unmetered AI usage per account isn't acceptable. Use the plain hosted-deploy or run-local paths for the common "Your Company OS" self-hosting case the README frames as the project's actual goal. ## Risks & Pitfalls - **`run-local` is not production-ready** — the README states this directly; it's for trying the product, not serving real users. - **Self-managed production deployment via `workerd` is not yet documented** ("COMING SOON") — teams wanting to run off Cloudflare's own infrastructure today are pointed at the raw `workerd` server config as a stopgap. - **`DISABLE_PASSWORD_AUTH` lockout risk** is guarded against but only because the flag is ignored when the auth-gatekeeper allowlist is empty — misconfiguring both together is still possible in principle. - **Many Gatekeepers require provider-side OAuth app setup that is intentionally not developer-friendly** — the README notes third-party providers "intentionally do not make this easy," since OAuth app registration targets developers, not end users. - The project overall is flagged as an **early-access, August 2026 v2 rewrite** with "many rough edges," a caveat that applies to deployment tooling as much as anything else. - Whenever `CF_AI_GATEWAY` is set, `CF_AI_GATEWAY_ACCOUNT_ID` and `CF_AI_GATEWAY_API_TOKEN` are both required — omitting either breaks inference for the free tier. ## Related Concepts - [[concepts/oauth-signin]] — the sign-in mechanism public mode relies on. - [[concepts/ai-gateway-billing]] — the free-tier/paid-tier usage model enabled by `ENABLE_CLOUDFLARE_LIMITS`. - [[concepts/gatekeepers]] — third-party service configuration (OAuth credentials) happens per Gatekeeper package. - [[concepts/cloudflare-os-overview]], [[concepts/getting-started]] — broader context for what's being deployed. - [[entities/gatekeeper-cloudflare]] — the gatekeeper billing and Cloudflare-account sign-in depend on. ## Sources - raw/github_doc-docs-public-server-md.md - raw/github_doc-readme-md.md (Get Started / deploy and run-locally sections) --- title: "Sharing" type: concept tags: [sharing, gadgets, user, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-docs-sharing-md.md"] confidence: medium cfos_snapshot: "2026-08-05" --- # Sharing ## Definition Sharing is how a Cloudflare OS user gives other people direct access to a Gadget they own, analogous to sharing a document in an online office suite. There are two distinct mechanisms: **collaborators** (granting other users direct, ongoing access so they can work on a Gadget alongside the owner — covered here) and **Blueprints** (sharing a snapshot of a Gadget's source code so others create independent copies — documented separately). The collaborator system is built on a directed **permission graph** that tracks *how* each collaborator gained access, which is what makes revocation lazy and reversible rather than requiring an eager cascade. ## How It Works **Roles.** Each collaborator has a role, totally ordered `build` > `use`: - `build` — full access: edit code, use AI chat, manage bindings, interact with the UI — same as the owner except for a few owner-only exceptions (cannot delete the gadget; uses their own AI models and their own connected third-party accounts for bindings rather than the owner's; can only revoke users they themselves added). - `use` — may only render and interact with the deployed UI: `getUiBundle()`/`connectToGadget()` against mainline code, restricted metadata reads, and `subscribeToPresence()`. Every other `Overseer` method throws `Unauthorized`, except two inert telemetry subscriptions kept quiet to avoid spurious client errors. Authorization is capability-based: `open()` computes the caller's effective role and hands back either the full `OverseerClientInterface` (`build`/owner) or a restricted `UseOverseerInterface` that implements the same interface but default-denies (throws `Unauthorized`) anything outside the `use` allowlist — so any newly added interface method fails to compile until a developer consciously decides whether `use` callers may invoke it. **Adding collaborators** happens two ways: - **Direct add** — the owner or a `build` collaborator enters a username (email); if the account exists, a collaborator record is created. No in-product notification is sent. - **Share link** — encodes a secret key as a `#share=` URL fragment. The server stores only an HMAC-SHA-256 hash of a random 128-bit key (with a fixed domain-separation constant); the raw key is shown once at mint time and never stored, so a database leak cannot expose valid share keys. A link owns one or more keys (creating mints the first; "copying" mints another), and any key redemption is atomic with opening the gadget (`openGadget(id, shareKey)`). **Permission graph.** Each collaborator has one or more edges recording how they got access: a **user edge** (a specific sharer added them directly, with role and timestamp) or a **share-link edge** (they redeemed a key for a specific link). A collaborator's **effective role** is the maximum role reachable from the owner (the implicit root, always `build`) through valid edges, computed live via a fixed-point algorithm (`SharingManager.computeEffectiveRoles()`) — never denormalized into storage, so it is always consistent and recomputed from scratch at every `open()`. **Lazy revocation.** Rather than cascading deletes, revocation just severs the edge(s) granting the removed party access and lets reachability do the rest. Removing a collaborator deletes only the edges that grant *them* access (the owner severs all incoming edges; a non-owner severs only their own edge); revoking a share link sets a `revoked` flag rather than deleting the link. Nothing cascades automatically — a dependent who becomes unreachable (e.g. someone who only had access via the now-removed person) is denied at their *next* `open()`, not proactively pruned. Because nothing is destructively deleted, revocation is reversible ("undo" by re-adding the severed edge) — though this means removed collaborators and revoked links accumulate in storage. **Preview and confirm.** The UI calls `previewRemoveCollaborator()`/`previewRevokeShareLink()` first to compute which collaborators would be affected (and whether they'd be fully removed or merely downgraded), then `removeCollaborator()`/`revokeShareLink()` to actually apply it. An optional `keepUsers` parameter lets the caller re-root specific dependents with a fresh edge at their prior role so they don't lose access as a side effect. **Resource isolation.** AI model bindings resolve from whoever created them (their account is billed); a collaborator adding a Gatekeeper binding connects through *their own* third-party account, not the owner's — so no collaborator implicitly gains access to another collaborator's connected accounts. See [[concepts/gatekeepers]]. **Terminating live sessions.** Because authorization is checked only at `open()`, `removeCollaborator`/`revokeShareLink` proactively restart the Gadget's Overseer Durable Object (`ctx.abort()`) whenever the change actually removed or downgraded someone, forcibly disconnecting every client so each reconnects and re-runs `open()` against the updated graph. ## Key Parameters - Role ordering: `build` > `use`. - Edge types: user edge, share-link edge; each grants `min(edge role, sharer's effective role)`. - Share key: 128-bit random value, stored only as an HMAC-SHA-256 hash. - `keepUsers` — optional re-rooting list on removal/revocation calls. ## When To Use Whenever a Gadget owner wants to collaborate with specific people (direct add), distribute broad access via a link (share link), or needs to revoke access — including cascading revocation through people who were only reachable via someone else's grant. ## Risks & Pitfalls - No per-thread hiding of individual chat threads/observations from individual collaborators yet — that granularity is not modeled, only whole-gadget access. This is closely related to, but distinct from, the read-through data-leak concern handled by [[concepts/observers]]. - `use` collaborators cannot yet reshare `use` access (the permission graph supports it, but no sharing method is on the `use` allowlist). - No binding-aware access control: nothing today prevents adding a collaborator who lacks permissions the gadget's bindings require, or adding a sensitive binding that existing collaborators can't satisfy. - Share links have no expiration or usage limits, and there is no UI/RPC to un-revoke a revoked link (the `revoked` flag is one-way in practice). - Removed collaborators and revoked links accumulate in storage under the lazy model; no garbage collection exists yet. - No in-product notifications when access is granted or revoked. ## Related Concepts - [[concepts/observers]] — the mechanism that additionally prevents a shared Gadget from leaking Gatekeeper-mediated data to a collaborator who couldn't access it directly, layered on top of this permission graph. - [[concepts/gatekeepers]] — Gatekeeper bindings are per-collaborator, not inherited from the owner. - [[concepts/gadgets]] — the object being shared. ## Sources - raw/github_doc-docs-sharing-md.md --- title: "Writing a Gatekeeper" type: concept tags: [gatekeepers, developer, advanced, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-agents-skills-write-gatekeeper-skill-md.md", "raw/github_doc-agents-skills-write-gatekeeper-skeleton-md.md", "raw/github_doc-packages-gatekeeper-github-storage-schema-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- # Writing a Gatekeeper ## Definition "Writing a Gatekeeper" is the developer workflow — packaged as the `write-gatekeeper` agent skill (`.agents/skills/write-gatekeeper/SKILL.md`, with an accompanying `SKELETON.md` implementation template) — for authoring a new [[concepts/gatekeepers]] connector that bridges a Gadget to an external service. The skill splits the work into two phases: Phase 1 (auth, API design, resource granting) delivers a working connector; Phase 2 (logging/approvals, caching, simulation, observer verification) hardens it into something safe to deploy. ## How It Works ### Phase 1: Core implementation 1. **Understand the external service** — its auth model (OAuth 2.0, API keys, etc.), which resources to expose at which granularities, and which operations are reads (observations) vs. side effects (actions). 2. **Design the Session types** in `src/types.d.ts` (and a `Hook` interface if the service pushes events). Design principles: one interface per logical resource (not a god-object), capability-based method design, JSDoc that documents *only* what the calling agent needs — never approval-queue mechanics or gatekeeper internals like caching, DO storage, OAuth, or syncing. 3. **STOP — present the API for operator review.** The skill is explicit that this is mandatory: "Do not proceed without operator approval," since getting the API wrong means rebuilding it. 4. **Implement**, following the `SKELETON.md` template. The package layout is: ``` packages/gatekeeper-/ ├── src/ │ ├── configurator/ # Optional resource-picker UI modules and UI-facing types │ ├── .ts # Vendor, UserAccount, UserImpl, GatekeeperImpl, SessionImpl │ ├── types.d.ts # Session/Hook types (compile-time) │ ├── types.txt -> types.d.ts # Symlink (runtime, for getTypeScriptTypes()) │ └── -api.ts # (optional) Helper wrapping the service's HTTP API ├── wrangler.jsonc ├── package.json └── tsconfig.json ``` 5. **Configure and register** — add a service binding to `packages/workshop-backend/wrangler.jsonc` (`"binding": "GATEKEEPER_"`); the backend auto-discovers vendors from `GATEKEEPER_`-prefixed bindings. 6. **Add a resource selection UI** for each resource type in `getSupportedResources()`, served as `iframeHtml` from `GatekeeperUser.startResourceConfigurator()`. The optional `@gadgets/configurator-ui` helper turns `src/configurator/*-ui.tsx` into that HTML. Every gatekeeper with selectable resources should implement `initialValuesFromResourceUrl()` so an agent's `requestConnection` (which passes a concrete `resourceUrl`) opens the configurator pre-filled and editable rather than blank. 7. **STOP — ask the operator whether to proceed to Phase 2**, which may happen in a separate session. ### Phase 2: Logging, approvals, caching, simulation, observers - **Logging and approvals** — walk every API method and decide where `ApprovalQueue` calls belong: `authorizeObservation()` for reads, `submitAction()` (with `applyAction()` deferred until approval) for anything with a side effect. - **Caching** — store fetched data in `this.ctx.storage`, using the synchronous `this.ctx.storage.kv` for simple key/value data (no batch `get`/`put`, but plain multiple calls are cheap) and `this.ctx.storage.sql` for a full private SQLite database when needed. Cache transformed data (e.g. Markdown) rather than raw responses when the transform is expensive. - **Simulation** — once `submitAction()` has run but `applyAction()` hasn't, reads must reflect the pending action so the calling agent doesn't need to know approvals exist. Two implementation approaches: (1) mutate the cache on submit and rebuild/invalidate it on `rejectAction()`, or (2) store pending actions separately and overlay them onto reads at query time. The GitHub gatekeeper's actual `storage-schema.md` illustrates approach (2) concretely: pending actions live in `pendingAction:` keys and are overlaid on cached/remote state at read time; new issues/PRs get provisional IDs (`~1`, `~2`, …) until GitHub assigns real ones, and `diffAlias:` maps provisional review-comment IDs to real GitHub comment IDs once applied. Any queued/applied/rejected/reverted action clears all `cache:*` entries and simulation is rebuilt from the pending-action log. - **Observer verification** (see [[concepts/observers]] for the full mechanism) — `getVerifier()` mints an opaque `GatekeeperUserVerifier` for the calling user's account; `addObserver(id, verifier)` must throw if that user shouldn't see everything the Gatekeeper has read so far; `removeObserver(id)` is idempotent. A vendor extends the opaque verifier with its own methods (the "non-standard method" pattern) and implements them on a `WorkerEntrypoint` that queries the service using the *observer's own* token — auth/permission/not-found errors resolve to `false`, anything else rethrows so the open fails loudly rather than silently denying. Strategy is chosen per `Gatekeeper` DO class/binding, not per package: - **A — Private-only**: `addObserver()` always throws (e.g. a personal Gmail mailbox). - **B — ACL check (single unit)**: one atomic resource; `addObserver()` checks the observer's own access to it (e.g. repo/document/page bindings). - **C — Data-set tracking**: the binding spans sub-resources with distinct ACLs and a per-observer access oracle exists for each; the DO logs which sets were observed and re-verifies all stored observers whenever a *new* set is first touched, setting `excludeObservers` for any who fail (e.g. workspace/organization/dataset-spanning bindings). - **D — Low-stakes**: both methods are no-ops; any collaborator may observe. - **Hooks (push notifications)** — for services that push events (inbound email, webhooks, chat), a Gadget registers a persistent callback stub via a Session method (`subscribe(callback)`); a `HookController` (constructed with bind-time `props`) is `enable()`d/`disable()`d by the overseer; delivery calls `initiator.startHook()`, which returns a fresh `{callback, approvalQueue}` pair — `authorizeObservation()` runs before the callback is invoked. `gatekeeper-email` is called out as the canonical reference implementation. ## Key Parameters - **Package structure** under `packages/gatekeeper-/`, with `types.txt` as a *symlink* (never a copy) to `types.d.ts`. - **`wrangler.jsonc` migrations** — every Durable Object class (e.g. `UserAccount`, the `GatekeeperImpl` DO) must appear under `migrations[].new_sqlite_classes`; `WorkerEntrypoint`s like a verifier or hook controller need no migration entry but must still be `export`ed so `ctx.exports.(...)` resolves. - **Observer strategy per resource type** (A/B/C/D) — see [[concepts/observers]]. - **`suggestedBindingName`** in `describe()` reflects the resource *type* (e.g. `"GMAIL_INBOX"`), not the specific instance. ## When To Use When Cloudflare OS needs to reach a new external service that no existing Gatekeeper covers, or when adding a new resource type/granularity to an existing one. ## Risks & Pitfalls - Call `.dup()` on `approvalQueue` stubs before storing them in a session — Cap'n Web automatically disposes stubs passed as RPC parameters once the call returns. - For read-only or push-only Gatekeepers, `applyAction()` / `rejectAction()` / `revertAction()` can simply throw, since they'll never be invoked. - Pass credentials and resource IDs via `ctx.props` on `WorkerEntrypoint`/`DurableObject` subclasses, not constructor arguments — RPC stubs pointing at these can be stored long-term and restored later based on the same `props`. - Set a self-destruct alarm in `UserAccount.setCallback()` in case an OAuth flow never completes (the skeleton uses a 1-hour alarm). - `authorizeObservation()` may be called *after* fetching data (so its description can reference what was fetched) but must be awaited *before* returning anything to the caller. - Keep tokens and broad API clients out of public `RpcTarget` properties — use closures, `#private` fields, or a `WeakMap`, and expose only narrow read-only helpers (called out explicitly in the skeleton for the resource-configurator UI). - The B-vs-C observer strategy decision hinges on two criteria: (1) does the binding span sub-resources with genuinely distinct ACLs, and (2) is there a per-observer oracle to check each against? Failing either means B (or A/D) is correct, not C. ## Related Concepts - [[concepts/gatekeepers]] — the framework this skill produces instances of. - [[concepts/observers]] — the full observer-verification mechanism referenced throughout Phase 2. - [[concepts/mcp-support]] — a family of Gatekeepers (MCP, MCP Server Portals) that wrap an entire protocol rather than one bespoke service API, useful as a contrasting design example. - [[entities/gatekeeper-github]] — reference implementation whose storage schema is detailed above. ## Sources - raw/github_doc-agents-skills-write-gatekeeper-skill-md.md - raw/github_doc-agents-skills-write-gatekeeper-skeleton-md.md - raw/github_doc-packages-gatekeeper-github-storage-schema-md.md --- title: "Gatekeeper: Cloudflare" type: entity tags: [gatekeepers, auth, billing, deployment, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-cloudflare-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview Gatekeeper Cloudflare provides Cloudflare OAuth integration for Gadgets, currently for two purposes: **sign-in** ("Continue with Cloudflare" using minimal scopes to read the account email as identity) and **AI Gateway billing**, where a persisted connection lets the Workshop read a usable access token to power credit-balance reads and BYOK inference routing through the account's default AI Gateway. Resource capabilities for gadgets/agents (Workers logs, R2, etc.) are noted as coming later. ## Characteristics - **Sign-in scopes**: `offline_access user-details.read` — reads the account email (verified via Cloudflare's `/user` API); the sign-in grant is transient, discarded right after the email is read. `openid` is intentionally **not** requested — the Cloudflare dashboard OAuth client isn't permitted that scope, so identity comes from `/user` instead. - **AI Gateway billing scopes** (requested on connect, connection persists): `offline_access aig.read aig.run user-details.read account-settings.read`. - **OAuth endpoints/scopes are hardcoded** in `src/oauth.ts` — only the client id/secret and redirect URI are configurable. - **Redirect URI**: `${BASE_URL}/oauth`, where `BASE_URL` defaults to `http://localhost:8787/gatekeeper/cloudflare` locally (full redirect: `http://localhost:8787/gatekeeper/cloudflare/oauth`). Must be registered exactly on the Cloudflare OAuth client or you get `invalid_request` ("redirect_uri does not match any pre-registered redirect urls"). - **Config (verbatim)**, `.env` at `packages/gatekeeper-cloudflare/.env`: ```bash CLIENT_ID=your-client-id-here CLIENT_SECRET=your-client-secret-here ``` Locally, `run-dev-server.js` also seeds these from `CLOUDFLARE_OAUTH_CLIENT_ID`/`CLOUDFLARE_OAUTH_CLIENT_SECRET` in root `.dev.vars`; a per-package `.env` takes precedence. - **Enabling sign-in**: add `cloudflare` to `AUTH_GATEKEEPERS`, e.g. `AUTH_GATEKEEPERS=cloudflare,google,github` (order controls login-button order). For AI Gateway billing/top-up, also set `ENABLE_CLOUDFLARE_LIMITS=true`. ## How to Use 1. Obtain a Cloudflare dashboard OAuth client (client id + secret). 2. Register the redirect URI `${BASE_URL}/oauth` (e.g. `http://localhost:8787/gatekeeper/cloudflare/oauth`) exactly on that OAuth client. 3. Create `packages/gatekeeper-cloudflare/.env` with `CLIENT_ID`/`CLIENT_SECRET` (or seed via root `.dev.vars` as `CLOUDFLARE_OAUTH_CLIENT_ID`/`CLOUDFLARE_OAUTH_CLIENT_SECRET`). 4. (Optional) Add `cloudflare` to `AUTH_GATEKEEPERS` to offer "Continue with Cloudflare" sign-in. 5. (Optional) Set `ENABLE_CLOUDFLARE_LIMITS=true` for AI Gateway billing; a user then enables billing by connecting Cloudflare (requesting the fuller billing scopes) via **Usage & billing** > **Connect Cloudflare** in settings. 6. Verify: click **Continue with Cloudflare** on login, approve the pop-up, confirm sign-in by account email. Common errors: redirect URI not registered (`invalid_request`), "Not configured" page (missing `CLIENT_ID`/`CLIENT_SECRET`). ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-github]] — sibling `AUTH_GATEKEEPERS` sign-in provider - [[entities/gatekeeper-google]] — sibling `AUTH_GATEKEEPERS` sign-in provider - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Confluence" type: entity tags: [gatekeepers, productivity, developer, advanced, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-confluence-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The Confluence gatekeeper connects Gadgets to Atlassian Cloud Confluence (`*.atlassian.net`) via OAuth 2.0 (3LO), exposing spaces, pages, blog posts, comments, labels, and attachments through a capability-based Session API, with bodies converted to and from Markdown. It runs as its own Cloudflare Worker, auto-discovered via `GATEKEEPER_CONFLUENCE`. Only Atlassian **Cloud** is supported — no Server/Data Center adapter. ## Characteristics - **Three resource granularities**: | Granularity | URL pattern | Session type | | --- | --- | --- | | Site — search/open any space/page/blog post the account can access | `https://*.atlassian.net/wiki` | `ConfluenceSite` | | Space — list/search/create pages and blog posts in one space | `https://*.atlassian.net/wiki/spaces/:spaceKey` | `ConfluenceSpace` | | Page or blog post — read/edit body, manage labels/comments/attachments, child pages (pages only) | `https://*.atlassian.net/wiki/spaces/:spaceKey/pages/:pageId/*` | `ConfluenceContent` | A single account-level grant can span multiple sites; `getGatekeeperClassFor()` resolves a URL's host to one of the connection's accessible sites (cloud ID). - **Auth**: Atlassian OAuth 2.0 app requiring scopes from **both** the Granular and Classic tabs (v2 API for most operations, v1 fallback for a few): - Granular: `read:space:confluence`; `read:page:confluence`, `write:page:confluence`, `delete:page:confluence`; `read:blogpost:confluence`, `write:blogpost:confluence`, `delete:blogpost:confluence`; `read:comment:confluence`, `write:comment:confluence`, `delete:comment:confluence`; `read:label:confluence`, `write:label:confluence`; `read:attachment:confluence`, `write:attachment:confluence`, `delete:attachment:confluence`; `read:user:confluence`. - Classic (v1 fallback — CQL search, label add/remove, attachment upload, restore-from-trash): `search:confluence`, `read:confluence-content.all`, `read:confluence-content.summary`, `read:confluence-space.summary`, `write:confluence-content`, `write:confluence-file`, `readonly:content.attachment:confluence`, `read:confluence-user`. - Account-level: `read:me`, `offline_access` (required for refresh tokens). - **Callback URL**: `/oauth` (`http://localhost:8787/gatekeeper/confluence/oauth` locally). - **Config (verbatim)**, set in repo-root `.dev.vars`: ``` CONFLUENCE_CLIENT_ID=your-client-id CONFLUENCE_CLIENT_SECRET=your-client-secret ``` mapped by `run-dev-server.js` into the Worker's `CLIENT_ID`/`CLIENT_SECRET`. In production, set `CLIENT_ID`/`CLIENT_SECRET` as Worker secrets and `BASE_URL` to the public gatekeeper URL. `providesAuth` is false — Confluence is a data connector, not a sign-in method. - **Connect flow**: two-phase nonce (initiation → OAuth); exchanges code for access + rotating refresh tokens; lists accessible sites via `/oauth/token/accessible-resources`; reads identity via `/me`; routes calls through `https://api.atlassian.com/ex/confluence//wiki/...`; tokens refreshed proactively and on 401, persisting the new rotating refresh token each time. - **API highlights**: `ConfluenceSite` (`listSpaces`, `getSpace`, `getContent`, `search` — text or raw CQL, `getCurrentUser`); `ConfluenceSpace` (`listPages`, `listBlogPosts`, `getContent`, `search`, `createPage`, `createBlogPost`); `ConfluenceContent` (`getMetadata`/`getContent`, `setContent`/`appendContent`/`setTitle`, `listChildPages`/`createChildPage`, labels, comments, attachments — `downloadAttachment` capped at 16 KB, `trash`/`restore`). - **Approvals, caching & simulation**: every read calls `ApprovalQueue.authorizeObservation()`; every side-effecting op is a pending action submitted via `submitAction()`, applied only via `applyAction()`. Reads simulate pending writes (provisional IDs, body/title/label/comment overlays, trashed state). Content cached in DO storage with short TTL, invalidated on write. - **Limitations**: body conversion between storage-format XHTML and Markdown is best-effort (unsupported macros degrade to a placeholder); `author`/`lastUpdatedBy` expose only an `accountId`; CQL search, label add/remove, attachment upload, and restore-from-trash depend on v1 endpoints that could be removed by Atlassian. ## How to Use 1. Create an OAuth 2.0 integration in the [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/), add the Confluence API, and enable both the Granular and Classic scope sets listed above. 2. Set the callback URL to `/oauth`. 3. Copy the Client ID/Secret into `CONFLUENCE_CLIENT_ID`/`CONFLUENCE_CLIENT_SECRET` (local `.dev.vars`) or `CLIENT_ID`/`CLIENT_SECRET` Worker secrets (production, with `BASE_URL` set). 4. Connect either by pasting a Confluence URL into chat or via the Connections UI; the resource picker (space / page-or-blog / site) supports multi-site accounts. 5. Build: `pnpm --filter @gadgets/confluence-gatekeeper build`; test: `pnpm --filter @gadgets/confluence-gatekeeper test`. Run via root `pnpm dev-server`, not directly. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-notion]] — comparable docs connector with page/database granularities and Markdown conversion - [[entities/gatekeeper-slack]] — sibling productivity connector - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Email" type: entity tags: [gatekeepers, developer, deployment, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-email-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The Email gatekeeper is an inbound-email receiving connector for Gadgets. Unlike most gatekeepers, which mediate access to an external service, this one *is* the service: it implements a Cloudflare Email Worker that receives mail directly at an address like `@` and hands it to a Gadget's hook. ## Characteristics - **URL scheme**: `http://localhost:8787/gatekeeper/email/mailbox/` represents the email address `@`, where `` is the domain routed to this worker. - **No OAuth/tokens** — the binding is a mailbox local part (canonicalized to lowercase; letters, numbers, dots, underscores, plus signs, or hyphens; cannot start/end with a dot or contain consecutive dots). - **Session API**: ```typescript interface EmailSession { getAddress(): Promise; // e.g. "myinbox@example.com" } ``` - **Hook mechanism**: a Gadget exports a `WorkerEntrypoint` implementing `EmailHook`; the coding agent calls `setBindingHook` to connect it. Emails are parsed with `postal-mime` into structured data (from, to, cc, subject, date, text, html, attachments) before reaching the hook. Example hook: ```typescript import { WorkerEntrypoint } from "cloudflare:workers"; export class MyEmailHandler extends WorkerEntrypoint { async receiveEmail(email) { console.log(`Got email from ${email.from.address}: ${email.subject}`); } } ``` - **Storage**: each email address maps to an `EmailAddress` Durable Object (keyed by local part) that stores the hook `Fetcher` in KV, persisting across requests. - **Production config**: `BASE_URL` env var — the full base URL (protocol + host + optional path) where the gatekeeper's fetch handler is served, no trailing slash (e.g. `https://gatekeeper-email.example.workers.dev` or `https://app.example.com/gatekeeper/email`). - Local dev does not support real inbound SMTP; wrangler exposes `/cdn-cgi/handler/email` to simulate inbound mail. ## How to Use 1. In dev mode, open a Gadget's Connections tab, **+ New Connection**, choose **Email Mailbox**, and enter a mailbox local part (e.g. `myinbox`); the binding appears as `EMAIL`. 2. Implement a `WorkerEntrypoint` with a `receiveEmail(email)` method in the Gadget and connect it with `setBindingHook` (Binding: `EMAIL`, Export name: e.g. `MyEmailHandler`). 3. Test locally with curl against `/cdn-cgi/handler/email`: ```bash curl -X POST 'http://localhost:8787/cdn-cgi/handler/email' \ --url-query 'from=sender@example.com' \ --url-query 'to=myinbox@example.com' \ --header 'Content-Type: application/json' \ --data-raw 'From: "Alice" To: myinbox@example.com Subject: Hello from local dev Content-Type: text/plain; charset="utf-8" This is a test email body.' ``` 4. For production: deploy the gatekeeper and set `env.BASE_URL`; enable [Cloudflare Email Routing](https://developers.cloudflare.com/email-routing/) on the domain (DNS records for MX/SPF); create an Email Worker Route (e.g. catch-all `*@yourdomain.com` or `gadget-*@yourdomain.com`) pointing to the deployed `gatekeeper-email` worker, or configure this in `wrangler.jsonc`. Flow: `Internet email → Cloudflare Email Routing → gatekeeper-email worker → email() handler parses recipient → EmailAddress DO (per username) → loads stored hook Fetcher → Gadget's hook entrypoint (via Overseer loopback)`. An email with no configured hook for its address is rejected. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-scheduler]] — another ambient/ ownerless-trigger gatekeeper pattern (hooks/callbacks rather than OAuth connections) - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: GitHub" type: entity tags: [gatekeepers, auth, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-github-readme-md.md", "raw/github_doc-packages-gatekeeper-github-storage-schema-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview Gatekeeper GitHub provides GitHub OAuth integration for Gadgets. It serves two purposes: **sign-in** ("Continue with GitHub" on the login page, using minimal scopes to read the user's primary verified email as identity) and **connections**, where a user grants a Gadget full scopes to access repositories, issues, and pull requests on their behalf. ## Characteristics - **Sign-in scopes**: `read:user user:email` — minimal, requests only the primary verified email; the sign-in grant is transient and discarded right after the email is read. - **Connection scopes**: `repo read:user user:email` — full access so gadgets can act on repositories, issues, and pull requests. - **Must use a GitHub _OAuth App_, not a GitHub _App_.** Only OAuth Apps honor the OAuth `scope` parameter (the minimal-on-login / full-on-connect split). A GitHub App (client id starting with `Iv…`) ignores `scope` entirely and returns `Resource not accessible by integration` for the email lookup unless separately granted the **Email addresses** account permission — and even then login isn't minimal-scope. - **Config (verbatim)**: `.env` file at `packages/gatekeeper-github/.env`: ```bash CLIENT_ID=your-client-id-here CLIENT_SECRET=your-client-secret-here ``` - **Enabling sign-in**: add `github` to the deployment's `AUTH_GATEKEEPERS` allowlist, e.g.: ``` AUTH_GATEKEEPERS=cloudflare,google,github ``` - **OAuth callback URL**: `http://localhost:8787/gatekeeper/github/oauth` locally (replace host with `PUBLIC_BASE_URL` in production). - **Storage model** (Durable Object KV only — no SQLite): - `UserAccount` DO keys: `callback`, `nonce` (`{ value, expiresAt, stage: "initiation"|"oauth" }`), `accessToken`, `scopes`, `expiredNotified`, `reconnecting`. - `GitHubGatekeeperImpl` DO: counters (`counter:action`, `counter:resource`, `counter:comment`, `counter:review`, `counter:diff`, `counter:reply`); a pending action log (`pendingAction:` storing action variants like `createIssue`, `createPullRequest`, `setTitle`, `addLabels`, `postComment`, `postReview`, `mergePullRequest`, etc.); provisional resource mapping (`provisional:` -> `{ kind, realId? }`, with new issues/PRs getting provisional IDs like `~1`, `~2`); diff comment alias mapping (`diffAlias:` -> real GitHub comment ID); incremental discussion sync state (`discussionComments:*` and `pullReviewComments:*` families tracking `depth`, `freshness`, `exhausted`, `chunkSize`, `ids`); and short-lived TTL caches (`cache:*`, e.g. viewer 5 min, entity caches 30s, list/search 15s), revalidated via stored ETags. - Simulation overlays pending actions from the action log onto reads at read time; any queued/applied/rejected/reverted action clears all `cache:*` entries. ## How to Use 1. Create a GitHub **OAuth App** (not a GitHub App) at GitHub Settings > Developer settings > OAuth Apps, with Homepage URL `http://localhost:3000` and Authorization callback URL `http://localhost:8787/gatekeeper/github/oauth`. 2. Generate a client secret; copy the Client ID and Client secret. 3. Create `packages/gatekeeper-github/.env` with `CLIENT_ID` and `CLIENT_SECRET`. 4. (Optional) Add `github` to `AUTH_GATEKEEPERS` in the root `.dev.vars` to enable "Continue with GitHub" sign-in. 5. Verify: start dev mode, open a Gadget's **Connections** tab, **+ New Connection**, choose a GitHub resource type (repository, issue, or pull request), authorize, then pick the specific resource to connect. 6. If a GitHub App must be used instead, grant it the **Email addresses** account permission (Permissions & events → Account permissions) and have existing users re-authorize — otherwise sign-in fails with `Resource not accessible by integration`. Common errors: `redirect_uri_mismatch` (callback URL mismatch), `bad_verification_code` (expired/reused auth code), "Not configured" (missing `CLIENT_ID`/`CLIENT_SECRET`). ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-google]] — same sign-in + connections OAuth pattern and `AUTH_GATEKEEPERS` mechanism - [[entities/gatekeeper-cloudflare]] — also participates in `AUTH_GATEKEEPERS` sign-in allowlist - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Google" type: entity tags: [gatekeepers, auth, productivity, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-google-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview Gatekeeper Google provides Google OAuth integration for Gadgets, connecting agents to Gmail, Google Docs, Google Sheets, Google Calendar, and BigQuery. It serves both **sign-in** ("Continue with Google," using minimal identity scopes) and **connections**, where a user grants scopes for specific Google resources (a mailbox, a doc, a spreadsheet, a calendar, or a BigQuery project/dataset/table) so gadgets can act on them. ## Characteristics - A **single Google OAuth client** backs both sign-in and connections. - **Sign-in scopes**: `openid`, `userinfo.email`, `userinfo.profile` — reads the verified email (`email_verified`) as identity; transient, discarded right after the email is read. - **Connection scopes are requested per resource type**, not all at once (identity is always included): `gmail.modify` (Gmail thread reads/organize/reply/forward/send, includes label access); `documents` (Google Docs read/edit); `drive.metadata.readonly` (search Docs/Sheets by title in resource pickers); `spreadsheets.readonly` (read spreadsheet metadata/cell values); `calendar.calendarlist.readonly` (list calendars); `calendar.events` (manage events/check availability); `bigquery` (dry-runs and queries — intentionally broader than `bigquery.readonly` because dry-runs use `jobs.insert`; the gatekeeper enforces read-only SQL and resource-scope checks before running queries). - Requires enabling five Google Cloud APIs: Gmail API, Google Docs API, Google Drive API (metadata only, for pickers), Google Sheets API, Google Calendar API, and BigQuery API. - **Config (verbatim)**: `.env` file at `packages/gatekeeper-google/.env`: ```bash CLIENT_ID=your-client-id-here.apps.googleusercontent.com CLIENT_SECRET=your-client-secret-here ``` - **Enabling sign-in**: add `google` to `AUTH_GATEKEEPERS`, e.g. `AUTH_GATEKEEPERS=cloudflare,google,github`. - OAuth redirect URI: `http://localhost:8787/gatekeeper/google/oauth`. - While the Google Cloud OAuth consent screen is in **Testing** mode, only explicitly added **Test Users** can authenticate — this applies to both sign-in and connections. ## How to Use 1. Create a Google Cloud Project in the [Google Cloud Console](https://console.cloud.google.com/). 2. Enable Gmail API, Google Docs API, Google Drive API, Google Sheets API, Google Calendar API, and BigQuery API. 3. Configure the OAuth consent screen: select **External** user type, fill in app name; the Scopes page can be skipped (scopes come from the OAuth request itself). 4. Add yourself (and any other testers) as a **Test User** on the consent screen. 5. Create OAuth credentials: Credentials > Create Credentials > OAuth client ID > Web application, with authorized redirect URI `http://localhost:8787/gatekeeper/google/oauth`. 6. Put the resulting Client ID/Secret into `packages/gatekeeper-google/.env`. 7. (Optional) Add `google` to `AUTH_GATEKEEPERS` for sign-in. 8. Verify: dev mode, open a Gadget's Connections tab, **+ New Connection**, pick a resource type (Gmail, Doc, Spreadsheet, Calendar, or BigQuery), authorize through Google's consent screen (will show an "unverified app" warning in Testing mode — click Advanced > Go to [app] (unsafe)), then pick the specific resource. Common errors: `redirect_uri_mismatch`, `access_denied` (not a listed test user, or consent declined), `invalid_client` (bad CLIENT_ID/SECRET), rate-limit/quota errors visible in the Cloud Console. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-github]] — parallel OAuth sign-in + connections pattern - [[entities/gatekeeper-cloudflare]] — also an `AUTH_GATEKEEPERS` sign-in provider - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Home Assistant" type: entity tags: [gatekeepers, developer, advanced, emerging] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-homeassistant-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The Home Assistant gatekeeper lets a Gadget read state from a connected Home Assistant (HA) instance, call services on devices (lights, thermostats, locks, etc.), edit Lovelace dashboards, render templates, and read history — all mediated through the standard Gadgets approval queue. ## Characteristics - **Auth**: Home Assistant **long-lived access token (LLAT)** + instance URL, no OAuth flow — chosen because each HA instance has a different URL (no central directory), LLATs don't expire (10-year default lifetime), it's the pattern users already know from other HA tools, and it works for both Nabu Casa Cloud and self-hosted/LAN users. The user pastes URL + LLAT into a form; the gatekeeper validates via `GET /api/` and stores both in a per-user Durable Object. - **Reachability**: on Cloudflare-hosted deployments, HA must be publicly reachable (Nabu Casa, Cloudflare Tunnel, port-forwarding). On self-hosted (workerd) deployments, LAN addresses like `http://homeassistant.local:8123` or `http://192.168.x.x:8123` work directly — the intended setup for same-network deployments. - **Five resource granularities**: Whole instance (every area/device/entity/dashboard/service), Area (one room's devices/entities), Label (all entities with a given HA label), Device (one physical device and its entities), Entity (one light/sensor/switch/etc.) — each with its own configurator picker UI. - **TypeScript API**: bindings expose `HomeAssistantSession` (whole-instance), `Area`, `Label`, `Device`, or `Entity` depending on granularity. Example: ```ts const areas = await session.listAreas(); const light = await session.getEntity("light.kitchen"); await light.turnOn({ brightness: 200 }); const state = await light.getState(); // reflects simulated post-write state const livingRoom = await session.getArea("living_room"); await livingRoom.callService("light", "turn_off"); const temp = await session.renderTemplate("{{ states('sensor.outside_temp') | float }}"); const dashboard = await session.getDashboard("lovelace"); await dashboard.saveConfig(dashboardConfig); ``` - **Approval & simulation**: every read calls `authorizeObservation`, every write goes through `submitAction`; writes do not execute against HA until approved. Until approval, reads reflect a simulated post-action world (e.g. `getState()` shows `"on"` right after `turnOn()`) — but simulation predicts final states only, with no transition timing, and leaves state untouched for unrecognized service calls (custom integrations, scenes, scripts, templates). - **Not yet implemented (Phase 2)**: caching (every read is a fresh registry fetch) and hooks/push events (`setHook` is a no-op; WebSocket `subscribe_events`/`subscribe_entities` would enable a `HomeAssistantHook`). - **Implementation notes**: service calls go through HA's WebSocket API (`call_service`), not the REST endpoint, for full target-shape support across area/label/floor targets; every action gets an integer `id` from a per-DO counter, stored under `pending:` for read-time simulation; malformed action bodies fail synchronously with a corrected-call suggestion. ## How to Use 1. In a Gadget's Connections UI, connect a Home Assistant account by pasting the instance URL and a long-lived access token (generate one in HA's own UI). 2. Choose a resource granularity to grant: whole instance, an area, a label, a device, or a single entity, using the matching configurator picker. 3. Use the TypeScript Session API (see `src/types.d.ts` for full method list and `@example` blocks) to read state, call services, render templates, or edit dashboards; writes queue for approval before taking effect on the real HA instance. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-spotify]] — comparable approval/simulation model for a consumer device-control API - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: MCP Server Portal" type: entity tags: [gatekeepers, mcp, deployment, operator, advanced, emerging] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-mcp-portal-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The MCP Server Portals gatekeeper connects a deployment's own MCP portal as a Gadgets capability: an administrator configures one portal URL, and everyone in the organization then reaches every approved MCP server through it without ever typing an endpoint. The intended deployment is a [Cloudflare MCP server portal](https://developers.cloudflare.com/cloudflare-one/access-controls/ai-controls/mcp-portals/), where Access controls who may connect and Gateway logs/inspects traffic. It runs as its own Cloudflare Worker, auto-discovered via `GATEKEEPER_MCP_PORTAL`. For endpoints a user supplies themselves, use [[entities/gatekeeper-mcp]] instead. ## Characteristics - **One resource type, two grant breadths, always naming one upstream server** (never "everything the portal offers," which would hand a Gadget every connected system at once): | Granularity | Resource URL | Session type | | --- | --- | --- | | Server — every tool of one upstream server, including future ones | `#server=github` | `McpSession` | | Named tools — only listed tools of that server | `#server=github&tool=github_a&tool=github_b` | `McpSession` | The session API (typed method per tool, `callTool`, `getActionResult`, `listTools`) is identical to [[entities/gatekeeper-mcp]]. Scoping to one server shrinks the exposed surface materially — a 57-tool portal generates 12 tool signatures when scoped to one server. - **Configuration**: | Variable | Meaning | | --- | --- | | `MCP_PORTAL_URL` | The portal's MCP endpoint. Unset hides the connector entirely. | | `MCP_PORTAL_NAME` | Display name in the connector list and approval prompts. Default: `MCP Server Portal ()`. | | `MCP_PORTAL_AUTH` | `oauth` (default), `none`, or `token`. | | `MCP_PORTAL_TOKEN` | Secret bearer token, for `MCP_PORTAL_AUTH: "token"`. | | `MCP_PORTAL_TRUST_ANNOTATIONS` | `true` lets upstream tool annotations drive auto-approval; off by default. | | `MCP_ALLOW_INSECURE` | `"true"` disables endpoint checks (permits `http://` and private/loopback/metadata hosts) — local dev only. | Only `MCP_ALLOW_INSECURE` is set in the repo's `wrangler.jsonc` (pinned `"false"`); the rest belong in deployment-specific configuration since a committed portal URL would become every deployment's default. `MCP_PORTAL_TRUST_ANNOTATIONS` is read fresh at each use and never persisted, so clearing it de-escalates every existing connection immediately. - **No connect form**: since the endpoint is a deployment setting, pressing "connect" goes straight to the portal's sign-in — OAuth discovery under `"oauth"`, presenting `MCP_PORTAL_TOKEN` under `"token"`, or unauthenticated under `"none"`. The account records `provenance: "deployment"` so an upstream server can't rename itself over `MCP_PORTAL_NAME`. - **Repointing**: changing `MCP_PORTAL_URL` on a deployment with existing connections is a repoint — bindings fail closed at once and users must reconnect (the only endpoint change an account accepts, because it originates from Worker config, not a user form); tokens and in-progress authorization from the old endpoint are dropped, and a persisted generation counter ignores stale in-flight responses. - **Server recovery**: tools are named `{server_id}_{original_name}` (split on the first underscore); `portal_list_servers` (available in every portal session) supplies id/name/enabled state for display. Detection of "is this a portal" is a capability probe for `portal_list_servers`, not a hostname match. `portal_*` tools themselves are excluded from every grant at every scope (a hard capability-boundary rule) since they could let a session widen which upstream servers it can reach. - **Trust tier**: `byo` by default (a portal aggregates servers the deployment administrator never individually reviewed) unless `MCP_PORTAL_TRUST_ANNOTATIONS=true` is set. Otherwise identical approvals/sharing behavior to [[entities/gatekeeper-mcp]] (`addObserver` refuses everyone). - **Limitations**: same core limits as [[entities/gatekeeper-mcp]] (no simulation, no revert, no hooks, no scoping below tool names, `tools/*` only), plus: one portal per deployment (`MCP_PORTAL_URL` is a single value — a second portal needs a second Worker), and an aggregator that doesn't prefix tool names by server exposes no seam to scope a grant to (use `gatekeeper-mcp` for such an endpoint instead). ## How to Use 1. Deploy a Cloudflare MCP server portal (or compatible aggregator prefixing tools as `{server_id}_{name}` and exposing `portal_list_servers`). 2. Set `MCP_PORTAL_URL` (and optionally `MCP_PORTAL_NAME`, `MCP_PORTAL_AUTH`, `MCP_PORTAL_TOKEN`, `MCP_PORTAL_TRUST_ANNOTATIONS`) in the deployment's own configuration — not the shared repo `wrangler.jsonc`. 3. In a Gadget's Connections UI, choose which upstream server behind the portal to grant, then **All tools** (default) or **Choose tools**. 4. Use the resulting binding exactly as documented for [[entities/gatekeeper-mcp]]. 5. Build: `pnpm --filter @gadgets/mcp-portal-gatekeeper build`; test: `pnpm --filter @gadgets/mcp-portal-gatekeeper test`. Run via root `pnpm dev-server`. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-mcp]] — the user-supplied-endpoint counterpart sharing session API, approvals model, and connect machinery - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: MCP" type: entity tags: [gatekeepers, mcp, developer, advanced, emerging] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-mcp-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The MCP gatekeeper connects any [Model Context Protocol](https://modelcontextprotocol.io) server as a Gadgets capability. The user pastes an endpoint URL, the gatekeeper runs the OAuth discovery chain against it, and each of the server's tools becomes a typed method on the session — no Gadgets-specific work is needed on the server side. It runs as its own Cloudflare Worker, auto-discovered via `GATEKEEPER_MCP`. For an administrator-configured, organization-wide gateway to a curated set of servers, use [[entities/gatekeeper-mcp-portal]] instead. ## Characteristics - **One resource type, two grant breadths**: | Granularity | Resource URL | Session type | | --- | --- | --- | | Server — every tool the endpoint offers, including future ones | `` | `McpSession` | | Named tools — only listed tools; anything else refused | `#tool=a&tool=b` | `McpSession` | `` derives from the endpoint's host (for readability, not identity — `acme.com` and `acme.io` both yield `Acme`); `` is four hex characters derived from the resource URL, so two differently-scoped grants get distinct type names. - **Generated session**: a named method per tool from the server's own `inputSchema`, plus a `callTool(name, args)` fallback for tools whose wire name can't be a method name (reserved words like `then`/`map`/`dup`, non-identifiers like `2fa`, or case collisions like `list_issues`/`listIssues`). Read-only tools resolve immediately as observations; everything else queues for approval and is retrieved via `getActionResult(actionId)`. - **Configuration**: | Variable | Meaning | | --- | --- | | `BASE_URL` | Public base URL of this Worker, for OAuth redirects. | | `MCP_CLIENT_NAME` | Client name sent in `initialize` and dynamic client registration. | | `MCP_ALLOW_INSECURE` | `"true"` disables endpoint checks (permits `http://` and private/loopback/link-local/cloud-metadata hosts) — local dev only. | No per-server config exists; an administrator's only lever is whether the connector is offered at all. - **Connect flow**: validates the endpoint against a host blocklist (no private/loopback/metadata hosts; HTTPS required unless `MCP_ALLOW_INSECURE`); opens a Streamable HTTP session and calls `initialize` (unauthenticated success = recorded as public); a `401` triggers the official MCP OAuth chain — protected resource metadata (RFC 9728) → authorization server metadata (RFC 8414) → dynamic client registration (RFC 7591) → authorization code + PKCE (RFC 7636) + resource indicator (RFC 8707), falling back to conventional `/authorize`/`/token`/`/register` paths. Tokens are stored in the `McpAccount` Durable Object and refreshed proactively; a mid-session 401 marks the account as needing reconnection rather than triggering a refresh. The endpoint is fixed at first connect and cannot be repointed by reconnecting. - **Approvals & trust tier**: a tool the server annotates `readOnlyHint: true` resolves immediately as an observation; every other call is queued via `submitAction()` and only reaches the server via `applyAction()`. User-supplied endpoints get the `byo` trust tier — `readOnlyHint` is honored (a knowing departure from MCP's "treat annotations as untrusted unless the server is trusted" guidance, made because refusing it would force an approval prompt on every search/list), but no annotation can auto-apply a write. Every call records `McpToolInfo.classifiedBy`. Auto-approval requires a `vetted` endpoint, which only [[entities/gatekeeper-mcp-portal]] can produce. - **Sharing**: `addObserver` refuses unconditionally — a Gadget bound to an MCP server can only be opened by its owner, since the Gadget runs on the owner's credentials. - **Limitations**: no simulation (no way to predict a tool's effect — queued calls suspend the turn via `awaitDecision`); no revert; no hooks (`notifications/tools/list_changed` is session-scoped, not durable); no scoping below tool names; only `tools/*` (no prompts/resources/sampling/elicitation); tool-list changes are adopted and logged (`catalog.changed`), not pinned; SSRF is enforced post-DNS by the `global_fetch_strictly_public` compatibility flag, not by the connect-time hostname blocklist alone (which doesn't apply under `wrangler dev`, keeping `MCP_ALLOW_INSECURE` usable locally). ## How to Use 1. In a Gadget's Connections UI, start a connection and enter the MCP server's endpoint URL. 2. The gatekeeper attempts `initialize`; if the server requires auth, complete the OAuth flow that appears (dynamic client registration, then authorization code + PKCE). 3. Choose the grant breadth: **All tools** or **Choose tools** (tick specific ones) — the choice is asked explicitly rather than inferred from checkbox state, since it affects behavior when the server adds new tools later. 4. Use the binding (named from the endpoint's host, e.g. `MCP_LINEAR`) via its generated typed methods or `callTool()`: ```ts let search = await env.MCP_LINEAR.searchIssues({ query: "state:open" }); let queued = await env.MCP_LINEAR.createIssue({ title: "Fix the thing" }); if (queued.status === "pending") { let outcome = await env.MCP_LINEAR.getActionResult(queued.actionId); } ``` 5. Build: `pnpm --filter @gadgets/mcp-gatekeeper build`; test: `pnpm --filter @gadgets/mcp-gatekeeper test`. Run via root `pnpm dev-server`. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-mcp-portal]] — the administrator-configured, organization-wide counterpart sharing the same session API and connect machinery - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Notion" type: entity tags: [gatekeepers, productivity, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-notion-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The Notion gatekeeper mediates a Gadget's access to a user's Notion workspace: pages and databases. It runs as its own Cloudflare Worker, auto-discovered via its `GATEKEEPER_NOTION` binding. ## Characteristics - **Auth**: OAuth 2.0 public connection via a Notion **public integration**; `CLIENT_ID`/`CLIENT_SECRET` provided to the worker (locally seeded from `NOTION_CLIENT_ID`/`NOTION_CLIENT_SECRET` via `run-dev-server.js` and root `.dev.vars`). - **Redirect URI**: `/oauth`, defaulting to `http://localhost:8787/gatekeeper/notion/oauth` locally. Recommended integration capabilities: read/insert/update content, read/insert comments, read user info (no email needed — `providesAuth` is false). - Connect flow uses a **two-phase nonce** (initiation → OAuth); access + refresh tokens are stored in a `UserAccount` Durable Object and refreshed on a 401. - **Resource granularities**: | Granularity | URL pattern | Session type | | --- | --- | --- | | Whole workspace | `https://*` | `NotionWorkspace` | | A page or database | `https://www.notion.so/:path+` | `NotionPage` or `NotionDatabase` (detected server-side) | - Only pages/databases the user shares with the integration during the OAuth page-picker are reachable. - **API highlights**: `NotionWorkspace` (`search`, `getPage`, `getDatabase`, `createPage`, `listUsers`); `NotionPage` (`getMetadata`/`getProperties`/`getContent`, `appendContent`, `setTitle`/`setProperties`/`setIcon`, `createSubPage`, `archive`/`restore`, comments, `listChildPages`); `NotionDatabase` (`getSchema`, `query` with typed filters/sorts, `getPage`, `createPage`). Page bodies exchanged as Markdown. - **Approvals, caching & simulation**: every read calls `authorizeObservation()`; every write is staged via `submitAction()` and only performed in `applyAction()`. Reads simulate pending writes (including provisional IDs for created pages). Responses cached in DO storage with short TTLs. - **Data sources**: Notion's database model splits into one or more data sources; `query`/`getSchema`/row creation use Notion-Version `2025-09-03` against the database's primary data source, while pages/blocks/comments/search use `2022-06-28` for consistent IDs/URLs. This split is hidden from the Session API. ## How to Use 1. Create a Notion **public integration** with a redirect URI of `/oauth` and the recommended capabilities (read/insert/update content, read/insert comments, read user info). 2. Set `NOTION_CLIENT_ID`/`NOTION_CLIENT_SECRET` (e.g. in root `.dev.vars`). 3. Build: `pnpm --filter @gadgets/notion-gatekeeper build`; test: `pnpm --filter @gadgets/notion-gatekeeper test`. 4. In a Gadget's Connections tab, connect Notion and use the OAuth page-picker to share specific pages/databases (or the whole workspace) with the integration. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-confluence]] — comparable docs/wiki connector with site/space/page granularities and Markdown body conversion - [[entities/gatekeeper-slack]] — sibling productivity connector - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Scheduler" type: entity tags: [gatekeepers, developer, operator, advanced, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-scheduler-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview Scheduled Tasks (Scheduler) is an **ambient** gatekeeper — it doesn't connect to an external service but lets workspace code register persistent callbacks for elapsed intervals, wall-clock recurrences, and one-time runs. Each account also gets a read-only management app at `/gatekeepers/scheduler` for searching and inspecting schedules across the account. ## Characteristics - **No external auth** — it is capability-authorized rather than identity-authorized: it does not receive Workshop user identity, assert its own ambient policy, or expose external network authority. Workshop's hook admission and observation authorization remain the security boundaries. - **Agent API** (`ScheduleSession`, full contract in `src/types.d.ts`) exposes three registration methods: - `every(everyMs, callback, options)` — elapsed UTC time; minimum interval 60 seconds. - `calendarAt(rule, callback, options)` — local wall-clock time, requires explicit IANA timezone; supports hourly/daily/weekly rules. - `runAt(when, callback, options)` — runs once at an absolute epoch-ms timestamp or timezone-aware wall-clock time. Example: ```ts const callback = await ctx.restore({ type: "dailyBrief" }); const scheduleId = await SCHEDULER.calendarAt( { timeZone: "America/Chicago", freq: "weekly", byDay: ["MO","TU","WE","TH","FR"], hour: 8, minute: 0 }, callback, { title: "Daily brief", description: "Prepare the morning calendar and inbox brief.", occurrences: { count: 10 } }, ); ``` - **Bounds**: recurring calls take `occurrences: { count: N }` or `occurrences: { until: {...} }` (never both); `runAt()` accepts neither. Count bounds *due slots*, not successful runs — a slot consumes one count as soon as due, even if admission/delivery fails. - **Callbacks** implement `ScheduledTaskHook.onSchedule()` and must be made persistent via `ctx.restore()` before registration. Each firing carries `scheduleId`, `runId` (stable across retries — use as idempotency key), `scheduledTime`, `actualTime`, `timeZone`. Failures retry up to 8 total attempts with exponential delay (starts at 1 minute, caps at 1 hour); exhausted schedules enter **Needs attention**. - **Lifecycle**: registration only binds a disabled hook (writes no schedule row); enabling it in the Connections UI creates the account-driver row and arms the alarm; disabling removes the row/capabilities; re-enabling creates fresh active state but preserves recurrence phase (no catch-up of missed work). Disconnecting the Scheduler account revokes its driver, deletes state, and leaves a tombstone. - **Architecture**: one SQLite-backed `ScheduleDriver` Durable Object and one alarm per account, covering all its workspaces. The alarm processes at most 20 due schedules per pass with 4 concurrent deliveries, persists state before crossing RPC boundaries, and uses stable `runId` fencing. - **Limits (fixed policy, not deployment settings)**: 500 enabled/terminal schedule rows per account; 100 per workspace; 100 rows per management page; 20 due schedules per alarm pass / 4 concurrent deliveries; 8 callback attempts per occurrence; titles 200 chars, descriptions 2,000 chars. - **Deployment requirement**: the `allow_irrevocable_stub_storage` compatibility flag is required while stored callback capabilities exist and must not be removed from an existing deployment. Add `GATEKEEPER_SCHEDULER` to the deployment's Workshop service bindings; vendor provisioning defaults to `optional`. ## How to Use 1. Ask the agent to create a scheduled task, or open **Scheduled** and choose a starter prompt; confirm cadence and IANA timezone (for wall-clock schedules) with the user explicitly — never infer timezone from locale. 2. The agent registers a persistent callback (`every`, `calendarAt`, or `runAt`), which returns a schedule ID but leaves the hook disabled. 3. Enable the hook in the Workshop's Connections UI to start it. 4. Use the **Scheduled** management app (All / Active / Needs attention / Finished tabs) to search and inspect schedules; it is read-only — editing, pausing, deletion, and run history stay in Connections. 5. Development: `pnpm install`, `pnpm --filter @gadgets/gatekeeper-scheduler test`, `pnpm --filter @gadgets/gatekeeper-scheduler build`, `pnpm run dev-server`, `pnpm run dev-client`. Troubleshooting: "Scheduled missing from navigation" → check the binding/vendor/connected account; "new schedule not listed/running" → its hook is registered disabled, enable it; "Needs attention" → callback exhausted retries, fix and re-enable; blueprint-created workspaces never inherit schedules — register again. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-email]] — another ambient, hook-driven gatekeeper rather than an OAuth-connected external service - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Slack" type: entity tags: [gatekeepers, productivity, auth, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-slack-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The Slack gatekeeper mediates a Gadget's **read-only** access to a user's Slack workspace: channels, direct messages, threads, members, and search. It runs as its own Cloudflare Worker, auto-discovered via its `GATEKEEPER_SLACK` binding, and never sends or modifies Slack data. ## Characteristics - **Auth**: OAuth 2.0 using a **user token** (`xoxp-…`) requested via `user_scope` (not a bot token), so the agent sees exactly what the connecting user can see — including private channels, DMs, and search. - **App config**: create a Slack app at api.slack.com/apps; provide `CLIENT_ID`/`CLIENT_SECRET` to the worker (locally, `run-dev-server.js` maps `SLACK_CLIENT_ID`/`SLACK_CLIENT_SECRET` from root `.dev.vars`). - **Redirect URL**: `/oauth`, defaulting to `http://localhost:8787/gatekeeper/slack/oauth` locally. - **Token rotation**: enable OAuth & Permissions → Token Rotation. Tokens then last ~12h and refresh via `oauth.v2.access?grant_type=refresh_token`; non-rotating tokens work as a fallback. - **Three grant granularities**, each mapping to a URL pattern and session type: | Granularity | URL pattern | Session type | | --- | --- | --- | | Whole workspace | `https://*` | `SlackWorkspaceSession` | | A conversation (channel/DM/group DM) | `https://app.slack.com/client/:teamId/:conversationId` | `SlackConversation` | | A thread | `https://*.slack.com/archives/:conversationId/:messageId` | `SlackThread` | - **User token scopes per resource**: Workspace — `team:read`, conversation read scopes, `search:read`; Conversation — conversation read scopes, `search:read`; Thread — `channels:history`, `groups:history`, `im:history`, `mpim:history`; Always — `users:read` (conversation read scopes = `channels`/`groups`/`im`/`mpim` `:read` + `:history`). - **API highlights**: `SlackWorkspaceSession` (`getInfo`, `listChannels`, `listDirectMessages`, `listUsers`, `getUser`, `getConversation`, `search`); `SlackConversation` (`getInfo`, `members`, `listMessages`, `getThread`, `search` — hard-restricted to the bound conversation); `SlackThread` (`getRoot`, `listReplies`). List/search methods return paginated `Cursor` objects; mentions render with readable names. ## How to Use 1. Create a Slack app and set its OAuth redirect URL to `/oauth` (`http://localhost:8787/gatekeeper/slack/oauth` for local dev). 2. Enable **token rotation** under OAuth & Permissions. 3. Request the user-token scopes needed for the granted resources (see Characteristics); `users:read` is always requested. 4. Supply `SLACK_CLIENT_ID`/`SLACK_CLIENT_SECRET` (e.g. via root `.dev.vars`). 5. Build: `pnpm --filter @gadgets/slack-gatekeeper build`. 6. In a Gadget's Connections tab, connect a Slack account and grant access at the workspace, conversation, or thread level as needed. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-notion]] — similar OAuth-app connector pattern for a productivity workspace - [[entities/gatekeeper-confluence]] — similar multi-granularity resource model - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Spotify" type: entity tags: [gatekeepers, developer, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-spotify-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The Spotify gatekeeper provides Spotify Web API integration for Gadgets: once connected, gadgets can search the catalog, read and edit the user's library and playlists, manage follows, and control playback on Spotify Connect devices, all behind the approval queue. It is **not** an authentication provider (`providesAuth: false`) — Spotify is not offered as a "Continue with…" sign-in method. ## Characteristics - **Two resource granularities**: **Spotify Account** (whole-instance: profile, catalog search, library — saved tracks/albums, top items, recently played —, follows, playlists, and playback control via `getPlayer()`) and **Spotify Playlist** (read/edit/follow/unfollow a single playlist). - **Targets Spotify's post-February-2026 development-mode API**: library writes use generic `PUT`/`DELETE /me/library`, playlist contents use `/playlists/{id}/items`; fields Spotify removed in dev mode (e.g. track `popularity`, parts of the user profile) return as `null`. - **Auth**: OAuth. Redirect URI must be exactly `http://127.0.0.1:8787/gatekeeper/spotify/oauth` — **Spotify does not allow `localhost`** as a redirect URI for new apps, so the loopback literal `127.0.0.1` is required (replace host/port with `BASE_URL` in production; production must use HTTPS). - **Config (verbatim)**, `.env` at `packages/gatekeeper-spotify/.env`: ```bash CLIENT_ID=your-client-id-here CLIENT_SECRET=your-client-secret-here BASE_URL=http://127.0.0.1:8787/gatekeeper/spotify ``` Because the redirect uses `127.0.0.1`, the Workshop must also be opened at `http://127.0.0.1:8787` (not `localhost`) so the OAuth popup stays same-host. - New Spotify apps start in **development mode**, limited to explicitly added users (User Management in the dashboard) until extended quota is requested. - **Playback control requires Spotify Premium** — player write commands (play/pause/seek/volume) return 403 for free accounts. - **Approvals & simulation**: reads are logged, writes queue for human approval; reads optimistically reflect the user's own pending edits (e.g. a pending playlist add shows in `listTracks`). Playback commands are gated but **not** simulated — `getState()` always shows real device state. - **Limitations**: non-owned/non-collaborated playlists withhold track contents (`listTracks()` returns empty; `getDetails()` metadata still works); some third-party Spotify Connect endpoints (e.g. Music Assistant) can be invisible to `getDevices()`, and `getState().device` may misidentify the active device. ## How to Use 1. Create a Spotify app in the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard); add redirect URI `http://127.0.0.1:8787/gatekeeper/spotify/oauth`; select **Web API** under APIs used; copy Client ID/Secret. 2. Create `packages/gatekeeper-spotify/.env` with `CLIENT_ID`, `CLIENT_SECRET`, and `BASE_URL=http://127.0.0.1:8787/gatekeeper/spotify`. 3. Add your Spotify account under **User Management** in the dashboard (development-mode requirement). 4. Open the Workshop at `http://127.0.0.1:8787` (not `localhost`), go to a Gadget's Connections tab, **+ New Connection**, choose whole account or a specific playlist, authorize, and (for a playlist) pick it via the resource picker. Common errors: "Spotify Gatekeeper Not Configured" (missing CLIENT_ID/SECRET), `INVALID_CLIENT: Invalid redirect URI` (host mismatch between dashboard and `BASE_URL`), "User not registered in the Developer Dashboard" (account missing from User Management). ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-homeassistant]] — comparable approval/simulation model for device control - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: Supabase" type: entity tags: [gatekeepers, developer, deployment, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-supabase-readme-md.md"] confidence: medium cfos_snapshot: "2026-08-05" --- ## Overview The Supabase gatekeeper integrates Gadgets with the Supabase Management API via OAuth2, letting a Gadget run read-only and approval-gated mutating SQL, introspect schema, and list edge functions and storage buckets for a connected project or organization. ## Characteristics - **Two resource granularities**: **Project** (`https://supabase.com/dashboard/project/:ref`) — the recommended unit; a hosted Postgres database plus its auth, storage, and edge functions — and **Organization** (`https://supabase.com/dashboard/org/:slug`) — broader access across every project in an organization. - A connected account corresponds to the organization the user authorizes during the OAuth consent flow. - **Auth**: OAuth app created under the Supabase organization's **OAuth Apps** tab (`https://supabase.com/dashboard/org/_/apps`). Must use the OAuth app's **Client ID + Client Secret** — not a personal access token or project API key — since the `authorization_code` exchange requires OAuth app credentials. - **Redirect URI**: `${PUBLIC_BASE_URL}/gatekeeper/supabase/oauth` (`http://localhost:8787/gatekeeper/supabase/oauth` for local dev). - **Config (verbatim)**, seeded from shell/`.dev.vars` by `run-dev-server.js` into the worker's `CLIENT_ID`/`CLIENT_SECRET`: ``` SUPABASE_CLIENT_ID= SUPABASE_CLIENT_SECRET= ``` - **Approvals**: reads are logged as observations; mutating SQL (`execute()`) is submitted to the approval queue and only runs once a human approves it. - **No simulation for writes**: mutating SQL is not simulated — arbitrary statements cannot be reliably previewed through the stateless query endpoint, so a subsequent `query()` will not observe an un-approved change (documented in the API types). ## How to Use 1. In Supabase organization settings, open **OAuth Apps** and **Add application**. 2. Set the Redirect URI to `${PUBLIC_BASE_URL}/gatekeeper/supabase/oauth`. 3. Copy the Client ID and Client Secret (secret shown once). 4. Set `SUPABASE_CLIENT_ID`/`SUPABASE_CLIENT_SECRET` in the environment/`.dev.vars` that `run-dev-server.js` reads. 5. In a Gadget's Connections tab, connect Supabase and choose either a specific **Project** or the broader **Organization** grant. ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-cloudflare]] — sibling infra-provider connector using OAuth for platform access rather than sign-in alone - [[entities/gatekeepers-catalog]] --- title: "Gatekeeper: ZoomInfo" type: entity tags: [gatekeepers, developer, advanced, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-zoominfo-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview The ZoomInfo gatekeeper integrates Gadgets with ZoomInfo's GTM (go-to-market) API via OAuth2 (Authorization Code + PKCE), letting a gadget look up controlled filter values, search company/contact/intent/scoop/news data for free, enrich matched records (consuming credits), use Copilot features (lookalikes, recommendations, AI account summaries), and read usage/credit counters — all for one connected ZoomInfo account. ## Characteristics - **Single whole-account resource**: `https://app.zoominfo.com/`, scoped to one connected account's OAuth grant and package entitlements. ZoomInfo is **not** a sign-in provider (`getAuthenticatedEmail()` returns `null`). - **Capabilities**: Lookup (resolve controlled filter values, discover entitled enrich fields — free); Search (companies, contacts, intent signals, scoops, news — free, full firmographic/technographic filters); Enrich (turn matches into full detail — consumes credits); Copilot (lookalikes, contact recommendations, AI account summaries + ask, curated insight signals); Usage (credit/limit counters). - **Auth**: OAuth2 Authorization Code + PKCE (S256), with refresh-token rotation; the `authorization_code` exchange posts Client ID + Secret as HTTP Basic auth (form-urlencoded) to the token endpoint. - **Scopes requested (verbatim, no wildcard/`lookup` scope)**: ``` api:data:company api:data:contact api:data:intent api:data:news api:data:scoops api:recommendations:read api:account-summary:read api:insights:read ``` - **Redirect URI**: `${BASE_URL}/gatekeeper/zoominfo/oauth` (`http://localhost:8787/gatekeeper/zoominfo/oauth` locally). - **Config (verbatim)**, seeded from root `.dev.vars`: ``` ZOOMINFO_CLIENT_ID= ZOOMINFO_CLIENT_SECRET= ``` Optional overrides: `BASE_URL` (default `http://localhost:8787/gatekeeper/zoominfo`) and `ZOOMINFO_API_BASE_URL` (default `https://api.zoominfo.com/gtm`). - **Credits**: search/lookup/recommendations/insights/account summaries are free; enrichment costs roughly one credit per newly-enriched record (already-owned or no-match/error results are free); `getCreditUsage()` is authoritative — the per-record `creditCharged` flag is a display-only upper bound. - **Approvals**: reads are logged as observations; each `enrich*` call is submitted to the approval queue and spends credits only once approved, with results fetched afterward via `getEnrichmentResult(ticket)`. Enrichments are **not** simulated — they carry `awaitDecision`, so the agent's turn suspends until the decision. - **Constraints**: intent/scoop *search* filters by firmographics only (no company identity); to target a known company use `enrichIntent`/`enrichScoops` instead. `state` and `country` are mutually exclusive — ZoomInfo silently ignores `state` when `country` is set, so the gatekeeper rejects the combination up front. Every call is scoped to the account's package entitlements; unentitled fields return empty. ## How to Use 1. Create an OAuth application in the [ZoomInfo Developer Portal](https://api.zoominfo.com/). 2. Set the redirect URI to `${BASE_URL}/gatekeeper/zoominfo/oauth`. 3. Enable all eight API scopes listed above. 4. Copy Client ID/Secret into `ZOOMINFO_CLIENT_ID`/`ZOOMINFO_CLIENT_SECRET` (root `.dev.vars`); ensure the app issues refresh tokens (enable the refresh-token grant). 5. Connect the account through the Connections UI; use `getCreditUsage()` to track spend before/after enrichment calls. Common errors: "ZoomInfo Gatekeeper Not Configured" (missing credentials), `invalid_scope` (scope mismatch with the eight listed), redirect URI mismatch, "ZoomInfo did not return a refresh token" (refresh-token grant not enabled). ## Related Entities - [[concepts/gatekeepers]] - [[concepts/writing-a-gatekeeper]] - [[entities/gatekeeper-supabase]] — comparable OAuth2 + Management/GTM API connector with approval-gated writes - [[entities/gatekeepers-catalog]] --- title: "Gatekeepers Catalog" type: entity tags: [gatekeepers, overview, foundational, well-established] created: 2026-08-05 updated: 2026-08-05 sources: ["raw/github_doc-packages-gatekeeper-github-readme-md.md", "raw/github_doc-packages-gatekeeper-github-storage-schema-md.md", "raw/github_doc-packages-gatekeeper-google-readme-md.md", "raw/github_doc-packages-gatekeeper-slack-readme-md.md", "raw/github_doc-packages-gatekeeper-notion-readme-md.md", "raw/github_doc-packages-gatekeeper-confluence-readme-md.md", "raw/github_doc-packages-gatekeeper-email-readme-md.md", "raw/github_doc-packages-gatekeeper-homeassistant-readme-md.md", "raw/github_doc-packages-gatekeeper-spotify-readme-md.md", "raw/github_doc-packages-gatekeeper-supabase-readme-md.md", "raw/github_doc-packages-gatekeeper-zoominfo-readme-md.md", "raw/github_doc-packages-gatekeeper-scheduler-readme-md.md", "raw/github_doc-packages-gatekeeper-cloudflare-readme-md.md", "raw/github_doc-packages-gatekeeper-mcp-readme-md.md", "raw/github_doc-packages-gatekeeper-mcp-portal-readme-md.md"] confidence: high cfos_snapshot: "2026-08-05" --- ## Overview Cloudflare OS ships 14 **Gatekeeper** connectors — see [[concepts/gatekeepers]] for the framework they share (approval queues, capability-based Session APIs, OAuth/credential handling patterns). Each connector safely mediates a Gadget's access to one external system (or, for Email and Scheduler, provides an ambient capability with no external service at all). This page is the index: what each connector connects to, and a link to its full entity page. ## Characteristics Connectors group loosely by what they're for: | Connector | Connects to | Entity page | | --- | --- | --- | | **Productivity / docs** | | | | Google | Gmail, Docs, Sheets, Calendar, BigQuery | [[entities/gatekeeper-google]] | | Notion | Notion pages and databases | [[entities/gatekeeper-notion]] | | Confluence | Atlassian Cloud Confluence spaces/pages/blog posts | [[entities/gatekeeper-confluence]] | | Slack | Slack workspace (read-only: channels, DMs, threads, search) | [[entities/gatekeeper-slack]] | | Email | Inbound email at a `@` address (ambient — this gatekeeper *is* the mail service) | [[entities/gatekeeper-email]] | | **Dev / infra** | | | | GitHub | GitHub repositories, issues, pull requests (+ sign-in) | [[entities/gatekeeper-github]] | | Cloudflare | Cloudflare account (sign-in + AI Gateway billing) | [[entities/gatekeeper-cloudflare]] | | Supabase | Supabase projects/organizations (Postgres, auth, storage, edge functions via Management API) | [[entities/gatekeeper-supabase]] | | Home Assistant | A self-hosted or Nabu Casa Home Assistant instance (devices, dashboards, history) | [[entities/gatekeeper-homeassistant]] | | Scheduler | No external system — ambient scheduled-callback registration for workspace code | [[entities/gatekeeper-scheduler]] | | **Protocol** | | | | MCP | Any user-supplied Model Context Protocol server | [[entities/gatekeeper-mcp]] | | MCP Server Portal | An administrator-configured MCP portal fronting an organization's approved servers | [[entities/gatekeeper-mcp-portal]] | | **Other** | | | | Spotify | Spotify account (catalog, library, playlists, playback) | [[entities/gatekeeper-spotify]] | | ZoomInfo | ZoomInfo GTM data (company/contact/intent/scoop/news search and enrichment) | [[entities/gatekeeper-zoominfo]] | Common threads across the catalog (see individual pages for specifics): - Most connectors use **OAuth 2.0** (GitHub, Google, Slack, Notion, Confluence, Spotify, Supabase, ZoomInfo, Cloudflare, MCP, MCP Server Portal); **Home Assistant** instead uses a pasted long-lived access token + URL (no central OAuth directory exists across HA instances); **Email** and **Scheduler** have no external auth at all since they are ambient, in-process capabilities. - Three connectors double as **sign-in providers** via `AUTH_GATEKEEPERS` — [[entities/gatekeeper-github]], [[entities/gatekeeper-google]], and [[entities/gatekeeper-cloudflare]] — each requesting minimal, transient identity scopes for login distinct from their fuller "connection" scopes. - Most connectors follow the shared **approval-queue + simulation** pattern: reads call an `authorizeObservation()`-style method and resolve immediately; writes are queued via `submitAction()` and only take effect once a human approves (`applyAction()`). Depth of simulation varies — GitHub, Notion, Confluence, and Home Assistant simulate pending writes at read time; Supabase, Spotify's playback commands, ZoomInfo enrichment, and MCP tool calls do not simulate (too costly, not previewable, or protocol-incapable, respectively). - [[entities/gatekeeper-mcp]] and [[entities/gatekeeper-mcp-portal]] are the two halves of one protocol-level connector family: same session API and connect machinery, differing in who supplies the endpoint (the user, vs. a deployment administrator) and therefore in trust tier and grant shape. ## How to Use To add a new Gatekeeper connector to a deployment: see [[concepts/writing-a-gatekeeper]] for the framework contract, then follow one connector's README as a template — [[entities/gatekeeper-mcp]] and [[entities/gatekeeper-github]] are the most fully documented examples of the OAuth + Session-API + approval-queue pattern. To connect an *existing* connector as a user, open a Gadget's **Connections** tab, **+ New Connection**, and pick the relevant resource type; each connector's own page has its specific setup steps (OAuth app creation, redirect URIs, scopes, and environment variables). ## Related Entities - [[concepts/gatekeepers]] - [[entities/gatekeeper-github]] - [[entities/gatekeeper-google]] - [[entities/gatekeeper-slack]] - [[entities/gatekeeper-notion]] - [[entities/gatekeeper-confluence]] - [[entities/gatekeeper-email]] - [[entities/gatekeeper-homeassistant]] - [[entities/gatekeeper-spotify]] - [[entities/gatekeeper-supabase]] - [[entities/gatekeeper-zoominfo]] - [[entities/gatekeeper-scheduler]] - [[entities/gatekeeper-cloudflare]] - [[entities/gatekeeper-mcp]] - [[entities/gatekeeper-mcp-portal]] # Change Log ## 2026-08-05 — Initial build Built from the cloudflare/cloudflare-os repo docs (v2 early-access, snapshot 2026-08-05) plus the announcement blog. Cloudflare OS is an open-source AI productivity "operating system" on Cloudflare Workers — agent chat + sandboxed "gadget" apps + the Gatekeepers security framework — self-hostable as "Your Company OS". **Pages (29):** 14 concepts + 15 entities (14 Gatekeeper connectors + a catalog hub). **Sourcing notes:** - Docs-only build: the repo has no releases and no high-comment issues yet (brand-new v2), so no release-digest or issue-mirror pages. - Two Gatekeeper connectors present in the repo — `gatekeeper-context` (the Context Library / company-knowledge mechanism) and `gatekeeper-linear` — have NO README in the repo, so they get no grounded entity page. The Context Library is covered at the concept level (agent-chat-and-company-knowledge) from the README/AGENTS docs; both are flagged here as documentation gaps to fill when upstream adds READMEs. - The `observers` page reflects a curation catch: `docs/observers.md` is titled "Observer Tracking & Read-Through Sharing Permissions" — a sharing-security mechanism, not a general observability system — and the page is grounded in what the source actually specifies, not the assumed framing. - No XL tier: too early (no releases/issue corpus). The Gatekeepers framework + connector catalog are the natural XL seed as the project matures.