Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ixo.world/llms.txt

Use this file to discover all available pages before exploring further.

Overview

createOracleApp(opts: CreateOracleAppOptions): Promise<OracleApp> resolves the plugin set, validates env, populates registries, builds the dynamic RuntimeAppModule, bootstraps NestJS, and returns an OracleApp whose listen() runs beforeListen callbacks then starts the HTTP server. Matrix init runs in the background — the returned promise resolves as soon as Nest is built. Status flips via onPluginStatusChange.
import { createOracleApp } from '@ixo/oracle-runtime';

CreateOracleAppOptions

export interface CreateOracleAppOptions {
  config: OracleConfig;
  features?: Partial<Record<BundledFeatureName | (string & {}), FeatureToggle>>;
  plugins?: OraclePlugin[];
  nestModules?: Array<Type | DynamicModule>;
  authExcludedRoutes?: AuthExcludedRoute[];
  bundledPlugins?: OraclePlugin[];
  env?: NodeJS.ProcessEnv;
  skipMatrixInit?: boolean;
  skipGracefulShutdown?: boolean;
  logger?: Logger;
  hooks?: MainAgentHooks;
}
  • Type: OracleConfig
  • Required: yes
Inline oracle config — name, optional org, optional description, optional prompt. The runtime fills in entityDid from the ORACLE_ENTITY_DID env var, so don’t put it here.
config: {
  name: 'My Oracle',
  org: 'My Org',
  description: 'What this oracle is for',
  prompt: {
    opening: '...',
    communicationStyle: '...',
    capabilities: '...',
  },
}
  • Type: Partial<Record<BundledFeatureName | (string & {}), FeatureToggle>> where FeatureToggle = boolean | 'auto'
  • Required: no
Override the default opt-in state per plugin. Keys are plugin names. Values: true (force on), false (force off), 'auto' (run autoDetect(env)). Omitted keys are treated as 'auto'.
features: {
  slack: false,
  composio: true,
  domainIndexer: 'auto',
}
  • Type: OraclePlugin[]
  • Required: no
Your plugin instances. Plus any bundled plugins you want to instantiate with constructor args (new EditorPlugin({ matrixClient }), new CreditsPlugin({ redis, network })). The plugin loader dedupes by name, so an explicit instance overrides the bundled default of the same name.
  • Type: Array<Type | DynamicModule>
  • Required: no
Your own NestJS modules. Spread into RuntimeAppModule.imports. They get full DI access to the Tier-0 services (Sessions, Messages, Secrets, UCAN, …) and can declare controllers, providers, OnModuleInit, OnModuleDestroy.
  • Type: AuthExcludedRoute[]
  • Required: no
Host-declared routes that must not pass through AuthHeaderMiddleware. Use for routes contributed by nestModules (webhooks, OAuth callbacks, public probes). Symmetric with each plugin’s getAuthExcludedRoutes() hook — both lists merge onto the runtime’s built-in exclusions (/health, /docs).
import { RequestMethod } from '@nestjs/common';

authExcludedRoutes: [
  { path: 'version', method: RequestMethod.GET },
]
  • Type: OraclePlugin[]
  • Required: no
Override the bundled plugin set. Provided primarily for tests so the harness can spin up createOracleApp without dragging in the full bundled catalog. Production callers should leave this unset.
  • Type: NodeJS.ProcessEnv
  • Required: no
Override process.env. Tests use this to inject a clean env. Production code should leave it unset.
  • Type: boolean
  • Required: no
Skip starting the Matrix background init. Tests set this so the factory resolves without touching real Matrix. Do not use in production.
  • Type: boolean
  • Required: no
Skip registering the SIGTERM/SIGINT shutdown handler. Tests set this so the harness does not leak process-level listeners. Do not use in production.
  • Type: Logger
  • Required: no
Override the bootstrap logger. Falls back to NestJS Logger.
  • Type: MainAgentHooks
  • Required: no
Optional overrides for the agent-build hooks (checkpointer, model resolver, prompt section snippets). Merged on top of the runtime’s defaults — host hooks win. The runtime supplies a per-user SQLite checkpointer by default; pass { checkpointerForUser: ... } here to swap it for an alternate implementation.

OracleApp

The resolved OracleApp exposes:
export interface OracleApp {
  getNestApp(): INestApplication;
  ambient: AmbientServices;
  plugins: { status(): PluginStatusReport };
  beforeListen(fn: (nestApp: INestApplication) => Promise<void> | void): void;
  onError(handler: (err: Error, source: string) => void): void;
  onPluginStatusChange(handler: (event: PluginStatusChangeEvent) => void): void;
  listen(port?: number): Promise<void>;
}
Returns the underlying INestApplication. Use it to add Express middleware, register global filters, or do anything else NestJS exposes. Mainly an escape hatch — nestModules and beforeListen cover most cases.
The production AmbientServices bag — used by MessagesController to build per-request RuntimeContexts before invoking createMainAgent. Forks normally don’t touch this directly.
Returns a snapshot of loader/exclusion/soft-dep state.
{
  loaded: string[];
  excluded: Array<{ plugin: string; reason: string }>;
  softDepGaps: Array<{ plugin: string; missing: string }>;
}
Register a callback that runs after Nest is built but before HTTP starts accepting. Multiple registrations run in order, awaiting each.
Subscribe to background errors. The runtime currently surfaces Matrix init failures here (with source: 'matrix-init'). If no handler is registered, errors log to the bootstrap logger.
Subscribe to plugin lifecycle changes. The current emitter is the Matrix init flow:
{ plugin: 'matrix', from: 'pending', to: 'loaded' | 'failed', reason?: string }
Multiple handlers may register; each is invoked for every event.
Start the HTTP server. Honours beforeListen callbacks first. Port resolution: explicit port arg → PORT env (validated) → 5678 default. Throws if called twice.

Behaviour

The function executes these steps in order:
  1. Validates config (name required).
  2. Resolves plugin set: bundled + your plugins, deduped by name, with features toggles and autoDetect predicates applied. Excluded plugins surface in plugins.status().excluded with their reason.
  3. Topologically sorts by dependsOn. Cycles or missing hard deps fail boot.
  4. Validates every plugin’s manifest. Hard violations fail boot.
  5. Composes env schema (base + all loaded plugins’ configSchema) and validates process.env. Missing required vars fail boot with [boot-error] Plugin '<name>' env validation failed for '<field>'.
  6. Cross-field check: the API key for the selected LLM_PROVIDER is present. (Schema-merge can’t express this.)
  7. Builds the internal OracleIdentity from config + validated env.
  8. Populates the six registries (tools, sub-agents, middleware, manifests, configSchema, sharedState).
  9. Collects getNestModules from every loaded plugin.
  10. Builds the dynamic RuntimeAppModule and bootstraps NestJS (NestFactory.create). CORS enabled with the framework’s allowed headers. Swagger UI mounts at /docs.
  11. Builds AmbientServices.
  12. Warms the boot caches (each registry runs its boot-time hooks once).
  13. Wires the default checkpointer (UserMatrixSqliteSyncService + SqliteSaver), then merges host hooks over it.
  14. Populates the OracleRuntimeBundleHolder so MessagesController can read it on each request.
  15. Schedules background Matrix init (unless skipMatrixInit: true). Registers a graceful-shutdown handler (unless skipGracefulShutdown: true).
  16. Returns the OracleApp.
Step 15 (Matrix init) runs asynchronously and does not block the returned promise. listen() blocks on beforeListen callbacks before starting HTTP.

Throws

  • Error('createOracleApp: \'config\' is required.')config missing.
  • Error('createOracleApp: \'config.name\' is required.')config.name empty.
  • Plugin resolution errors (cycle, missing hard dep, manifest invalid, env invalid). Each error is reported via logger.error with [boot-error] … prefix and a remediation hint.
  • Error('createOracleApp: ORACLE_ENTITY_DID env is required and was empty after validation.') — when the validated env’s ORACLE_ENTITY_DID is empty.
  • Error('OracleApp.listen called twice.') — when listen() is called more than once.