Cat Factory
Home
Get Started
GitHub
Home
Get Started
GitHub
  • Start

    • Introduction
    • Core Concepts
    • Quick Start
    • Tutorial: Your First Task to a Merged Pull Request
  • Guides

    • Recipes

      • Cookbook
    • Plan the work

      • Design Your Board
      • Clarify Requirements
      • Author a Document
      • Plan an Initiative
    • Run pipelines

      • Choose and Edit a Pipeline
      • Run a Pipeline
      • Schedule Recurring Work
      • Review and Merge Pull Requests
      • Control Spend with Budgets
    • Connect

      • Connect a Repository
      • Connect Issue & Document Sources
      • Feed Design Context to Agents
      • Preview and Test a Frontend
    • Models & prompts

      • Connect a Model Provider
      • Apply Standards with Prompt Fragments
      • Run a Claude Skill as a Step
      • Compare Prompts and Models in the Sandbox
    • Collaborate

      • Invite and Manage Your Team
      • Share Services Across Workspaces
      • Register Foundational Services
  • Deploy

    • Run Locally
    • Deploy to Node.js
    • Deploy to Cloudflare
    • Deploy on Kubernetes
    • Lay Out a Kubernetes Cluster
    • Set Up a Local Kubernetes Cluster on Windows
    • Register the GitHub App
    • Set Up Enterprise SSO
    • Set Up Your Deployment Repository
    • Configuration
  • Operate

    • Observability
    • Set Up Notifications
    • Run Jobs on Your Own Runners
    • Provision Ephemeral Environments
    • Debug a Run from Outside the Browser
    • Troubleshooting
    • Upgrades & Data Retention
  • Extend

    • Add a Custom Agent Kind
    • Add a Custom Gate or Judge
    • Add a Custom Provider
    • Extend the App with Frontend Modules
    • Integration Manifests
    • Give Agents External Tools (MCP)
    • Package a Reusable Operation
    • Register an Initiative Preset
    • Public API
    • Official SDKs
    • MCP Server
    • Cloudflare OS Gatekeeper
  • Reference

    • Architecture
    • Agent Isolation Model
    • Security Model & Hardening
    • Packages & Repository Layout
    • GitHub and GitLab Support Matrix
    • Environment Variables
    • API Endpoint Reference
    • Glossary

Configuration

For whoever owns the deployment's secrets and toggles. It is the reference for every environment variable you set when deploying Cat Factory, grouped by concern: authentication, model providers, infrastructure, service wiring, and feature toggles. The full generated list of variables, including ones no page here narrates, is Environment Variables.

When a required value is missing

A mandatory variable that is missing or invalid does not crash-loop the container. The backend starts anyway and serves a minimal fallback: the health check returns 200 with status misconfigured (so the orchestrator keeps the container alive instead of restarting it in a loop that hides the problem), and the SPA branches to a full-screen Backend not configured screen listing each problem with the variable name, what it is for, how to fix it, and a link to the matching documentation. Only variable names and remedies are shown, never any secret value, so the screen and the matching boot log are safe to share. The same structured problem list is returned as a 503 to non-browser callers.

The values validated this way include DATABASE_URL, ENCRYPTION_KEY (present, valid base64, decodes to ≥ 32 bytes), AUTH_SESSION_SECRET, HARNESS_SHARED_SECRET, TELEMETRY_DB (on the Worker), the primary DB binding (on the Worker), a login provider on a remote deployment, the container-executor wiring, a DB_SCHEMA that isn't a valid Postgres identifier, and malformed AGENT_MODELS JSON. GITHUB_APP_PRIVATE_KEY (and the privileged App key) must be a PKCS#8 PEM; a GitHub-issued PKCS#1 BEGIN RSA PRIVATE KEY is rejected with the exact openssl pkcs8 -topk8 conversion to run. Setting REDIS_URL without the optional ioredis package installed is also fatal, with the pnpm add ioredis fix. Fix the listed values and reload.

Warnings that don't block boot

Some misconfigurations are logged as a single structured warning (with a doc link) and boot continues on the built-in default rather than stopping:

  • A numeric knob set to a non-numeric value (for example AGENT_MAX_OUTPUT_TOKENS, CONTAINER_MAX_AGE_MINUTES, or any of the retention-day vars) logs NAME is set to "…", which is not a number — ignoring it and using the built-in default and falls back.
  • Only one half of CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN being set names the missing half.
  • REDIS_URL set but unreachable: a boot probe logs the credential-free host:port and a redis-cli -u <REDIS_URL> ping check to run, then continues (cross-node coordination stays off until Redis answers).
  • In local mode, a GITHUB_PAT that fails a quick GET /user probe (revoked, forbidden, or missing the repo/workflow scopes) logs a warning with a pre-scoped token-creation link.

One boot check is deliberately fatal-then-recoverable: when the database refuses the connection on a loopback host (a localhost that resolves to IPv6 ::1 on Windows or Docker Desktop, a common footgun), the misconfigured screen names DATABASE_URL and the 127.0.0.1 fix, rather than crash-looping. A refused connection to a remote database still crashes and retries, so a transient outage isn't frozen behind the screen.

Elaborated failure messages

Runtime failures an operator has to act on now carry a structured cause, a fix, and a doc link while preserving the original error text: rejected GitHub/GitLab API calls (per HTTP status), GitHub App installation-token mint failures, webhook signature rejections (logged operator-side with the env var to compare, while the caller still gets a terse 401), credential-decryption failures (which name whether the ENCRYPTION_KEY was rotated or a stored secret is corrupted), unsupported model or Bedrock models, and container/runner dispatch failures (a 404 names a stale executor-harness image). These surface in the boot log and, where they belong to a run, on the run's step (see Observability → Run and step diagnostics).

Health vs. readiness

The Node backend exposes two public endpoints. GET /health is a static liveness signal (200{status:"ok"}, or misconfigured above) that your orchestrator restarts on. GET /ready is a readiness signal a load balancer drains on: it round-trips the Postgres pool with a bounded SELECT 1 and checks the pg-boss worker, returning 200 {status:"ready"} or 503{status:"not_ready"} with a per-check breakdown, and flips to not-ready the moment graceful shutdown begins. The Cloudflare Worker has no long-lived process and no readiness endpoint.

Authentication

A deployment can offer three sign-in providers in any combination: GitHub OAuth, Google OAuth, and email/password. Auth turns on as soon as any provider is configured together with a strong AUTH_SESSION_SECRET; each provider stays off until its own credentials are present. Regardless of how people sign in, repository access comes from the Register the GitHub App installation unless a run's initiator has stored a personal token, so a Google- or password-only user works fully. See Which credential a run pushes with. For who is allowed to create an account and how roles and invitations work, see Invite and Manage Your Team.

VariablePurpose
AUTH_SESSION_SECRETSigns session tokens (≥ 32 chars). Auth fails closed without it, whichever providers are set.
GITHUB_OAUTH_CLIENT_IDThe GitHub App's OAuth client ID, for "Login with GitHub".
GITHUB_OAUTH_CLIENT_SECRETThe App's OAuth client secret.
GOOGLE_OAUTH_CLIENT_IDGoogle OAuth client ID. Both Google vars must be set, or Google login stays off.
GOOGLE_OAUTH_CLIENT_SECRETGoogle OAuth client secret.
GOOGLE_OAUTH_REDIRECT_URLOptional. Explicit callback; defaults to ${origin}/auth/google/callback.
AUTH_PASSWORD_ENABLEDSet to true to offer email/password signup and login.
AUTH_TRUST_PROXYLet the password throttle read the client address from x-forwarded-for instead of the socket peer. Set it only when a proxy you control terminates every request: otherwise the header is attacker-supplied and a client-chosen address defeats the throttle. Node and local only; the Worker reads cf-connecting-ip, which its edge injects and overwrites.
AUTH_TRUST_PROXY_HOPSHow many trusted proxies sit in front of this process, used to pick the client hop out of an x-forwarded-for chain (default 1; a CDN plus a load balancer is 2). A shorter chain is discarded in favour of the socket peer.
AUTH_ALLOWED_EMAIL_DOMAINSComma-separated domains allowed to self-sign-up without an invite (password/Google), and allowed to sign in with a PAT. Empty means invite-only.
AUTH_ALLOWED_LOGINSComma-separated GitHub/GitLab logins allowed to sign in with a PAT.
AUTH_ALLOWED_ORGSComma-separated orgs whose members may sign in with a PAT.
GITHUB_APP_IDIdentifies the GitHub App used for repository operations.
GITHUB_APP_PRIVATE_KEYPKCS#8 private key that signs App requests and mints installation tokens.
GITHUB_WEBHOOK_SECRETVerifies inbound webhook payloads. Verification fails closed on an empty secret, so set a non-empty value on any deployment that receives GitHub webhooks.
GITHUB_APP_SLUGThe App's URL slug, used to build installation links.
GITHUB_API_BASEGitHub API base. Defaults to https://api.github.com; override for GitHub Enterprise.
GITHUB_OAUTH_BASEOAuth host. Defaults to https://github.com; override for GitHub Enterprise.
GITHUB_PRIVILEGED_APP_IDOptional second App that can create org repos. See Programmatic repository creation.
GITHUB_PRIVILEGED_APP_PRIVATE_KEYPKCS#8 private key for the privileged App. Both vars must be set, or the tier stays off.

New-user creation is invite-only unless an email domain is allowlisted: a person gets in by redeeming an email invitation or by signing up with an address on AUTH_ALLOWED_EMAIL_DOMAINS. With neither, signup is refused.

Password sign-in is rate-limited on a durable, cross-replica ledger rather than per process, so the throttle holds on a multi-replica deployment. It applies a burst cap per IP and email pair plus an aggregate per IP against credential stuffing. An in-process counter remains as the backstop for when the ledger itself is unreachable.

A remote (hosted) Node deployment has no anonymous tier: it fails to boot unless at least one provider is configured (GitHub OAuth, Google OAuth, or AUTH_PASSWORD_ENABLED with a strong AUTH_SESSION_SECRET). Users can also sign in with their own GitHub or GitLab PAT: they paste the token, the server resolves it to their account, and the same AUTH_ALLOWED_LOGINS / AUTH_ALLOWED_ORGS / AUTH_ALLOWED_EMAIL_DOMAINS allowlists decide who gets in (it fails closed when all three are empty). Local mode is the exception: it signs in with the deployment's configured PAT or a local password. GitLab PAT sign-in is offered only when the deployment has a GitLab connection configured (GITLAB_TOKEN, below); GitLab group membership then counts toward AUTH_ALLOWED_ORGS, matching GitHub.

What a personal access token can do on the run path

A PAT is the operational credential on two deployment shapes, not just a sign-in identity. In local mode one token is both the identity and what every agent step clones, pushes and merges with. On a hosted deployment, a run initiator's stored github_pat outranks the GitHub App installation on the run path, unless the workspace turns that off. On both, a token too narrow for the work would otherwise reach its first failure several steps into a pipeline, as a 403 out of a container.

Three surfaces report what the token in use can actually do, from one classification and one required-scope list: the connect form's warnings when a token is stored, the local deployment's boot log, and a check on board load that resolves the token a run would present through the same path the dispatch mint uses. That last one raises a banner only on an established blocking gap or a rejected token, and names whether the credential is the deployment's or the signed-in user's. See Troubleshooting → a step fails cloning, pushing or merging.

The token's raw scope list is deliberately not published on that response: any member can read it, and the breadth of a shared deployment credential is not a member-level fact. The per-capability verdict is what a reader can act on. None of this narrows a token either. Mint a fine-grained PAT restricted to the repositories the deployment works on rather than relying on a check to catch a broad one.

GitLab (source control)

GitLab is a first-class source-control backend on every runtime, not just local mode. It is opt-in and off until you set a token. With it configured, a GitLab repo clones, pushes, gates on real CI, and merges through a real merge request, and users can sign in with a GitLab PAT. See Connect a Repository → GitLab.

VariablePurpose
GITLAB_TOKENEnables GitLab on Cloudflare and Node. In local mode the equivalent is GITLAB_PAT. Needs the api scope. With ENCRYPTION_KEY also set, workspaces can connect their own GitLab accounts with a personal access token.
GITLAB_API_BASEOptional. GitLab REST v4 base for a self-managed instance, e.g. https://gitlab.example.com/api/v4. Defaults to the public GitLab API.
GITLAB_WEBHOOK_SECRETOptional. Verifies inbound GitLab webhook payloads (merge request, issue, push, pipeline).
GITLAB_CONNECTION_IDOptional. Logical id for the GitLab connection. Defaults to gitlab.

LLM providers

Direct provider API keys (OpenAI, Anthropic, Qwen, DeepSeek, Moonshot, OpenRouter, LiteLLM) are onboarded in the UI, scoped to an account, workspace, or user, pooled, and stored encrypted under ENCRYPTION_KEY. The same is true of vendor credentials: a coding-plan subscription (Claude, GLM, or Codex, kept per-user) or a poolable vendor credential (Kimi, DeepSeek), run through the Claude Code or Codex harness. None of these needs a provider env var; they only need ENCRYPTION_KEY set. Local runners (Ollama, LM Studio, …) are likewise pure per-user UI configuration with no deployment env var. See Connect a Model Provider.

What stays in the environment is the Cloudflare Workers AI fallback, AWS Bedrock, the aggregator-gateway base URLs, and routing defaults:

VariablePurpose
CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKENServe Cloudflare Workers AI over REST off Cloudflare (no provider key; optional CLOUDFLARE_AI_GATEWAY). On the Worker, the AI binding serves it instead.
OPENAI_BASE_URL / QWEN_BASE_URL / DEEPSEEK_BASE_URL / MOONSHOT_BASE_URLOptional base-URL overrides for the OpenAI-compatible direct providers (the keys themselves come from the UI pool).
OPENROUTER_BASE_URLOptional. Overrides the OpenRouter gateway; defaults to the public gateway (https://openrouter.ai/api/v1), so OpenRouter works with just a connected key.
LITELLM_BASE_URLRequired to enable LiteLLM. Your self-hosted LiteLLM gateway URL. Until it is set, a connected LiteLLM key yields no selectable model.
BEDROCK_REGION + AWS credentials + BEDROCK_MODELSAWS Bedrock, via @cat-factory/provider-bedrock. BEDROCK_MODELS="" (set but blank) means "allow all".

With no Cloudflare provider registered and no keys connected, a model has nothing to resolve to (the picker shows nothing selectable). Default routing is tunable with AGENT_DEFAULT_PROVIDER, AGENT_DEFAULT_MODEL, AGENT_DEFAULT_TEMPERATURE, AGENT_MAX_OUTPUT_TOKENS, and per-kind overrides via AGENT_MODELS; a workspace can override the model per agent kind at runtime (see Choosing models).

Credential encryption

One shared master key encrypts every integration's per-workspace credentials at rest: connected Jira sites, runner-pool API tokens, environment-provider auth, Slack bot tokens, and pooled or personal model subscriptions. The cipher domain-separates per integration internally, so a single key is safe across all of them.

VariablePurpose
ENCRYPTION_KEYBase64 key, ≥ 32 bytes decoded (openssl rand -base64 32). Required. The always-on Jira task-source integration fails to boot with a loud config error until it is set.

Use one stable key

A single ENCRYPTION_KEY backs all of these integrations. Use the same key value across restarts and replicas, or encrypted credentials become unreadable. If a credential can't be decrypted (the key was rotated or regenerated), the error names the key and the affected credential and tells you to restore the original key or re-enter that credential; one broken credential is isolated, so unrelated providers keep working rather than the whole config failing.

Spend caps (operator ceilings)

Workspace, account, and per-user monthly budgets are set in the UI (see Control Spend with Budgets). Two optional env vars let an operator impose a hard ceiling that a user cannot exceed from the UI:

VariablePurpose
BUDGET_MAX_MONTHLY_PER_ACCOUNTCaps the per-account monthly limit. It clamps whatever the account budget UI submits, and stands in as the account ceiling when no account limit is set.
BUDGET_MAX_MONTHLY_PER_USERThe same for the per-user monthly limit.

Each applies only when set to a finite number >= 0; unset means no operator ceiling for that tier. The caps are in the base pricing currency and are surfaced read-only in the budget UI, where the matching field is clamped to the cap and Save is refused above it.

Infrastructure

VariablePurpose
DATABASE_URLPostgreSQL connection string (Node.js deployment only).
DB_SCHEMAPostgres schema the app's unqualified tables live in. Defaults to public; set it to share one database with another service. Must be a valid lowercase identifier or boot fails.
DB_MIGRATIONS_SCHEMASchema holding the migration ledger. Defaults to drizzle; relocate it so it can't collide with another tool's migration table on a shared database.
DB_PGBOSS_SCHEMASchema pg-boss uses for its job queues. Defaults to pgboss.
Container image registry + pull credentialsSource of the executor-harness image.
HARNESS_SHARED_SECRETInbound-auth secret for the executor harness (≥ 16 chars, stable). The backend injects it into each per-run container's env and sends it as the x-harness-secret header, so the harness rejects any call that doesn't carry it. A deployment that dispatches container jobs validates it at boot and fails with a config error when it is unset; keep the value stable so a container re-attach after a restart still authenticates. A self-hosted runner pool configures its own secret pool-side.
RUNNERS_ENABLEDSet to true to turn on self-hosted runner pools (also requires ENCRYPTION_KEY).
Runner pool manifestDeclarative description of your self-hosted execution pool (see Manifests).

When a workspace is missing infrastructure a run needs, the app says so up front rather than failing mid-run. It raises a per-area setup banner, with a deep link to the right config screen, when ephemeral environments, the agent executor (self-hosted runner pool, remote Node), or binary content storage is undefined. Each banner can be dismissed for the session or permanently per user.

Content storage (binary artifacts)

The Tester's screenshots for the Visual Confirmation gate are kept in a binary-artifact store. This is configured per account in the UI (Account → Deployment settings), not through environment variables, and each account picks its own backend:

BackendRuntimesNotes
fsNode, localOn-disk under a base path (default .file-storage, git-ignored). The default on local. Local-disk only, so it is unsafe for a scaled or ephemeral Node deployment where instances don't share a disk.
s3Node, localAn S3 bucket. Enter the bucket and keys (sealed at rest) in the UI; the keys can fall back to ambient AWS credentials.
r2CloudflareThe Worker's R2 binding. The default and only backend on Cloudflare (the AWS SDK is kept out of the Worker bundle; for S3, run Node/local).

The default is fs on local, r2 on Cloudflare, and off on Node until an account configures one. A pipeline that includes an agent needing binary storage (the UI Tester, which uploads its screenshots) is refused at start when the account has no store configured, with a message that names the fix, rather than failing mid-run. Pipelines that don't use such an agent are unaffected. Switching an account's backend orphans artifacts stored under the previous one.

Node container execution

On the Node.js runtime, repo-operating agent kinds run on a runner pool, and that path only activates once the deployment can mint per-run GitHub tokens and dispatch jobs securely. All of the following must be set; otherwise inline kinds still work and container kinds fail loudly instead of faking success:

VariablePurpose
GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEYMint short-lived per-run GitHub installation tokens.
PUBLIC_URLThe backend's externally reachable URL (runners call back to it).
AUTH_SESSION_SECRETSession secret (also required for real auth).
ENCRYPTION_KEYEncrypts the runner-pool credentials stored at rest (the shared master key above).

Multi-node coordination (Redis)

A Node.js deployment running more than one instance uses Redis to keep replicas in sync: live board events reach browsers on every node, and each node drops stale in-memory cache entries after a write on another node. A single instance and local mode need none of this; the Cloudflare Worker coordinates through its own globally-addressed primitives.

VariablePurpose
REDIS_URLEnables cross-node coordination (real-time event propagation and cache invalidation). Unset means single-node, in-process, no dependency. ioredis is an optional package: install it when you set this, or boot fails with a clear message.
REDIS_REALTIME_CHANNELOptional. The pub/sub channel carrying real-time events. Defaults to cat-factory:realtime.
REALTIME_NODE_IDOptional. A readable prefix for this node's id (used to ignore a node's own echoes). Defaults to a random id; a per-process suffix is always appended, so setting the same value on every replica is safe.

Redis is only ever a message bus for these signals, never a data tier: a node always repopulates its own in-memory state on a miss, and only keys travel on the wire, never values.

Private package registries

Agent containers resolve private npm dependencies from registries you connect per workspace in the UI (the Infrastructure window's Private package registries tab), not through environment variables. The feature is available whenever ENCRYPTION_KEY is set (it seals the tokens at rest); with no key the panel and its API return a "not configured" state.

Each entry names an ecosystem (npm today), a vendor, the npm scopes it covers, and a token:

VendorRegistry hostNotes
npm (npmjs.com)registry.npmjs.orgPrivate packages under your npm org.
GitHub Packagesnpm.pkg.github.comUses an explicit token you paste, not the GitHub App installation.

You pick the vendor rather than typing a URL: the host is fixed per vendor. Scopes are npm @org names the token can install. Tokens are write-only, shown afterward only as a …tail of the last four characters; to change one, remove the entry and re-add it. One entry per vendor per workspace. Before any agent runs, the harness renders these into a locked-down ~/.npmrc scoped to the two allowed hosts, read by npm, pnpm, and yarn v1. Tokens are registered for output redaction.

Capability credentials

A deployment's MCP tool servers and generative integrations declare the secrets they need by name. Fill those names in per workspace on the Infrastructure window's Capability credentials tab, which sits beside the package registries because what an agent's tools authenticate as belongs with where those agents run. The tab needs secrets.manage.

The panel is a checklist projected from the deployment's own registrations, never a blank key-value form. Each row names the key, who asks for it (a tool server or a generative integration), whether it is required, and when it was last set. Values are write-only: a stored value is replaced by typing a new one and is never read back.

An empty row means one of three things, kept apart because each needs a different reaction:

  • Nothing stored for this board, but the deployment also reads the key from its own environment, so the capability may still be working.
  • Nothing stored and no environment fallback, so the capability cannot authenticate.
  • The deployment supplied its own credential chain, so whether the key is answered elsewhere cannot be described here.

Keys that are stored but that nothing registered asks for are listed separately as Stored but not asked for, which is what a retired integration or a renamed variable leaves behind. They stay sealed until you remove them. If the declaration list itself cannot be read, the panel says so and withholds the orphan list rather than reporting every credential as orphaned.

A multi-tenant deployment can drop the environment fallback entirely and make the per-workspace store the only source, so one process serving many workspaces never answers every tenant with one variable.

Reserved variable names

A capability credential may not name a variable the platform itself reads. A definition names both the key it wants and the endpoint that key is sent to, so an unreserved name would let a registration inject the deployment's own secrets into a prompt-injectable agent process. The floor is enforced at declaration and again at dispatch, and it binds a deployment's own resolver too. For a stdio tool server whose client reads a documented variable inside a reserved prefix, declare the lookup key and the injected name separately.

Service configuration

VariablePurpose
NUXT_PUBLIC_API_BASEFrontend → backend URL. Build-time for the SPA.
CORS_ALLOWED_ORIGINSComma-separated browser origins allowed to call the API. A lone * reflects any origin.
ENVIRONMENTThe deployment stage. Recognized development values (development, dev, test, testing, local, e2e) relax CORS when CORS_ALLOWED_ORIGINS is unset; see below.
Workspace / account identity providersIdentity resolution settings.
Organization membership resolutionDetermines workspace access.

CORS fails safe in production. When CORS_ALLOWED_ORIGINS is unset, the API reflects any origin only when ENVIRONMENT is one of the recognized development values above. An unset, unknown, or production ENVIRONMENT with no CORS_ALLOWED_ORIGINS denies cross-origin browser calls rather than silently allowing them, so set CORS_ALLOWED_ORIGINS explicitly on any shared deployment.

Web search

Web search is opt-in and no-op until configured. It comes in two independent surfaces, and no provider key ever enters the per-run container: container agents reach search through a backend proxy.

VariablePurpose
WEB_SEARCH_BRAVE_API_KEYBrave Search key for the backend proxy (recommended).
WEB_SEARCH_SEARXNG_URL (+ WEB_SEARCH_SEARXNG_API_KEY)Reverse-proxy to a self-hosted SearXNG instead.
INLINE_WEB_SEARCH_ENABLEDEnables provider-hosted search for the inline architect/researcher agents.
INLINE_WEB_SEARCH_KINDS / INLINE_WEB_SEARCH_MAX_USESTune the allow-list and per-run cap.

Inline search only takes effect on providers with a hosted search tool (Anthropic / OpenAI).

Document & task sources

Document sources (Confluence, Notion, GitHub repo docs, the Figma and Zeplin design-context sources, and Linear Docs) and the Jira task source are always on: they ship enabled and each workspace connects its own site through the UI, with credentials stored encrypted under ENCRYPTION_KEY. There is no per-integration enable flag. The integrations fail loudly at boot if ENCRYPTION_KEY is missing rather than silently returning errors later.

Each workspace connects its own sites from the Integrations panel (sidebar → Integrations), which lists model providers alongside the document and task-tracker sources:

The Integrations panel listing OpenRouter, provider keys, and document sources such as Confluence, Notion, Figma, Zeplin, and Linear

VariablePurpose
DOCUMENT_SOURCESComma-separated allow-list of document sources to expose. Defaults to confluence,notion,github,figma,zeplin,linear (every known source). figma and zeplin are design-context sources, each connected per workspace with a personal access token.
DOCUMENT_PLANNERHow imported documents are turned into context: llm (default) or headings (deterministic split).

Task sources (Jira, GitHub Issues, and Linear) are configured per workspace. Each workspace turns its sources on or off in the UI (Workspace settings → Issue tracker); they work on every runtime, GitHub Issues rides the per-tenant GitHub App installation (or, in local mode, the PAT) with no env, and Linear connects per workspace via OAuth or a personal API key. See Connect Issue & Document Sources. The tech-debt recurring pipeline files its ticket through the workspace's chosen filing tracker.

Observability

Every model call is metered for the spend gauge and the in-app observability dashboard. Two optional settings tune what is recorded and where it is sent. Both are covered in depth in Observability.

VariablePurpose
LLM_RECORD_PROMPTSSet to false to drop prompt text from recorded metrics and to skip full agent-context capture (tokens, timing, finish reason, and counts are still kept). Defaults to recording prompts.
LANGFUSE_ENABLEDSet to true to stream every LLM call to Langfuse as a trace. Off by default.
LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEYLangfuse project keys (pk-lf-… / sk-lf-…). Both required when enabled.
LANGFUSE_BASE_URLLangfuse host. Optional; defaults to https://cloud.langfuse.com.

Langfuse honours LLM_RECORD_PROMPTS: with prompts off, the traces carry only numeric telemetry.

Telemetry store

Call metrics and full agent-context snapshots live in an isolated telemetry store, separate from the main application database. On Cloudflare it is a dedicated D1 database; on Node and local it is a telemetry schema inside the existing DATABASE_URL database. A retention cron prunes it on a schedule. See Observability → The telemetry store.

VariablePurpose
TELEMETRY_DBCloudflare only. D1 binding for the telemetry store. Required on the Worker: the build path and the retention cron fail fast if it is unbound. On Node and local the store is a telemetry schema in DATABASE_URL, with no separate binding.
PROVISIONING_DBCloudflare only. Optional D1 binding for the ephemeral-environment and container-provisioning event log. The log is off when unbound. On Node and local it is a provisioning schema in DATABASE_URL.
LLM_CALL_METRICS_RETENTION_DAYSHow long call metrics and agent-context snapshots are kept before the retention cron prunes them. Defaults to 14 days.
PROVISIONING_LOG_RETENTION_DAYSHow long provisioning events are kept. Defaults to 14 days.

The post-release-health gate and Agent-On-Call watch production through a pluggable observability provider (Datadog today) after a merge. They are opt-in and covered in Observability → Post-release health.

VariablePurpose
OBSERVABILITY_ENABLEDSet to true to enable the post-release-health gate and Agent-On-Call (also requires ENCRYPTION_KEY). Off by default; the gate is a pass-through when unset. The per-workspace provider site and keys are entered in the UI and sealed at rest.
PAGERDUTY_API_TOKEN + PAGERDUTY_FROM_EMAILOptional. Post the on-call investigation as an annotation onto an open PagerDuty incident.
INCIDENTIO_API_KEYOptional. The same enrichment for incident.io.

Notifications (Slack and webhooks)

Board notifications (merge reviews, pipeline completions, CI failures, requirement reviews) land in the in-app inbox by default. Slack and a per-workspace outbound webhook are optional extra transports. Both are opt-in and configured per workspace, and their secrets are encrypted under ENCRYPTION_KEY. Full setup is in Set Up Notifications.

VariablePurpose
SLACK_ENABLEDSet to true to make Slack available. Requires ENCRYPTION_KEY.
SLACK_CLIENT_ID / SLACK_CLIENT_SECRETOptional. Enable the OAuth "Add to Slack" flow; without them, operators paste a bot token by hand.
SLACK_REDIRECT_URLOptional OAuth callback, e.g. https://your-host/slack/oauth/callback.
NOTIFICATION_WEBHOOK_ALLOW_URL_HOSTSOptional. Comma-separated hostnames a webhook endpoint may use despite the private/internal-host block. Same matching rules as the environment and runner-pool allow-lists, and scoped independently of them.
NOTIFICATION_WEBHOOK_ALLOW_HTTP_URLSOptional. Set to true to also permit plain http webhook endpoints (a local-development affordance).

Email (invitations)

Email carries invitation links. There is no separate enable flag: email is available whenever an encryption key is set (the per-account API key is sealed with it). The provider, API key, and From address are then onboarded per account in the UI and stored sealed in the database (like the Slack bot token). Adapters exist for SendGrid and Resend. With no sender connected, invitations still work: the accept link is returned for manual sharing.

VariablePurpose
APP_BASE_URLThe SPA origin that invitation accept links point at. Falls back to AUTH_SUCCESS_REDIRECT_URL.
EMAIL_ENCRYPTION_KEYOptional. Seals the per-account email API key at rest; falls back to the shared ENCRYPTION_KEY.

Feature toggles

Optional integrations enabled by their own flag:

VariablePurpose
PROMPT_LIBRARY_ENABLEDThe prompt-fragment library is on by default (it needs no secrets and its tables ship in the base migrations). Set to false to turn it off. PROMPT_LIBRARY_SELECTOR=llm ranks fragments per run; anything else keeps the deterministic default.
CONSENSUS_ENABLEDSet to true to enable multi-model consensus on eligible steps. Off (unset) leaves the standard single-actor behaviour; the task-estimator step works either way.
OBSERVABILITY_ENABLEDSet to true for the post-release-health gate and Agent-On-Call (also requires ENCRYPTION_KEY).
SANDBOX_DBCloudflare only. Optional D1 binding that turns on the Compare Prompts and Models in the Sandbox for prompt and model testing. The Sandbox is off until it is bound. On Node and local it is a sandbox schema in DATABASE_URL.

Ephemeral environments have no enable flag. The module assembles wherever ENCRYPTION_KEY is set (it seals environment-provider credentials under that key), the same always-on-where-the-key-is-present model as the document and task sources. It stays inert until a workspace registers an infrastructure handler and a pipeline includes a deployer/tester step, so there is nothing to turn on at the deployment level.

Treat secrets as secrets

Provider keys, subscription tokens, the GitHub App private key, ENCRYPTION_KEY, the Langfuse secret key, HARNESS_SHARED_SECRET, and the webhook secret are all sensitive. On Cloudflare, set them as secret bindings (wrangler secret put …); on Node.js and local, keep them in your .env / secret manager and never commit them.


Next: scale execution with Run Jobs on Your Own Runners and Provision Ephemeral Environments, or check a value you set against Troubleshooting.

Edit this page on GitHub
Last Updated: 8/11/26, 9:33 AM
Prev
Set Up Your Deployment Repository