Skip to content

Environment Variables

All environment variables used by Crewlet and its integrations.

Two kinds of variable appear below. A few names are read directly by the engine or CLI (marked as such). Everything else is a ${VAR} reference convention: any string value in the YAML config can reference any environment variable (see Usage in YAML), and the names listed here are simply the conventions the bundled examples/nimbus.company.yaml uses — rename them freely as long as the config references match.


VariableDescriptionWhere to get it
CREWLET_DATABASE_DSNPostgreSQL DSN the example Tier A configs reference (providers.database.dsn)Your database (postgresql://crewlet:crewlet@localhost:5432/crewlet for the bundled compose)
CREWLET_PULSAR_URLPulsar broker URL the example Tier A configs reference (providers.queue.url)pulsar://localhost:6650 for the bundled compose
CREWLET_API_TOKEN_FOUNDERBearer token for the founder API identity (api.auth.tokens)Generate one: openssl rand -hex 32
LLM_API_KEYAPI key for your LLM provider (providers.llm.default.api_keys)Your LLM provider dashboard
LLM_MODELModel id served by your OpenAI-compatible endpoint (providers.llm.default.model in the example)Your LLM provider docs
LLM_BASE_URLYour OpenAI-compatible endpoint’s /v1/ base URL (providers.llm.default.base_url in the example)Your LLM provider docs
OPENAI_API_KEYRead directly as a fallback by the openai / openai-compatible LLM providers (when api_keys is empty) and the OpenAI embeddings provider (when api_key is unset)OpenAI dashboard
ANTHROPIC_API_KEYRead directly as a fallback by the anthropic LLM provider when api_keys is emptyAnthropic Console
CREWLET_TOOL_SKILLS_SPACERead directly by the engine and crewlet confluence import: the Confluence space key the Tool Skills sync watches. Default TS. Set to empty string to disable the sync entirely.
CREWLET_TOOL_SKILLS_PROJECTRead directly by the engine, the knowledge searcher’s result exclusion, and crewlet plane import: the Plane project identifier the Tool Skills sync watches when Plane is the knowledge backend — the CREWLET_TOOL_SKILLS_SPACE analog. Default TS. Set to empty string to disable the sync (and the search exclusion).

VariableDescriptionWhere to get it
SLACK_BOT_TOKEN_<ROLE>Bot User OAuth Token (xoxb-...)Written by crewlet slack provision, or Slack app > OAuth & Permissions
SLACK_SIGNING_SECRET_<ROLE>Signing SecretWritten by crewlet slack provision, or Slack app > Basic Information
SLACK_CONFIG_TOKENApp configuration token (xoxe.xoxp-..., 12 h lifetime) authenticating the App Manifest APIs used by crewlet slack provisionapi.slack.com/apps > Your App Configuration Tokens (once); rotated near expiry + persisted to the env file automatically
SLACK_CONFIG_REFRESH_TOKENRefresh token (xoxe-1-...) minting the next config-token pair via tooling.tokens.rotateIssued alongside SLACK_CONFIG_TOKEN; rotated automatically
SLACK_CONFIG_TOKEN_EXPIRES_ATUnix timestamp of the access token’s expiry — lets re-runs skip rotation while the token is freshWritten by crewlet slack provision; never set by hand

Replace <ROLE> with the role name in uppercase (e.g., SLACK_BOT_TOKEN_ENGINEER). The per-role names are conventions — any ${VAR} name referenced from role.integrations.slack works, and crewlet slack provision writes whatever names the YAML uses.


VariableDescriptionWhere to get it
JIRA_URLYour Jira instance URLe.g., https://company.atlassian.net
JIRA_API_TOKENJira API token (admin/service account)Atlassian account > API tokens
JIRA_EMAILAdmin email for Cloud Basic AuthYour Atlassian account email
JIRA_WEBHOOK_SECRETSecret for HMAC verificationSet when creating the Jira webhook
VariableDescription
<ROLE>_JIRA_TOKENPer-agent Jira API token (e.g., CTO_JIRA_TOKEN)

VariableDescriptionWhere to get it
CONFLUENCE_URLYour Confluence instance URL (integrations.confluence.url)e.g., https://company.atlassian.net/wiki
CONFLUENCE_API_TOKENAdmin/service API token (integrations.confluence.token)Atlassian account > API tokens
CONFLUENCE_EMAILAdmin email for Cloud Basic Auth (integrations.confluence.email)Your Atlassian account email
CONFLUENCE_WEBHOOK_SECRETHMAC secret for Data Center webhooks (integrations.confluence.webhook_secret)Set when creating the webhook

Per-agent Confluence credentials go through role.mcp_env on the atlassian MCP server (CONFLUENCE_USERNAME / CONFLUENCE_API_TOKEN), like Jira.


VariableDescriptionWhere to get it
TAVILY_API_KEYKey for the shared Tavily web-search MCP server the example org declareshttps://tavily.com

Conventions used by the Plane integration and its provisioning/bootstrap tooling. Apart from PLANE_PROVISION_TOKEN (read directly by the CLI), these are ${VAR} references in the company YAML — crewlet plane provision mints the token values into .env.plane for you.

VariableDescriptionWhere to get it
PLANE_URLPlane instance base URL — the one reference the example config resolves for integrations.plane.url and skill_variables.plane_base_urlWritten to .env.plane by scripts/plane-dev-bootstrap.sh locally; your Plane deployment’s URL otherwise
PLANE_PROVISION_TOKENRead directly by crewlet plane provision as the operator credential fallback (a workspace-admin personal API token; --provision-token overrides)Plane profile > API tokens (workspace-admin account)
PLANE_ENGINE_TOKENThe crewlet-engine read account’s API token (integrations.plane.token)Minted by crewlet plane provision
PLANE_WEBHOOK_SECRETWorkspace webhook secret (integrations.plane.webhook_secret) — generated by Plane at hook creationCaptured by crewlet plane provision --webhook-url …
PLANE_TOKEN_<SEAT>Per-agent service-account API token (each role’s mcp_env.plane.PLANE_API_KEY, e.g. PLANE_TOKEN_CEO)Minted by crewlet plane provision

Conventions used by the GitLab integration. Apart from the operator credentials (read directly by the CLI), these are ${VAR} references in the company YAML — crewlet gitlab provision mints the PAT values into .env.gitlab.

VariableDescriptionWhere to get it
GITLAB_PROVISION_TOKENRead directly by crewlet gitlab provision as the operator credential fallback (group Owner / admin PAT with api scope; --provision-token overrides)GitLab > Access tokens
GITLAB_ADMIN_TOKENSecond fallback for the same operator credentialGitLab > Access tokens
GITLAB_ENGINE_TOKENEngine read token (integrations.gitlab.token)GitLab service account, or minted by provisioning
GITLAB_SIGNING_SECRETWebhook signing token (integrations.gitlab.signing_secret, GitLab 19.1+ Standard-Webhooks scheme)Set when registering the webhook
GITLAB_TOKEN_<SEAT>Per-agent service-account PAT (each role’s mcp_env.gitlab.GITLAB_TOKEN, also referenced from role.sandbox.env, e.g. GITLAB_TOKEN_SWE)Minted by crewlet gitlab provision

VariableDescription
GMAIL_APP_PASSWORDGmail app password (if using Gmail)

There is no dedicated database environment variable: the PostgreSQL DSN lives in the Tier A bootstrap YAML (providers.database.dsn), which — like every YAML string — may reference an environment variable of your choosing:

providers:
database:
dsn: "${CREWLET_DATABASE_DSN}" # the conventional name the examples use — or inline: "postgresql://crewlet:crewlet@localhost:5432/crewlet"

The bundled docker-compose.yml uses the timescale/timescaledb:latest-pg18 image, which ships with the TimescaleDB and pgvector extensions preloaded. The event store (crewlet_events hypertable) lives in the same database as the rest of Crewlet’s state — there is no separate observability DB to configure.


Only needed when the broker runs with token authentication (see Deployment § Authentication). Both are ${VAR} conventions — the first in the Tier A YAML, the second in the compose .env.

VariableDescriptionWhere to get it
CREWLET_PULSAR_TOKENThis engine’s broker token (providers.queue.auth_token)bin/pulsar tokens create --subject <engine-role>
PULSAR_ADMIN_TOKENOperator/superuser token used by the compose broker config, its healthcheck, and pulsar-adminbin/pulsar tokens create --subject admin

VariableDescriptionWhere to get it
CREWLET_SECRET_KEY_<ID>Base64-encoded 32-byte key referenced by a Tier A secrets.keys[].material. When a keyring is configured, the entire Tier B config is stored encrypted at rest in the DB as one opaque blob instead of as verbatim ${VAR} references.crewlet secrets keygen

The keyring lives in Tier A (config.yaml) and is the sole root of trust — the DB holds only the encrypted document, never the key, and the key is required for every config read. Without a keyring, Crewlet keeps the default ${VAR}-reference behaviour and every env var on this page is resolved from the environment at construction time. See Configuration § Secrets.

A keyring lets you retire the per-secret env vars on this page (LLM_API_KEY, <ROLE>_JIRA_TOKEN, SLACK_BOT_TOKEN_<ROLE>, *_WEBHOOK_SECRET, …) two different ways:

  • Secret store (recommended) — keep the ${VAR} references in the config and store the values in the encrypted secret_values table (crewlet secrets set, or --secret-store on a provisioning CLI). The engine consults that table ahead of os.environ, so a name it answers no longer needs to be exported at all. Rotation is an update of one row.
  • Literal values in the encrypted config — set them via PUT /config or import a company.yaml with literals. Simpler, but every rotation writes a new immutable revision that archives the superseded secret, and one credential referenced from two places (a Slack bot token is both role.integrations.slack.bot_token and role.mcp_env.slack.SLACK_MCP_XOXB_TOKEN) becomes two literals that must change together.

Either way, ${VAR} references that remain unanswered by the store still resolve from the environment.

Two variables can never move into the store, no matter how it is configured: the database DSN and CREWLET_SECRET_KEY_<ID> itself. Tier A is what opens and decrypts the store, so it is always env/file-sourced.


All read directly by the engine.

VariableDescriptionExample
OTEL_EXPORTER_OTLP_ENDPOINTOTLP HTTP endpoint for trace exporthttp://localhost:4318/v1/traces
OTEL_EXPORTER_OTLP_HEADERSk=v,k2=v2 headers for the OTLP backend (e.g. auth). Also used engine-side as the upstream auth for forwarded sandbox telemetry — never handed to the sandbox itself.authorization=Bearer%20...
OTEL_EXPORTER_OTLP_PROTOCOLOTLP protocol selector, propagated into sandbox runs so the coding agent exports the same wayhttp/protobuf

When OTEL_EXPORTER_OTLP_ENDPOINT is set, the engine exports OTel spans to the specified endpoint (Jaeger, Grafana Tempo, etc.). Without it, spans are still created for internal trace context propagation but not exported.


Used only when providers.sandbox is configured so sandbox-enabled roles can author code in an isolated E2B sandbox — see Code Sandbox. Requires the sandbox extra — pip install 'crewlet[sandbox]' (or uv sync --extra sandbox from a checkout) — which pulls in the e2b SDK. The variable names below are the conventions the Nimbus example references; any ${ENV} name works.

VariableDescriptionWhere to get it
E2B_API_KEYE2B API key (providers.sandbox.api_key). Required even for self-hosted/local E2B — the SDK always authenticates (sends it as an X-API-KEY header); E2B_DOMAIN only changes which API it talks to.e2b.dev dashboard (cloud) or your self-hosted E2B’s key management
E2B_DOMAINSelf-hosted / local E2B cluster domain (providers.sandbox.domain). Omit for E2B cloud.Your self-hosted E2B deployment
E2B_VALIDATE_API_KEYSet to false to skip the SDK’s e2b_<hex> key-format check — needed if your self-hosted cluster issues keys in a different format. Default true. Read directly by the e2b SDK from the env.
CREWLET_SANDBOX_OTEL_RECEIVER_URLRead directly by the engine: the externally-reachable base URL of the engine’s own API (e.g. http://host.docker.internal:80). When set, the engine wires its /otlp/{token}/v1/{signal} receiver route and sandbox runs export telemetry through it (forwarded to OTEL_EXPORTER_OTLP_* when configured). Unset = no sandbox telemetry.Your engine’s public address

Inside each sandbox run the engine injects CREWLET_AGENT_HANDLE and CREWLET_AGENT_EMAIL — the running agent’s identity facts, readable by role.sandbox.setup recipes (e.g. to configure git config user.name/user.email). They are outputs of the engine, not inputs you set.

The coding agent’s LLM credential derives from the role’s resolved providers.llm entry — no sandbox-specific LLM secret is needed. External-service tokens are explicit config: each seat declares them in role.sandbox.env (e.g. GITLAB_TOKEN: "${GITLAB_TOKEN_SWE}" — by convention the same PAT the seat already uses for its mcp_env.gitlab server, so merge requests land under the agent’s identity). The engine itself injects only the generic facts above — it never names a tool-specific variable; a declared ${ENV} reference that resolves to empty logs sandbox_env_unresolved at launch. With an OpenAI-compatible LLM provider, keep default_coding_agent: opencode (provider-agnostic); claude-code additionally requires an Anthropic-compatible credential.


All string values in YAML support ${ENV_VAR} references:

providers:
llm:
default:
api_keys: # LLM providers take a list (one or many)
- "${LLM_API_KEY}"
embeddings:
api_key: "${OPENAI_API_KEY}" # embeddings still take a single scalar

Variables are resolved at startup from the secret store first (when one is configured and holds the name), then os.environ. An unanswered reference resolves to the empty string.

Only the braced identifier form is substituted — ${NAME} where NAME matches [A-Za-z_][A-Za-z0-9_]*. Bare $NAME and shell parameter expansions (${1:-x}, ${line#host=}) are left untouched, so config-authored script content — a sandbox setup step’s helper script, say — survives intact.