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.

This page is for AI tools (Cursor, Claude Code, Codex, …) scaffolding a QiForge oracle. Humans should read Quickstart and Build a plugin instead. If you are an AI agent: read top-to-bottom once. Every signature you need is inlined. Source paths cite the canonical files on the QiForge runtime (packages/oracle-runtime) and the reference oracle (apps/qiforge-example).
If you are working inside a scaffolded oracle project (one created with qiforge-cli --init or qiforge-cli new), there is already a Claude Code skill at .claude/skills/qiforge-oracle/SKILL.md with project-local guidance: adding plugins, adding tools, wiring env, writing tests with createTestRuntime. Load it first — it carries denser, scenario-specific references than this page.

TL;DR — what you produce

A QiForge oracle is one main.ts that calls createOracleApp({ config, plugins, … }) plus one folder per plugin under src/plugins/<name>/. The runtime handles HTTP, auth, the agent graph, the checkpointer, Matrix, and bundles 15 plugins by default. You ship glue code, not infrastructure.

main.ts shape

import { createOracleApp } from '@ixo/oracle-runtime';
import { MyPlugin } from './plugins/my-plugin/my-plugin.plugin.js';

const app = await createOracleApp({
  config: {
    name: 'My Oracle',
    org: 'Acme',
    description: 'One-line pitch the system prompt will use.',
  },
  plugins: [new MyPlugin()],
  features: {
    composio: false,        // opt out of bundled composio
    firecrawl: 'auto',      // (default) — autoDetect from env
  },
});

await app.listen(Number(process.env.PORT ?? 3000));
Source: apps/qiforge-example/src/main.ts.

createOracleApp — full options

packages/oracle-runtime/src/bootstrap/create-oracle-app.ts
interface CreateOracleAppOptions {
  config: OracleConfig;                                           // required
  features?: Partial<Record<BundledFeatureName | string, FeatureToggle>>;
  plugins?: OraclePlugin[];
  nestModules?: Array<Type | DynamicModule>;
  authExcludedRoutes?: AuthExcludedRoute[];
  bundledPlugins?: OraclePlugin[];                                // test override
  env?: NodeJS.ProcessEnv;                                        // test override
  skipMatrixInit?: boolean;                                       // test override
  skipGracefulShutdown?: boolean;                                 // test override
  logger?: PluginLogger;
  hooks?: MainAgentHooks;
}

type FeatureToggle = boolean | 'auto';

interface OracleConfig {
  name: string;
  org?: string;
  description?: string;
  prompt?: { opening?: string; communicationStyle?: string; capabilities?: string };
}

interface AuthExcludedRoute { path: string; method?: RequestMethod }

interface MainAgentHooks {
  checkpointerForUser?: (userDid: string) => Promise<BaseCheckpointSaver>;
  resolveModel?: (role: ModelRole, params?: ChatOpenAIFields) => ChatModel;
  getRoomTitle?: (roomId: string) => Promise<string | undefined>;
  safetyModel?: BaseChatModel;
  validationSkipToolNames?: string[];
  operationalMode?: string;
  editorSection?: string;
  composioContext?: string;
  userSecretsContext?: string;
  degradedServicesBlock?: string;
}
createOracleApp returns an OracleApp:
interface OracleApp {
  getNestApp(): INestApplication;
  ambient: AmbientServices;
  plugins: { status(): { loaded: string[]; excluded: { plugin: string; reason: string }[]; softDepGaps: { plugin: string; missing: string }[] } };
  beforeListen(fn: (nestApp: INestApplication) => Promise<void> | void): void;
  onError(handler: (err: Error, source: string) => void): void;
  onPluginStatusChange(handler: (event: { plugin: string; from: 'pending'|'loaded'|'failed'; to: 'pending'|'loaded'|'failed'; reason?: string }) => void): void;
  listen(port?: number): Promise<void>;
}

OraclePlugin — all 9 hooks

packages/oracle-runtime/src/plugin-api/oracle-plugin.ts
abstract class OraclePlugin {
  abstract readonly name: string;                     // kebab-case
  abstract readonly version: string;
  abstract readonly manifest: PluginManifest;

  readonly dependsOn?: string[];                      // hard deps — boot fails if missing
  readonly softDependsOn?: string[];                  // soft deps — branches on availability
  readonly configSchema?: z.ZodObject<any>;           // merged into runtime env schema

  autoDetect?(env: NodeJS.ProcessEnv): boolean;       // skip plugin when false
  readonly autoDetectHint?: string;                   // surfaced in boot errors

  getTools?(ctx: PluginContext): PluginTool[] | Promise<PluginTool[]>;
  getSubAgents?(ctx: PluginContext): PluginSubAgent[];
  getRequestTools?(rtCtx: RuntimeContext): PluginTool[] | Promise<PluginTool[]>;
  getRequestSubAgents?(rtCtx: RuntimeContext): PluginSubAgent[] | Promise<PluginSubAgent[]>;
  getMiddlewares?(ctx: PluginContext): AgentMiddleware[];
  getSharedState?(): Record<string, (state: any, runCtx: RuntimeContext) => unknown>;
  getNestModules?(ctx?: PluginContext): Array<Type | DynamicModule>;
  getAuthExcludedRoutes?(): AuthExcludedRoute[];
}

PluginManifest

interface PluginManifest {
  title: string;                                                       // human name
  summary: string;                                                     // one-line, shown in Tier-1 prompt for `always`
  whenToUse: string[];                                                 // triggers
  whenNotToUse?: string[];                                             // anti-patterns
  examples?: { user: string; thought?: string; tool: string; args?: Record<string, unknown> }[];
  tags?: string[];
  category?: 'data' | 'communication' | 'automation' | 'memory' | 'integration' | 'ui' | 'auth' | 'observability' | 'core';
  visibility?: 'always' | 'on-demand' | 'silent';                      // default: 'on-demand'
  stability?: 'stable' | 'beta' | 'experimental';
}

PluginTool, PluginSubAgent

interface PluginTool {
  name: string;
  description: string;
  schema: z.ZodType;
  handler: (args: unknown, ctx: RuntimeContext) => Promise<unknown>;
  visibility?: 'always' | 'on-demand' | 'silent';                      // override plugin default per tool
}

interface PluginSubAgent {
  name: string;                                                        // e.g. 'call_memory_agent'
  description: string;
  systemPrompt: string | ((ctx: PluginContext) => string);
  tools: PluginTool[] | ((ctx: PluginContext) => PluginTool[]);
  model?: ModelRole;                                                   // default 'subagent'
  middlewares?: AgentMiddleware[];
  forwardTools?: boolean | string[];                                   // forward tool calls to main message stream
  onComplete?: (result: string, ctx: RuntimeContext) => Promise<void>;
}

Visibility rules

VisibilityBound at boot?In Tier-1 prompt?Discoverable via list_capabilities?
alwaysyesyesyes
on-demand (default)no — until load_capability(name)noyes
silentyes, but agent doesn’t see themnono

Bundled plugins (14)

From packages/oracle-runtime/src/plugins/index.ts:
NameVisibilityDefaultRequired env
memoryalwaysonMEMORY_MCP_URL, MEMORY_ENGINE_URL
portalon-demandon
firecrawlon-demandauto-detectFIRECRAWL_MCP_URL
domain-indexeralwayson
composioon-demandauto-detectCOMPOSIO_API_KEY
sandboxalwaysauto-detectSANDBOX_MCP_URL
skillsalwayson (needs sandbox)
editoron-demandon (needs Matrix)
aguion-demandon
slacksilentauto-detectSLACK_BOT_OAUTH_TOKEN
tasksstubauto-detect (stub)REDIS_URL
creditssilenton unless DISABLE_CREDITS=true
callsstubon (stub)
user-preferencesalwayson
Toggle via features in createOracleApp: true forces on, false forces off, 'auto' runs autoDetect. Per-plugin reference pages: /build-an-oracle/reference/bundled-plugins/overview.

Core (Tier-0) env vars

From packages/oracle-runtime/src/config/base-env-schema.ts:
VarRequiredDescription
ORACLE_ENTITY_DIDyesThe oracle’s IXO entity DID
ORACLE_MNEMONICyesWallet mnemonic for chain signing
ORACLE_MATRIX_USER_IDyesMatrix user ID the bot logs in as
ORACLE_MATRIX_PASSWORDyesMatrix bot password
ORACLE_MATRIX_HOMESERVER_URLyesMatrix homeserver URL
IXO_NETWORK_RPC_URLyesIXO chain RPC endpoint
OPENAI_API_KEY or ANTHROPIC_API_KEYyesAt least one LLM provider key
LANGSMITH_API_KEYoptionalEnables LangSmith tracing
LANGSMITH_TRACINGoptionaltrue to enable
LANGSMITH_PROJECToptionalLangSmith project name
CORS_ORIGINoptionalWildcard * is default
PORToptionalHTTP port; defaults to 3000
Plugin-specific env vars merge in via each plugin’s configSchema.

Copy-pasteable plugin template

// src/plugins/example/example.plugin.ts
import { OraclePlugin, type PluginManifest, type PluginTool, type RuntimeContext } from '@ixo/oracle-runtime';
import { z } from 'zod';

const manifest: PluginManifest = {
  title: 'Example',
  summary: 'One-line description shown in the Tier-1 prompt block.',
  whenToUse: ['User asks about example things'],
  whenNotToUse: ['User asks about unrelated things'],
  visibility: 'on-demand',
  stability: 'experimental',
};

export class ExamplePlugin extends OraclePlugin {
  readonly name = 'example';
  readonly version = '0.1.0';
  readonly manifest = manifest;

  readonly configSchema = z.object({
    EXAMPLE_API_KEY: z.string().min(1),
  });

  autoDetect(env: NodeJS.ProcessEnv): boolean {
    return Boolean(env.EXAMPLE_API_KEY);
  }
  readonly autoDetectHint = 'EXAMPLE_API_KEY';

  override getTools(): PluginTool[] {
    return [
      {
        name: 'get_example',
        description: 'Fetch an example.',
        schema: z.object({ query: z.string() }),
        handler: async (args, rtCtx: RuntimeContext) => {
          const { query } = args as { query: string };
          rtCtx.logger.log(`Example tool called with ${query}`);
          return { result: `echo: ${query}` };
        },
      },
    ];
  }
}
Then in main.ts:
import { ExamplePlugin } from './plugins/example/example.plugin.js';

const app = await createOracleApp({
  config: { name: 'My Oracle' },
  plugins: [new ExamplePlugin()],
});
await app.listen(Number(process.env.PORT ?? 3000));

Where to dig deeper