Environment variables

Tier-0 (core) vars the runtime always requires, plus per-plugin vars contributed by each bundled plugin's configSchema.

How env validation works

The runtime composes one big Zod schema at boot: the Tier-0 base schema (always required) plus every loaded plugin's configSchema. 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

VariableTypeDefaultNotes
NODE_ENV'development' | 'production' | 'test''development'
PORTnumber (coerced)3000Override at app.listen(port) if needed.
ORACLE_NAMEstring—Required.
CORS_ORIGINstring'*'Wildcard disables credentials.
NETWORK'mainnet' | 'testnet' | 'devnet'—Required.

Matrix

VariableTypeDefaultNotes
MATRIX_BASE_URLstring—Required.
MATRIX_RECOVERY_PHRASEstring—Required.
MATRIX_STORE_PATHstring'./matrix-storage'Must persist across restarts.
MATRIX_ORACLE_ADMIN_USER_IDstring—Required.
MATRIX_ORACLE_ADMIN_PASSWORDstring—Required.
MATRIX_ORACLE_ADMIN_ACCESS_TOKENstring—Required.
MATRIX_ACCOUNT_ROOM_IDstring—Required.
MATRIX_VALUE_PINstring—Required.

Storage

VariableTypeDefaultNotes
SQLITE_DATABASE_PATHstring—Required. Must persist across restarts.

Blocksync / chain

VariableTypeDefaultNotes
BLOCKSYNC_GRAPHQL_URLstring—Required.
ORACLE_DIDstring—Required. The oracle's own DID (did:ixo:ixo1...) — the signer identity used by the UCAN service and the auth middleware.
ORACLE_ENTITY_DIDstring—Required. The oracle's on-chain entity record DID. Distinct from ORACLE_DID — do not conflate them.
SECP_MNEMONICstring—Required.
RPC_URLstring—Required.

Auth / UCAN

Always present. Both are z.coerce.number() — set them as plain integer strings in .env.

VariableTypeDefaultNotes
UCAN_AUTH_MAX_TTL_SECONDSnumber (coerced)900Max 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_SECONDSnumber (coerced)21600Throttle 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

VariableTypeDefaultNotes
LLM_PROVIDER'openrouter' | 'nebius''openrouter'
OPENAI_API_KEYstring—Optional.
OPEN_ROUTER_API_KEYstring—Required if LLM_PROVIDER=openrouter.
NEBIUS_API_KEYstring—Required if LLM_PROVIDER=nebius.

Cross-field check (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).

Model-call liveness (Workers runtime)

@ixo/oracle-runtime-workers bounds every model call — main agent, sub-agents, helper models, platform and BYO alike — by watching the raw response bytes, so a stream that goes quiet fails within these budgets instead of running to the turn deadline (TURN_TIMEOUT_MS, 600 s). All four are optional.

VariableTypeDefaultNotes
LLM_HEADERS_TIMEOUT_MSnumber (coerced)120000Time allowed for the response headers.
LLM_STREAM_IDLE_TIMEOUT_MSnumber (coerced)90000Longest silence between body bytes, and before the first one, on the platform lane (OpenRouter, Nebius). OpenRouter sends keep-alive comments while the model works, so a quiet stream really is stuck.
LLM_BYO_STREAM_IDLE_TIMEOUT_MSnumber (coerced)300000The same on BYO lanes (a user's own OpenAI, Anthropic, Gemini, DeepSeek or ChatGPT account), which may send nothing while a model reasons.
LLM_STREAM_RETRIESnumber (coerced)1Identical re-sends of a call that stalled before its first body byte, 0–5, clamped per lane so every attempt fits TURN_TIMEOUT_MS. A stall after bytes were received fails the call at once (kind: timeout, retryable).

Any byte resets the idle clock and there is no cap on a call's total duration. The worst cases per lane, the retry rules and what the logs show are in the runtime's own docs: packages/oracle-runtime-workers/docs/configuration.md (Model-call liveness) and operations.md in the qiforge repository.

Run recovery (Workers runtime)

@ixo/oracle-runtime-workers 0.37.0 and later. A run whose isolate is reset mid-turn is recovered from its last checkpoint. Optional.

VariableTypeDefaultNotes
RUN_RECOVERY_TOTAL_ATTEMPTSinteger string, min 08How many times in all a run that was reset mid-turn is recovered, progress or not. After that the run ends interrupted and every surface (channel, Matrix room, Portal) gets a failure notice. This ends a turn that resets the isolate on every step. RUN_RECOVERY_ATTEMPTS (default 4) still bounds consecutive attempts without progress.

The full recovery rules are in packages/oracle-runtime-workers/docs/operations.md in the qiforge repository. For the 128 MB isolate limit itself, see deploy → Cloudflare Workers runtime.

Output cap (Workers runtime)

@ixo/oracle-runtime-workers 0.39.0 and later. Optional.

VariableTypeDefaultNotes
LLM_MAX_OUTPUT_TOKENSinteger, min 016384Most tokens one platform model call (OpenRouter, Nebius) may generate, reasoning included. 0 leaves it to the model's own limit. Without a cap, a call that loops runs to that limit: 65,536 tokens on openai/gpt-5.6-luna. A reply cut off by the cap is kept as it stands. BYO lanes are not capped: the user's own key pays for them.

Context and prompt cache (Workers runtime)

@ixo/oracle-runtime-workers 0.39.0 and later. All optional. Every model call re-sends the system prompt, the tool list and the thread, and the provider's prompt cache only helps while that prefix stays the same. So the runtime saves changes to the prefix for a cold turn: the first turn of a thread, or a turn that opens CONTEXT_CACHE_COLD_MS or more after the thread's last activity, when the cache has expired anyway. Warm turns never unload a capability or trim old results. See Loading and unloading.

VariableTypeDefaultNotes
CONTEXT_CACHE_COLD_MSinteger string, min 02100000 (35 min)Quiet time after which the next turn is cold. 0 makes every turn cold.
CONTEXT_STALE_RESULT_TURNSinteger string, min 02On a cold turn, tool results older than this many finished turns go to the model as one-line stubs; read_result reads one back whole. Only the request changes: the checkpoint and the transcript keep every result. 0 keeps them all.
CONTEXT_PLUGIN_IDLE_TURNSinteger string, min 03On a cold turn, loaded capabilities that none of this many latest turns used are unloaded. The model can load one again with load_capability. 0 keeps them all.
CONTEXT_SUMMARIZE_MAX_TOKENSinteger string, min 1000200000Ceiling on when a long history is summarized. Summarizing still starts at 60% of the model's window (CONTEXT_SUMMARIZE_FRACTION) when that is lower.
CONTEXT_PRUNE_MAX_TOKENSinteger string, min 1000100000Ceiling on when old tool results are replaced by placeholders under pressure. Pruning still starts at 35% of the window (CONTEXT_PRUNE_FRACTION) when that is lower.
CONTEXT_RESULT_CAP_MAX_CHARSinteger string, min 400064000 (200000 before 0.39.0)Largest single tool result the model gets, in characters. The full result is stored and reachable through read_result.

The two token ceilings are about cost, not fit: on a large window they stop the runtime from re-sending a very long thread on every call. The other CONTEXT_* variables and the reasons behind each default are in packages/oracle-runtime-workers/docs/configuration.md in the qiforge repository.

Capability router (Workers runtime)

The capability router predicts which on-demand capabilities a message needs and loads them before the first model call, which saves the model a load_capability round trip. It needs a DECISION_PROVIDER. The thresholds are @ixo/oracle-runtime-workers 0.39.0 and later.

VariableTypeDefaultNotes
CAPABILITY_ROUTER'off' | 'shadow' | 'on''off'on waits up to one second for the verdict and then loads every predicted capability on the thread. shadow evaluates without loading anything and logs what it would have loaded ([capability-router-shadow]), to measure accuracy. Any failure loads nothing.
CAPABILITY_ROUTER_WARM_THRESHOLDnumber, min 00.8Score a capability needs to be loaded on a warm turn, where a wrong load costs a full prompt-cache miss. Above 1, the router does not run on warm turns. Provisional.
CAPABILITY_ROUTER_COLD_THRESHOLDnumber, min 00.7Score a capability needs on a cold turn. Provisional.

The router sends the user's message and the thread's last four visible turns, shortened, to the Decision provider. How it picks candidates is in Meta-tools and loading.

Misc

VariableTypeDefaultNotes
ORACLE_SECRETSstring''Comma-separated KEY=value pairs surfaced as x-os-* headers to capabilities.
LIVE_AGENT_AUTH_API_KEYstring''Optional.

LangSmith tracing (optional)

VariableTypeDefaultNotes
LANGSMITH_TRACINGstring—Set to 'true' to enable.
LANGSMITH_API_KEYstring—
LANGSMITH_PROJECTstring—
LANGSMITH_ENDPOINTstring—Optional override of the LangSmith API URL.

Declared in the base schema so they show up in 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.

PluginVariableRequiredNotes
memoryMEMORY_MCP_URLYesMust be a valid HTTP(S) URL.
memoryMEMORY_ENGINE_URLYesMust be a valid HTTP(S) URL.
memoryORACLE_ENTITY_DIDYes (@ixo/oracle-runtime-workers 0.24.0 and later)did:ixo:entity: followed by 32 lower-case hex characters. Sent to the Memory Engine as x-oracle-entity-did; the engine (2.0.0 and later) refuses requests without it. See memory.
firecrawlFIRECRAWL_MCP_URLYesMust be a valid HTTP(S) URL.
domain-indexerDOMAIN_INDEXER_URLNoURL override; otherwise resolved from NETWORK.
composioCOMPOSIO_API_KEYYes (when loaded)
composioCOMPOSIO_BASE_URLNoDefaults to https://composio.ixo.earth.
sandboxSANDBOX_MCP_URLYesMust be a valid URL.
skillsSKILLS_CAPSULES_BASE_URLNoURL. Defaults to https://capsules.skills.ixo.earth.
slackSLACK_BOT_OAUTH_TOKENYesTriggers autoDetect.
slackSLACK_APP_TOKENNo
slackSLACK_USE_SOCKET_MODENoDefaults to 'true'.
slackSLACK_MAX_RECONNECT_ATTEMPTSNoCoerced to number; default 10.
slackSLACK_RECONNECT_DELAY_MSNoCoerced to number; default 1000.
creditsSUBSCRIPTION_URLNoURL.
creditsSUBSCRIPTION_ORACLE_MCP_URLNoURL.
creditsDISABLE_CREDITSNoEnum — 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.
tasksREDIS_URLYes (when loaded)Triggers autoDetect.
tasksTASKS_MAX_PER_USERNoCoerced positive int; default 50. Max scheduled tasks per user.
tasksTASKS_RUN_LOCK_TTL_SECNoCoerced positive int; default 600. Per-run lock TTL (seconds).
tasksTASKS_MIN_CRON_INTERVAL_SECNoCoerced positive int; default 300. Minimum allowed cron interval (seconds).
matrix-group-chatsCHANNEL_MEMORY_SYNC_INTERVAL_MSNoCoerced int, min 1000; default 60000. Debounce window between a write and the Matrix snapshot upload.
matrix-group-chatsGROUP_CHAT_ACTIVE_THREAD_TTL_MSNoCoerced int, min 60000; default 1800000. How long a thread stays "active with the bot" after a reply.
matrix-group-chatsGROUP_CHAT_REQUIRE_POWER_LEVELNoCoerced int, min 0; default 0. Extra minimum power level the bot must have before posting (0 = use the room default).
matrix-group-chatsGROUP_CHAT_ROOM_INFO_TTL_MSNoCoerced int, min 60000; default 1800000. How long roomInfo (membership, DM flag) stays cached.
vfsVFS_MAX_READ_LINESNoCoerced positive int; default 2000. Max lines a single vfs_read window returns.
vfsVFS_REQUEST_TIMEOUT_MSNoCoerced positive int; default 20000. Per-request timeout to the VFS worker.
ixo-chainIXO_CHAIN_TOOLSNoWorkers runtime (@ixo/oracle-runtime-workers 0.28.0 and later). on (default) or off. off leaves the plugin out of the oracle.
ixo-chainCHAIN_REST_URLNoWorkers runtime (0.28.0 and later). Chain REST (LCD) base URL for balances, denom metadata, the overview's chain sections and the chain kinds of chain_lookup: https, no credentials, query or fragment (a /rest path is fine). Defaults per NETWORK: mainnet https://impacthub.ixo.world/rest, testnet https://testnet.ixo.earth/rest, devnet https://devnet.ixo.earth/rest. See ixo-chain.
ixo-chainIXO_CHAIN_IBC_DENOMSNoWorkers runtime (0.28.2 and later). More verified IBC tokens, merged over the built-in ones of the oracle's NETWORK. Invalid JSON or an invalid entry fails boot validation. See ixo-chain.
ixo-chainIXO_NAMES_URLNoWorkers runtime (0.34.0 and later). The IXO Names worker's origin: https, no credentials, query or fragment. Defaults per NETWORK: mainnet https://names.ixo.earth, testnet https://testnet.names.ixo.earth, devnet https://devnet.names.ixo.earth. See Names.
ixo-chainIXO_NAMESNoWorkers runtime (0.34.0 and later). on (default) or off. off leaves names out of every answer and asks the worker nothing.

Variables read but not owned

Some plugins read variables that live in another schema:

  • composio reads the core NETWORK and forwards it as x-ixo-network.
  • skills reads NETWORK and forwards as X-IXO-Network.
  • sandbox reads ORACLE_SECRETS (core) and SKILLS_CAPSULES_BASE_URL (owned by skills) and forwards them as headers.
  • vfs reads the core NETWORK (selects the bundled VFS + UCAN Store worker URLs — nothing to configure) and SANDBOX_MCP_URL (owned by sandbox; when set, adds the two sandbox↔files bridge tools).
  • ixo-chain reads the core NETWORK (selects the default chain REST gateway and Names worker, and is named in every result) and BLOCKSYNC_GRAPHQL_URL (the IXO Blocksync endpoint chain_lookup queries).

These are declared in the plugin's sibling schemas (typed 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.