Skip to main content
A QiForge oracle is one main.ts that calls createOracleApp. The runtime boots a NestJS app, loads bundled + your plugins, validates env, builds the agent graph, and starts HTTP when you call listen(). Reference implementation: apps/qiforge-example/src/main.ts. Source: packages/oracle-runtime/src/bootstrap/create-oracle-app.ts.

Minimal main.ts

That’s a working oracle — every bundled plugin runs its autoDetect(env) and the ones whose env is present load.

The recipe

Write your config

Put OracleConfig in its own file so tests can import it without booting the runtime.
entityDid is sourced from the ORACLE_ENTITY_DID env var — never put it in config. The prompt block is composed into the system prompt; absent fields fall back to runtime defaults. Source: plugin-api/types.ts (search OracleConfig).

Add your plugins

Bundled plugins flow in automatically from BUNDLED_PLUGINS — each runs its own autoDetect(env). Only list the ones you wrote yourself or bundled plugins that need constructor args:
The loader dedupes by name, so an explicit instance overrides the bundled default of the same name. Source: bootstrap/plugin-loader.ts.

Toggle bundled plugins with features

Override autoDetect:
Omitted keys are treated as 'auto'. Recipe: Enable bundled plugins.
Feature keys are the plugin’s kebab-case name'domain-indexer', 'user-preferences', 'matrix-group-chats'. A camelCase key like domainIndexer type-checks (it falls into the open string arm) but is a silent no-op: the plugin keeps its default behaviour. Always quote the exact name.

Mount your own Nest modules

Custom HTTP endpoints, event consumers, admin dashboards — anything that doesn’t fit the plugin model goes in nestModules. Each gets full DI access to runtime services (Sessions, Messages, Secrets, UCAN, Auth, …).

Exclude routes from auth

Every route defaults to going through AuthHeaderMiddleware (requires x-ucan-delegation). Opt routes out via authExcludedRoutes:
Symmetric with each plugin’s getAuthExcludedRoutes(). Both merge onto the runtime’s built-ins (/health, /docs). Recipe for the plugin side: Add HTTP endpoints.

Customise the agent graph (optional)

hooks overrides the agent-build defaults. Every field is optional:
Source: graph/main-agent-types.ts (search MainAgentHooks).Change the AI model. The most common reason to set hooks is to swap the model. The runtime resolves the main agent’s model via resolveModel('main'); override it and spread params to keep the provider’s fallback models and latency sort:
The provider (LLM_PROVIDER = openrouter default | nebius) and per-role default model ids are otherwise fixed in the runtime — there is no env var to change the main model id without this hook. You can also return any LangChain BaseChatModel (e.g. new ChatOpenAI({ model: 'gpt-4o' })), but doing so drops the OpenRouter fallback/latency wiring.

Run a beforeListen hook

Run setup that must complete before HTTP starts accepting:
Hooks run sequentially in registration order.

Observe plugin lifecycle

onPluginStatusChange fires when Matrix transitions pending → loaded (or failed). onError catches Matrix init + lifecycle errors. plugins.status() returns a snapshot of loaded, excluded, and soft-dep-gap plugins.

Listen

Calling listen() twice throws. Default port is 3000 — set the PORT env var or pass a number to listen() to override.

All options at a glance

bundledPlugins, env, skipMatrixInit, and skipGracefulShutdown exist for the test harness. Don’t use them in production code — integration tests must boot the same way prod does.

Full example

Write a plugin

End-to-end Weather plugin walkthrough.

Enable bundled plugins

The features map in detail.

createOracleApp reference

Flat reference table for every field.

Environment variables

Core + per-plugin env vars.