Skip to main content

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

Matrix

Storage

Blocksync / chain

Auth / UCAN

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

LLM

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).

Latency tuning

Two related latency wins ship always-on — there is no env var to disable them:
  • Request-path caches. Three wins ship together. The per-user SqliteSaver is reused across the two build-time hook calls per turn instead of being rebuilt on each call. The agent builder reads prior build-time checkpoints via a light path that skips the message-history join (O(1) instead of O(history)). The Memory Engine userContext cache is keyed by roomId instead of sessionId, so a fresh session for the same room reuses the cached context.
  • Memory-context compaction. The ## What you know about the user prompt block is deduped across all six memory buckets (entities collapsed by name, keeping the richest summary; facts / episodes / communities collapsed by normalized text — lowercase, punctuation stripped, whitespace collapsed) and bounded by fixed internal caps (per-entity summary and whole-block budget). When either cap trims content, the block ends with a _(More remembered — ask me to recall.)_ footer so trimmed detail stays reachable via the memory tool. No unique fact is dropped; paraphrases survive.

Misc

LangSmith tracing (optional)

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.

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.
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.