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.

Turn on LangSmith in one block

LangChain auto-wires tracing when these env vars are present in process.env — the runtime never reads them, only declares them in the base env schema so they show up in qiforge env output.
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=ls-...
LANGSMITH_PROJECT=my-oracle
LANGSMITH_ENDPOINT=https://api.smith.langchain.com   # optional, defaults to LangSmith cloud
Env varRequiredPurpose
LANGSMITH_TRACINGyesSet to true to enable tracing. Anything else (or unset) disables it.
LANGSMITH_API_KEYyesAPI key from your LangSmith workspace.
LANGSMITH_PROJECTyesProject name traces land under.
LANGSMITH_ENDPOINTnoOverride for self-hosted LangSmith. Defaults to LangSmith cloud.
No code changes needed. Set the vars and every LLM call, tool invocation, and middleware hook produces a span in the LangSmith UI. Unset them to turn it off.

What QiForge gives you out of the box

Three signals — that’s the whole surface. Bring your own log aggregator and metrics.
  1. LangSmith traces — env-driven, covered above.
  2. Plugin status eventsapp.onPluginStatusChange(handler).
  3. Plugin-scoped loggerctx.logger (boot) and rtCtx.logger (per-request).

Subscribe to plugin status events

1

Register a handler after createOracleApp returns

app.onPluginStatusChange((event) => {
  Logger.log(
    `[plugin] ${event.plugin} ${event.from}${event.to}` +
      (event.reason ? ` (${event.reason})` : ''),
  );
});
Live example: apps/qiforge-example/src/main.ts.
2

Listen for the matrix plugin transitions specifically

The plugin that reliably emits transitions is matrix — it starts pending at boot and flips to either loaded or failed once Matrix init completes in the background.
app.onPluginStatusChange((event) => {
  if (event.plugin === 'matrix' && event.to === 'loaded') {
    // safe to send messages, mint UCANs, etc.
  }
});
Use this to gate operations on Matrix being up, or to alert when it fails.
The event shape:
{
  plugin: string;
  from: 'pending' | 'loaded' | 'failed';
  to:   'pending' | 'loaded' | 'failed';
  reason?: string;
}

Capture background errors with onError

app.onError((err, source) => {
  Logger.error(`[runtime] ${source}: ${err.message}`);
});
source is a short label like 'matrix-init'. Use this for background failures (Matrix sync errors, key setup failures). Boot-time errors (env validation, manifest validation, dependency cycles) throw from createOracleApp itself — your try/catch around the call sees them.

Log the boot resolution snapshot

app.plugins.status() returns the loader’s resolution snapshot — log it once at boot so operators see what came up.
const status = app.plugins.status();
Logger.log(`[boot] loaded plugins: ${status.loaded.join(', ') || '(none)'}`);
if (status.excluded.length > 0) {
  Logger.log(
    '[boot] excluded plugins:',
    status.excluded.map((e) => `${e.plugin} (${e.reason})`).join(', '),
  );
}
Shape of the snapshot — defined in packages/oracle-runtime/src/bootstrap/plugin-loader.ts:
{
  loaded: string[];                                  // plugin names that came up
  excluded: Array<{
    plugin: string;
    reason: string;                                  // e.g. "auto-detect precondition not met (COMPOSIO_API_KEY)"
    cause: 'feature_false' | 'auto_detect_missing' | 'cascaded';
  }>;
  softDepGaps: Array<{ plugin: string; missing: string }>;
}

Use the plugin-scoped logger

Every PluginContext and RuntimeContext carries a logger field auto-prefixed with the plugin’s name — see Logger in types.ts.
override getTools(ctx: PluginContext): PluginTool[] {
  ctx.logger.log('Building weather tools');
  // ...
}

handler: async (args, rtCtx: RuntimeContext) => {
  rtCtx.logger.log(`Looking up ${args.city}`);
  // ...
}
The Logger interface:
MethodRequiredPurpose
log(message, ...optional)yesInfo-level output.
error(message, ...optional)yesError-level output.
warn(message, ...optional)yesWarn-level output.
debug(message, ...optional)noDebug-level — implementation-defined.
verbose(message, ...optional)noVerbose-level — implementation-defined.
child(bindings)noReturns a logger with additional context fields. Falls back to the same logger if not implemented.
Use NestJS’s default Logger format in development (pnpm dev shows it nicely); pipe to your aggregator in production. Override the global logger with createOracleApp({ logger }) if you want a custom format — see createOracleApp reference.

UI-facing tool-call events

For showing the user what the agent did, the runtime emits typed events via rtCtx.emit. The bundled clients (Portal, Slack, Matrix) consume these and render the right UI — you only emit them yourself when writing a custom client.
rtCtx.emit.toolCall({ /* ... */ });
rtCtx.emit.actionCall({ /* ... */ });
rtCtx.emit.renderComponent({ /* ... */ });
rtCtx.emit.reasoning({ /* ... */ });
rtCtx.emit.browserToolCall({ /* ... */ });
rtCtx.emit.router({ /* ... */ });
rtCtx.emit.messageCacheInvalidation({ /* ... */ });
The seven event types are defined on RuntimeContext.emit in types.ts.

Deployment

Logs and probes in production.

Add a middleware

Custom observability via plugin middleware hooks.

Runtime context

Full rtCtx.emit and rtCtx.logger surface.

Environment variables

Every env var the runtime declares.