Environment variables
Subspace is configured almost entirely through environment variables read at boot. There is no separate config file: set a variable before starting the server (or export it in your process manager / systemd unit) and it takes effect on the next boot. This page is the exhaustive reference; see Self-hosting for a narrative setup walkthrough.
The baseline variables every install cares about: where data lives, what port and host to bind, and which deterministic fakes to use in dev/test.
| Variable | Default | Purpose |
|---|---|---|
SUBSPACE_HOME |
~/Subspace |
Root data directory: embedded Postgres (pg/, unix socket only, no TCP) and content-addressed blobs (blobs/). Migrations run transactionally on every boot. |
SUBSPACE_PORT |
4780 |
Public HTTP/WebSocket port. Under the cloud profile, platform $PORT is used when this is unset. |
SUBSPACE_HOST |
127.0.0.1 |
Public bind address. A wildcard bind is accepted only under SUBSPACE_PROFILE=cloud; local installs retain the loopback-safe default. |
SUBSPACE_PROFILE |
local | Set to cloud for public cells. One fail-closed switch disables server PTY/Jupyter/stdio MCP, os.exec, containers, local MLOps, and the OKF watcher. |
SUBSPACE_PUBLIC_ORIGIN |
unset | Canonical browser origin, for example https://cell.example.com. Required and HTTPS under the cloud profile; drives OAuth redirects, Secure cookies, CSRF and WebSocket Origin checks. |
SUBSPACE_TRUST_PROXY |
unset | Set to railway only behind Railway’s overwriting edge. Enables trusted X-Forwarded-Proto and X-Real-IP; forwarded headers are ignored otherwise. |
SUBSPACE_CLAIM_TOKEN |
unset | One-shot first-admin credential. Required on an unclaimed non-loopback bind unless an unused admin bootstrap invite is already seeded; only its SHA-256 digest is stored. |
SUBSPACE_INTERNAL_PORT |
4782 |
Loopback-only capability-broker/WDK executor listener. It never binds $PORT or SUBSPACE_HOST. |
SUBSPACE_SEED |
unset | Set to mock-world to seed the shared e2e/demo fixture (distinct inboxes, calendars, CRM rows) on first boot. |
PROVIDERS |
unset | Set to fake to swap every external seam (Jupyter kernel, search embedder, mail/calendar/IM providers, LLM calls, dev-login) for deterministic fakes. Used by tests and pnpm dev:sandbox. |
SUBSPACE_SHELL |
$SHELL |
Shell binary terminals spawn on the server host. |
SUBSPACE_AUTH |
unset | Set to off to disable identity entirely (legacy single-user behavior). The server refuses to boot with this on a non-loopback SUBSPACE_HOST. |
SUBSPACE_DEVICE_TOKEN |
unset | Bearer device token a non-browser client (desktop, mobile) stamps on every request; see Device pairing. |
SUBSPACE_URL |
http://127.0.0.1:4780 |
Base URL used by @subspace/sdk and subspace-sdk mcp. This configures the external client process, not the server bind address. |
SUBSPACE_TOKEN |
unset | Scoped SDK/device token used by @subspace/sdk and subspace-sdk mcp; sent as x-subspace-device-token. Claimed instances reject the MCP process at startup when this credential is absent or invalid. |
SUBSPACE_SDK_CONFIG |
~/.subspace/sdk.json |
Node/Python SDK profile file override. The file contains named {url, token} profiles shared by the clients, CLI, verifier, and MCP command. |
SUBSPACE_SDK_PROFILE |
file default | Named SDK profile to select after explicit arguments and SUBSPACE_URL / SUBSPACE_TOKEN. |
SUBSPACE_SESSION |
unset | Web session used only by subspace-sdk token create. Prefer this environment variable to --session so the session secret stays out of shell history. |
SUBSPACE_CORS_ORIGINS |
unset | Comma/space-separated exact http(s) origins allowed to call token-authenticated SDK tRPC/socket-ticket routes from browser JavaScript. No wildcard; off by default; does not enable credentialed cookies. |
SUBSPACE_AUTH_RATE_LIMIT |
30 per 5 minutes |
Per-client request ceiling for authentication, pairing, and socket-ticket endpoints. Railway identity uses trusted X-Real-IP. |
SUBSPACE_UPLOAD_MAX_BYTES |
536870912 |
Maximum streamed /files request size (512 MiB by default). Multipart buffering is refused. |
SUBSPACE_MODEL |
claude-sonnet-5 |
Model id used for agent runs, event-triage classification, mail-rule matching, and other LLM seams (Vercel AI SDK + Anthropic). |
ANTHROPIC_API_KEY |
unset | Seeds the llm.anthropic capability in the capability broker on first boot (insert-only; see Tools and approvals). |
OPENAI_API_KEY |
unset | Needed for real embeddings (text-embedding-3-small) when PROVIDERS=fake is not set; also seeds the llm.openai capability. |
Backup and restore
Section titled “Backup and restore”Set any WAL-G repository variable to turn on continuous archiving; the nightly backup job and monthly restore drill pick the rest up automatically. See Backup and restore for the full lifecycle.
| Variable | Default | Purpose |
|---|---|---|
WALG_S3_PREFIX, WALG_FILE_PREFIX, WALG_GS_PREFIX, WALG_AZ_PREFIX, WALG_SSH_PREFIX, WALE_*_PREFIX |
unset | Any one of these (standard WAL-G repository variables) turns on PostgreSQL archive_command (archive_timeout=60s) and enables the 02:10 nightly backup-push job, which retains 14 full backups. Hosted cells use a tenant-specific R2 WALG_S3_PREFIX; a composed recovery point is only minted when the blob replica is also complete. |
SUBSPACE_WALG_BIN |
bundled WAL-G 3.0.8 | Dev override for the WAL-G binary path. |
SUBSPACE_BLOB_REPLICA_URL |
unset | Enables the durable blob-replicator outbox consumer: referenced blobs upload to ab/cd/<sha256> at this URL; deletes become seven-day delayed tombstones. |
SUBSPACE_BLOB_REPLICA_TOKEN |
unset | Bearer auth for SUBSPACE_BLOB_REPLICA_URL. |
RESTIC_REPOSITORY |
unset | With this set, the same nightly job snapshots okf/ (the canonical markdown dump) via restic and retains 14 daily snapshots. |
SUBSPACE_RESTIC_BIN |
restic |
Dev override for the restic binary path. |
Hosted cells also configure KMS-backed tenant-key escrow. A non-fake
SUBSPACE_PROFILE=cloud boot refuses to proceed unless the complete set is present.
The AWS SDK uses its normal workload-identity credential chain; no variable contains the
plaintext DEK.
| Variable | Default | Purpose |
|---|---|---|
SUBSPACE_TENANT_ID |
unset | Stable control-plane tenant id. It is authenticated by the escrow service and bound into the AWS KMS encryption context. |
SUBSPACE_KMS_PROVIDER |
unset | Hosted DEK wrapper provider. The production implementation currently accepts aws. |
SUBSPACE_KMS_KEY_ID |
unset | Immutable AWS KMS symmetric key ARN used for Encrypt/Decrypt (aliases are rejected so repointing one cannot strand retained generations). Grant the cell role only those actions and require the tenant/purpose encryption context. |
SUBSPACE_KEY_ESCROW_URL |
unset | Control-plane base URL for wrapped-generation escrow and recovery. HTTPS is mandatory except on loopback. The control plane is separate fleet infrastructure, not the tenant-cell server. |
SUBSPACE_KEY_ESCROW_TOKEN |
unset | Per-cell bearer credential for the escrow API. It authenticates the cell but is never sent to KMS or written to escrow audit records/logs. |
Key-escrow control plane
Section titled “Key-escrow control plane”The production image also contains a separate fleet executable. Set
SUBSPACE_SERVICE=key-escrow-control-plane on a dedicated service to select it. It
connects to an external control-plane PostgreSQL database and never starts or opens a
tenant cell’s embedded database. Its migrations live in the control_plane schema.
| Variable | Default | Purpose |
|---|---|---|
SUBSPACE_SERVICE |
cell |
Set to key-escrow-control-plane to run the durable escrow API instead of a tenant cell. |
SUBSPACE_KEY_ESCROW_DATABASE_URL |
$DATABASE_URL |
External PostgreSQL connection URL for the fleet escrow database. Required under the control-plane service. |
SUBSPACE_KEY_ESCROW_DB_SSL |
require |
Database TLS mode: require, verify-full, or disable. Use disable only for a loopback test database. |
SUBSPACE_KEY_ESCROW_DB_POOL_SIZE |
10 |
PostgreSQL pool size, bounded to 2–100 connections. |
SUBSPACE_KEY_ESCROW_CELL_TOKENS |
[] |
Secret JSON array of {credentialId, tenantId, cellId, token} entries to upsert at boot. Each token is credentialId.<32+ character random secret>. Only its SHA-256 digest is stored; omitting an existing entry does not revoke it. |
SUBSPACE_KEY_ESCROW_HOST |
0.0.0.0 |
Control-plane bind address. |
PORT / SUBSPACE_KEY_ESCROW_PORT |
4783 |
Public control-plane port (PORT wins for Railway compatibility). |
SUBSPACE_KEY_ESCROW_SHUTDOWN_MS |
30000 |
Drain deadline before the process closes its database pool, bounded to 1–120 seconds. |
Provision SUBSPACE_KEY_ESCROW_CELL_TOKENS through the platform secret manager; do
not place it in a checked-in environment file. Rotation is an upsert with a new token.
To revoke a cell immediately, set disabled_at on its
control_plane.cell_credentials row (or delete the credential) before removing the
old platform secret. GET /livez is process liveness. GET /readyz verifies the
database and latest migration; it returns 503 as soon as SIGTERM drain begins, and
new escrow calls return 503 while in-flight requests finish.
GitOps
Section titled “GitOps”Configuring SUBSPACE_GITOPS_REPO turns on declarative config deployment
from a git repo: config pages and the plugin set sync from the repo on boot
and on a poll interval. See GitOps for the full model.
| Variable | Default | Purpose |
|---|---|---|
SUBSPACE_GITOPS_REPO |
unset | Clone URL or local path of the config repo. Setting this enables gitops sync. |
SUBSPACE_GITOPS_BRANCH |
main |
Branch to sync from. |
SUBSPACE_GITOPS_PATH |
repo root | Subdirectory within the repo holding pages/** and an optional plugins.json. |
SUBSPACE_GITOPS_POLL |
300 (seconds) |
Poll interval after the blocking boot sync; 0 means boot-only (no poll). |
SUBSPACE_GITOPS_MODE |
readonly |
Set to write-back so managed pages become editable again: every edit exports back to the repo as a commit. |
SUBSPACE_GITOPS_WRITEBACK_BRANCH |
the sync branch | In write-back mode, picks direct-to-branch commits (default) or a PR side branch. |
SUBSPACE_GITOPS_TOKEN |
unset | Seeds the gitops.git.token secret: private HTTPS remotes authenticate through GIT_ASKPASS, injected per git invocation. Absent = ambient auth (ssh-agent, credential helper). |
SUBSPACE_GITOPS_GIT_USERNAME |
x-access-token |
Overrides the basic-auth username paired with SUBSPACE_GITOPS_TOKEN. |
SUBSPACE_GITOPS_WEBHOOK_SECRET |
unset | Seeds the gitops.webhook.secret used to verify POST /gitops/webhook (GitHub/Gitea HMAC or GitLab token header). No secret configured means the webhook 403s for everyone. |
SUBSPACE_PLUGINS |
unset | Comma/space-separated npm plugin specs to reconcile at boot; a repo-pinned plugins.json (when gitops is configured) wins over this. |
Secrets and roles
Section titled “Secrets and roles”Subspace runs as a single all-in-one process by default. Opt-in posture
B splits it into a trusted web plane and a secret-scrubbed worker
plane over the same home, selected with a CLI flag rather than an env var:
subspace-server --role=web # tier 0: HTTP/WS/tRPC, Postgres, approvals, broker routessubspace-server --role=worker # tier 1: agent workflow world, scrubbed env, broker RPC onlysubspace-server --role=all # default: single process, both roles| Variable | Default | Purpose |
|---|---|---|
SUBSPACE_WORKER_URL |
:4781 |
Loopback control API the web role uses to start/resume workflows on the worker role. |
SUBSPACE_BROKER_URL |
:4782 |
Loopback capability-broker URL the worker uses. In split containers this is the internal listener, never the public port. |
Provider and platform credentials are never read ambiently by tool code;
they seed the encrypted ops.secrets table once at boot (insert-only, a
rotated row is never clobbered by a stale env var) and are acquired through
the capability broker from then on:
| Variable | Seeds capability |
|---|---|
SLACK_BOT_TOKEN |
slack.send |
SLACK_APP_TOKEN |
slack.socket |
TELEGRAM_API_ID, TELEGRAM_API_HASH, TELEGRAM_SESSION |
telegram |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
google.oauth (also Google login) |
DEEPGRAM_API_KEY |
deepgram.stt |
ANTHROPIC_API_KEY |
llm.anthropic |
OPENAI_API_KEY |
llm.openai |
WANDB_API_KEY |
wandb.api |
DAGSTER_API_TOKEN |
dagster.api |
DEVIN_API_KEY |
devin.api |
DROPBOX_ACCESS_TOKEN |
provider.dropbox |
MICROSOFT_GRAPH_ACCESS_TOKEN, MICROSOFT_GRAPH_DRIVE_ID |
provider.sharepoint |
SUBSPACE_GITOPS_TOKEN |
gitops.git.token |
SUBSPACE_GITOPS_WEBHOOK_SECRET |
gitops.webhook.secret |
Dev and test
Section titled “Dev and test”Variables that override binaries, timings, or debug behavior. Most are only relevant when developing Subspace itself or writing e2e fixtures.
| Variable | Default | Purpose |
|---|---|---|
SUBSPACE_CLAUDE_BIN |
claude |
Overrides the Claude Code CLI binary the coding-agent dispatch ladder (code.claude.run) spawns. |
SUBSPACE_BD_BIN |
bd |
Overrides the bd issue-tracker CLI binary code.task.create shells out to. |
SUBSPACE_OS_EXEC |
unset | Set to 1 to enable the os.exec agent tool (also implicitly on under PROVIDERS=fake). |
SUBSPACE_CONTAINERS |
unset | Set to 1 to enable the real docker containers.* agent tools. |
SUBSPACE_DOCKER_BIN |
docker |
Overrides the docker binary for containers.* (e2e points this at a fixture). |
SUBSPACE_SANDBOX |
unset | Set to 1 to register the lower-trust sandbox.exec/read/write agent tools. Containers have a named per-run volume, no host mounts, credentials, or network, and fixed resource limits. |
SUBSPACE_SANDBOX_IMAGE |
bash:5.2-alpine3.21 |
Overrides the minimal image used by sandbox.*. The value is passed as one validated Docker image reference, never through a shell. |
SUBSPACE_SANDBOX_DOCKER_BIN |
docker |
Overrides only the sandbox adapter’s Docker CLI binary. Supplying it also enables the adapter for deterministic tests; it never enables containers.*. |
EVALS_LIVE |
unset | Set to 1 to run the eval harness against the real model instead of recorded fixtures (SUBSPACE_MODEL). See Evals. |
SUBSPACE_GRADER_MODEL |
claude-haiku-4-5 |
Model used for the cheap post-run observation grader and the weekly pattern clusterer. |
RUN_INTROSPECTION |
off |
off | observe | full (gates post-run observation grading and pattern clustering). |
SUBSPACE_INDEX_DEBOUNCE_MS |
0 under PROVIDERS=fake, 1500 otherwise |
Debounce window for the search indexer; 0 means synchronous (embed on every commit). |
SUBSPACE_MCP_SERVERS |
unset | JSON array ([{name, command, args?}], stdio) of MCP servers to bind, for names not claimed by an mcp/ page. Page-defined servers take precedence per name. See MCP servers. |
SUBSPACE_DEVIN_SWEEP_MS |
120000 (2 min) |
Poll interval for the Devin session status sweep. |
SUBSPACE_MLOPS_SWEEP_MS |
20000 (20s) |
Poll interval for the mlops run-watcher sweep. |