How env validation works
The runtime composes one big Zod schema at boot: the Tier-0 base schema (always required) plus every loaded plugin’sconfigSchema. process.env is validated against the merged schema. Missing required vars fail boot with [boot-error] Plugin '<name>' env validation failed for '<field>'.
Disabling a plugin (via features or autoDetect) removes its env requirements automatically.
Tier-0 (core)
Always required by the runtime. Source:packages/oracle-runtime/src/config/base-env-schema.ts.
Runtime
| Variable | Type | Default | Notes |
|---|---|---|---|
NODE_ENV | 'development' | 'production' | 'test' | 'development' | |
PORT | number (coerced) | 3000 | Override at app.listen(port) if needed. |
ORACLE_NAME | string | — | Required. |
CORS_ORIGIN | string | '*' | Wildcard disables credentials. |
NETWORK | 'mainnet' | 'testnet' | 'devnet' | — | Required. |
Matrix
| Variable | Type | Default | Notes |
|---|---|---|---|
MATRIX_BASE_URL | string | — | Required. |
MATRIX_RECOVERY_PHRASE | string | — | Required. |
MATRIX_STORE_PATH | string | './matrix-storage' | Must persist across restarts. |
MATRIX_ORACLE_ADMIN_USER_ID | string | — | Required. |
MATRIX_ORACLE_ADMIN_PASSWORD | string | — | Required. |
MATRIX_ORACLE_ADMIN_ACCESS_TOKEN | string | — | Required. |
MATRIX_ACCOUNT_ROOM_ID | string | — | Required. |
MATRIX_VALUE_PIN | string | — | Required. |
Storage
| Variable | Type | Default | Notes |
|---|---|---|---|
SQLITE_DATABASE_PATH | string | — | Required. Must persist across restarts. |
Blocksync / chain
| Variable | Type | Default | Notes |
|---|---|---|---|
BLOCKSYNC_GRAPHQL_URL | string | — | Required. |
ORACLE_DID | string | — | Required. The oracle’s own DID (did:ixo:ixo1...) — the signer identity used by the UCAN service and the auth middleware. |
ORACLE_ENTITY_DID | string | — | Required. The oracle’s on-chain entity record DID. Distinct from ORACLE_DID — do not conflate them. |
SECP_MNEMONIC | string | — | Required. |
RPC_URL | string | — | Required. |
Auth / UCAN
Always present. Both arez.coerce.number() — set them as plain integer strings in .env.
| Variable | Type | Default | Notes |
|---|---|---|---|
UCAN_AUTH_MAX_TTL_SECONDS | number (coerced) | 900 | Max lifetime (seconds) the oracle accepts for a user auth invocation. Bounds the server-side replay window regardless of the TTL the client declares. Default 15 minutes. |
UCAN_REAUTH_PROMPT_THROTTLE_SECONDS | number (coerced) | 21600 | Throttle window (seconds) between “please re-authorize” prompts posted into a user’s Matrix room when their stored delegation is missing or expired. Default 6 hours. |
LLM
| Variable | Type | Default | Notes |
|---|---|---|---|
LLM_PROVIDER | 'openrouter' | 'nebius' | 'openrouter' | |
OPENAI_API_KEY | string | — | Optional. |
OPEN_ROUTER_API_KEY | string | — | Required if LLM_PROVIDER=openrouter. |
NEBIUS_API_KEY | string | — | Required if LLM_PROVIDER=nebius. |
validateLlmProviderKey): the API key for the selected LLM_PROVIDER must be present — OPEN_ROUTER_API_KEY when LLM_PROVIDER=openrouter (the default), NEBIUS_API_KEY when LLM_PROVIDER=nebius. A missing key fails boot with a named-field error (e.g. OPEN_ROUTER_API_KEY / NEBIUS_API_KEY) rather than a generic upstream 401 at request time. The per-role model ids are hardcoded per provider — there is no env var to swap the main model id (use the resolveModel hook for that).
Misc
| Variable | Type | Default | Notes |
|---|---|---|---|
ORACLE_SECRETS | string | '' | Comma-separated KEY=value pairs surfaced as x-os-* headers to capabilities. |
LIVE_AGENT_AUTH_API_KEY | string | '' | Optional. |
LangSmith tracing (optional)
| Variable | Type | Default | Notes |
|---|---|---|---|
LANGSMITH_TRACING | string | — | Set to 'true' to enable. |
LANGSMITH_API_KEY | string | — | |
LANGSMITH_PROJECT | string | — | |
LANGSMITH_ENDPOINT | string | — | Optional override of the LangSmith API URL. |
qiforge-cli env output. LangChain auto-wires when these are present in process.env; the runtime never reads them directly.
Per-plugin
Only required when the named plugin is loaded.| Plugin | Variable | Required | Notes |
|---|---|---|---|
memory | MEMORY_MCP_URL | Yes | Must be a valid HTTP(S) URL. |
memory | MEMORY_ENGINE_URL | Yes | Must be a valid HTTP(S) URL. |
firecrawl | FIRECRAWL_MCP_URL | Yes | Must be a valid HTTP(S) URL. |
domain-indexer | DOMAIN_INDEXER_URL | No | URL override; otherwise resolved from NETWORK. |
composio | COMPOSIO_API_KEY | Yes (when loaded) | |
composio | COMPOSIO_BASE_URL | No | Defaults to https://composio.ixo.earth. |
sandbox | SANDBOX_MCP_URL | Yes | Must be a valid URL. |
skills | SKILLS_CAPSULES_BASE_URL | No | URL. Defaults to https://capsules.skills.ixo.earth. |
slack | SLACK_BOT_OAUTH_TOKEN | Yes | Triggers autoDetect. |
slack | SLACK_APP_TOKEN | No | |
slack | SLACK_USE_SOCKET_MODE | No | Defaults to 'true'. |
slack | SLACK_MAX_RECONNECT_ATTEMPTS | No | Coerced to number; default 10. |
slack | SLACK_RECONNECT_DELAY_MS | No | Coerced to number; default 1000. |
credits | SUBSCRIPTION_URL | No | URL. |
credits | SUBSCRIPTION_ORACLE_MCP_URL | No | URL. |
credits | DISABLE_CREDITS | No | Enum — exactly 'true' or 'false' (any other value fails env validation). 'true' disables the credit-enforcement middleware; the plugin’s autoDetect also excludes the whole plugin when DISABLE_CREDITS=true. |
tasks | REDIS_URL | Yes (when loaded) | Triggers autoDetect. |
tasks | TASKS_MAX_PER_USER | No | Coerced positive int; default 50. Max scheduled tasks per user. |
tasks | TASKS_RUN_LOCK_TTL_SEC | No | Coerced positive int; default 600. Per-run lock TTL (seconds). |
tasks | TASKS_MIN_CRON_INTERVAL_SEC | No | Coerced positive int; default 300. Minimum allowed cron interval (seconds). |
matrix-group-chats | CHANNEL_MEMORY_SYNC_INTERVAL_MS | No | Coerced int, min 1000; default 60000. Debounce window between a write and the Matrix snapshot upload. |
matrix-group-chats | GROUP_CHAT_ACTIVE_THREAD_TTL_MS | No | Coerced int, min 60000; default 1800000. How long a thread stays “active with the bot” after a reply. |
matrix-group-chats | GROUP_CHAT_REQUIRE_POWER_LEVEL | No | Coerced int, min 0; default 0. Extra minimum power level the bot must have before posting (0 = use the room default). |
matrix-group-chats | GROUP_CHAT_ROOM_INFO_TTL_MS | No | Coerced int, min 60000; default 1800000. How long roomInfo (membership, DM flag) stays cached. |
Variables read but not owned
Some plugins read variables that live in another schema:composioreads the coreNETWORKand forwards it asx-ixo-network.skillsreadsNETWORKand forwards asX-IXO-Network.sandboxreadsORACLE_SECRETS(core) andSKILLS_CAPSULES_BASE_URL(owned byskills) and forwards them as headers.
optional()); a missing value just skips the matching header instead of failing the plugin build.
Generating an .env template
qiforge-cli env generates a .env template for your installed plugin set. Until that lands, write the file by hand using this reference.
Related references
- Plugin catalog — env vars per plugin in catalog form.
- Plugin config and env guide — declaring your own plugin’s vars.