Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions build-an-oracle/develop/create-oracle-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@
const app = await createOracleApp({
config,
features: {
composio: false, // force off
firecrawl: true, // force on, even if env is missing (will fail env validation if so)
domainIndexer: 'auto', // default — runs autoDetect(env)
composio: false, // force off
firecrawl: true, // force on, even if env is missing (will fail env validation if so)
'domain-indexer': 'auto', // default — runs autoDetect(env)
},
});
```
Expand All @@ -87,6 +87,10 @@
| `'auto'` | Default. Run `autoDetect(env)`. |

Omitted keys are treated as `'auto'`. Recipe: [Enable bundled plugins](/build-an-oracle/develop/enable-bundled-plugins).

<Warning>
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`.
</Warning>
</Step>

<Step title="Mount your own Nest modules" icon="cube">
Expand Down Expand Up @@ -168,6 +172,26 @@
| `degradedServicesBlock` | empty | Degraded-services notice appended to system prompt |

Source: [`graph/main-agent-types.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/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:

```ts
import { createOracleApp, getProviderChatModel } from '@ixo/oracle-runtime';

const app = await createOracleApp({
config,
hooks: {
// role is 'main' | 'subagent' | 'utility' | (string & {}).
resolveModel: (role, params) =>
getProviderChatModel(role, {
...params,
...(role === 'main' && { model: 'google/gemini-3.1-flash-lite' }),
}),
},
});
```

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

<Step title="Run a beforeListen hook" icon="play">
Expand Down Expand Up @@ -201,16 +225,16 @@
logger.log(`[boot] loaded: ${status.loaded.join(', ')}`);
```

`onPluginStatusChange` fires when Matrix transitions `pending → loaded` (or `failed`). `onError` catches Matrix init + lifecycle errors. `plugins.status()` returns the same shape `qiforge inspect` prints.
`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.
</Step>

<Step title="Listen" icon="rocket">
```ts
await app.listen(); // honours config.PORT, env.PORT, then 5678
await app.listen(3000); // explicit port
await app.listen(); // uses the PORT env var (defaults to 3000)
await app.listen(8080); // explicit port — overrides PORT
```

Calling `listen()` twice throws. Default port is **5678** — set `PORT` env or pass a number to override.
Calling `listen()` twice throws. Default port is **3000** — set the `PORT` env var or pass a number to `listen()` to override.
</Step>
</Steps>

Expand Down Expand Up @@ -289,7 +313,7 @@

<CardGroup cols={2}>
<Card title="Write a plugin" icon="puzzle-piece" href="/build-an-oracle/develop/write-a-plugin">
End-to-end Weather plugin walkthrough.

Check warning on line 316 in build-an-oracle/develop/create-oracle-app.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/create-oracle-app.mdx#L316

Did you really mean 'walkthrough'?
</Card>
<Card title="Enable bundled plugins" icon="boxes-stacked" href="/build-an-oracle/develop/enable-bundled-plugins">
The `features` map in detail.
Expand Down
28 changes: 18 additions & 10 deletions build-an-oracle/develop/deploy.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
title: "Deploy your oracle"
description: "Build a production bundle, persist the Matrix store, wire health probes. Platform-agnostic with a reference Dockerfile."

Check warning on line 3 in build-an-oracle/develop/deploy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/deploy.mdx#L3

Did you really mean 'Dockerfile'?
icon: "rocket-launch"
---

## Reference Dockerfile

Check warning on line 7 in build-an-oracle/develop/deploy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/deploy.mdx#L7

Did you really mean 'Dockerfile'?

QiForge has no runtime dependency on itself — anywhere Node 22+ runs, an oracle runs. This Dockerfile is the shortest path from `pnpm dev` to a container you can ship.

Check warning on line 9 in build-an-oracle/develop/deploy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/deploy.mdx#L9

Did you really mean 'Dockerfile'?

```dockerfile
FROM node:22-slim AS builder
Expand All @@ -24,7 +24,7 @@
RUN mkdir -p /data
ENV MATRIX_STORE_PATH=/data/matrix-storage
ENV SQLITE_DATABASE_PATH=/data/sqlite
EXPOSE 5678
EXPOSE 3000
CMD ["node", "dist/main.js"]
```

Expand All @@ -33,8 +33,8 @@
## Prerequisites

- Node.js 22+
- A reachable Matrix homeserver

Check warning on line 36 in build-an-oracle/develop/deploy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/deploy.mdx#L36

Did you really mean 'homeserver'?
- A persistent volume for the Matrix store and SQLite checkpointer

Check warning on line 37 in build-an-oracle/develop/deploy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/deploy.mdx#L37

Did you really mean 'checkpointer'?
- Core env vars (per [`baseEnvSchema`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts))
- Optional: Redis (only if `credits` or `tasks` plugins are loaded)

Expand All @@ -55,7 +55,7 @@

```text
NODE_ENV=production
PORT=5678
PORT=3000
ORACLE_NAME=My Oracle
CORS_ORIGIN=https://your-portal.example
NETWORK=mainnet
Expand All @@ -67,6 +67,10 @@
RPC_URL=https://rpc.ixo.world
BLOCKSYNC_GRAPHQL_URL=https://blocksync.ixo.world/graphql

# Auth tuning (optional — safe defaults)
UCAN_AUTH_MAX_TTL_SECONDS=900 # max accepted lifetime of a user auth invocation (default 15 min)
UCAN_REAUTH_PROMPT_THROTTLE_SECONDS=21600 # gap between re-authorize prompts (default 6 hours)

# Matrix
MATRIX_BASE_URL=https://matrix.ixo.world
MATRIX_ORACLE_ADMIN_USER_ID=@my-oracle-bot:ixo.world
Expand Down Expand Up @@ -98,8 +102,8 @@
Identity and Matrix keys are provisioned via the CLI — see [identity and auth](/build-an-oracle/develop/identity-and-auth) for the full flow.

```sh
qiforge create-entity --no-interactive --network mainnet ...
qiforge setup-encryption-key
qiforge-cli create-entity --no-interactive --network mainnet ...
qiforge-cli setup-encryption-key
```
</Step>
</Steps>
Expand Down Expand Up @@ -159,13 +163,13 @@

<Steps>
<Step title="Use /health as the liveness probe">
The framework exposes `GET /health`, always public, never goes through `AuthHeaderMiddleware`. Returns 200 once Nest is up.
The framework exposes `GET /health`, always public, never goes through `AuthHeaderMiddleware`. Returns 200 once Nest is up. The built-in auth-excluded routes are `/` (a JSON landing payload), `/health`, `/docs`, and `/docs/(.*)` — everything else requires auth.

Docker:

```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsS http://localhost:5678/health || exit 1
CMD curl -fsS http://localhost:3000/health || exit 1
```

Kubernetes:
Expand All @@ -174,7 +178,7 @@
livenessProbe:
httpGet:
path: /health
port: 5678
port: 3000
```
</Step>

Expand All @@ -201,9 +205,11 @@
```text
Content-Type, Authorization,
x-ucan-delegation, x-matrix-access-token, x-matrix-homeserver,
x-did, x-request-id, x-timezone
x-did, x-request-id, x-auth-type, x-timezone
```

`Authorization` and `x-auth-type` carry the primary invocation-auth path (`Authorization: Bearer <invocation>` + `X-Auth-Type: ucan`); `x-ucan-delegation` carries the downstream-authorization delegation. If you replicate CORS at a reverse proxy, mirror this full list — dropping `x-auth-type` breaks browser clients on the primary auth path.

## Logging

The runtime uses NestJS's default `Logger`. Pipe stdout/stderr to your aggregator. Override the bootstrap logger with `createOracleApp({ logger })` if you need a custom format — see [createOracleApp reference](/build-an-oracle/reference/createoracleapp).
Expand All @@ -212,7 +218,9 @@

## Graceful shutdown

The runtime registers a `SIGTERM` / `SIGINT` handler that closes the Nest app and disconnects Matrix.
The runtime registers a `SIGTERM` / `SIGINT` handler that drains in order: (1) **flushes the per-user checkpoint to Matrix**, (2) closes the Nest app, (3) shuts down the Matrix client, (4) runs any plugin teardowns. Each step is isolated — a failure in one is logged and the rest still run.

Check warning on line 221 in build-an-oracle/develop/deploy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/deploy.mdx#L221

Did you really mean 'teardowns'?

That first step is why a clean `SIGTERM` matters: an abrupt `SIGKILL` skips the checkpoint upload and loses recent thread state. See [`graceful-shutdown.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/graceful-shutdown.ts).

```ts
// To disable (rare — only when your platform's process manager
Expand All @@ -224,7 +232,7 @@
```

<Warning>
Disable graceful shutdown only when your platform's process manager guarantees a clean `app.close()` on termination. Otherwise you'll get partial flushes and Matrix sync corruption.
Disable graceful shutdown only when your platform's process manager guarantees a clean `app.close()` on termination. Otherwise you'll skip the checkpoint flush and risk Matrix sync corruption.
</Warning>

## Where to read next
Expand Down
20 changes: 6 additions & 14 deletions build-an-oracle/develop/enable-bundled-plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

<Steps>
<Step title="The runtime starts with the bundled list">
[`BUNDLED_PLUGINS`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/index.ts) is a fixed 15-plugin tuple — memory, portal, firecrawl, domain-indexer, composio, sandbox, skills, editor, agui, slack, tasks, credits, calls, user-preferences, matrix-group-chats.

Check warning on line 34 in build-an-oracle/develop/enable-bundled-plugins.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/enable-bundled-plugins.mdx#L34

Did you really mean 'firecrawl'?

Check warning on line 34 in build-an-oracle/develop/enable-bundled-plugins.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/enable-bundled-plugins.mdx#L34

Did you really mean 'composio'?

Check warning on line 34 in build-an-oracle/develop/enable-bundled-plugins.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/enable-bundled-plugins.mdx#L34

Did you really mean 'agui'?

You do not import or instantiate the plugins you want at defaults — they are already there.
</Step>
Expand All @@ -53,11 +53,11 @@
</Step>

<Step title="Your own plugins are added next">
Plugins from the `plugins: []` array are always loaded — they're not gated by `features`. If a name collides with a bundled plugin, your instance wins (the loader dedupes by `name`).

Check warning on line 56 in build-an-oracle/develop/enable-bundled-plugins.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/enable-bundled-plugins.mdx#L56

Did you really mean 'dedupes'?
</Step>

<Step title="Hard-dep cascades resolve transitively">
If a loaded plugin has `dependsOn: ['removed-plugin']` and that dep ended up excluded, the dependent cascades off too. Soft deps (`softDependsOn`) only log a warning.

Check warning on line 60 in build-an-oracle/develop/enable-bundled-plugins.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ixoworld) - vale-spellcheck

build-an-oracle/develop/enable-bundled-plugins.mdx#L60

Did you really mean 'deps'?
</Step>
</Steps>

Expand All @@ -67,7 +67,7 @@

| Plugin | Auto-detects when | Notes |
| --- | --- | --- |
| `memory` | `MEMORY_MCP_URL` and `MEMORY_ENGINE_URL` set | Visibility `always` |
| `memory` | `MEMORY_MCP_URL` set | Visibility `always` |
| `portal` | always on | Visibility `on-demand` |
| `firecrawl` | `FIRECRAWL_MCP_URL` set | Visibility `on-demand` |
| `domain-indexer` | always on | Visibility `always` |
Expand All @@ -77,9 +77,9 @@
| `editor` | always on | Needs `matrixClient` — instantiate explicitly |
| `agui` | always on | Visibility `on-demand` |
| `slack` | `SLACK_BOT_OAUTH_TOKEN` set | Visibility `silent` (transport) |
| `tasks` | `REDIS_URL` set | Stub |
| `tasks` | `REDIS_URL` set | Visibility `on-demand`; BullMQ-backed async tasks (needs `REDIS_URL`) |
| `credits` | always on | Visibility `silent`; pass `redis` for production |
| `calls` | always on | Stub |
| `calls` | always on | Visibility `silent`; placeholder stub (no tools yet) |
| `user-preferences` | always on | Visibility `always` |
| `matrix-group-chats` | always on | Visibility `on-demand`; gating middleware + tools fire only in Matrix group rooms (`memberCount > 2`) |

Expand Down Expand Up @@ -176,22 +176,14 @@
// {
// loaded: ['memory', 'domain-indexer', 'editor', 'user-preferences', 'weather'],
// excluded: [
// { plugin: 'composio', reason: 'auto-detect precondition not met (COMPOSIO_API_KEY)', cause: 'auto_detect_missing' },
// { plugin: 'slack', reason: 'feature flag set to false', cause: 'feature_false' },
// { plugin: 'composio', reason: 'auto-detect precondition not met (COMPOSIO_API_KEY)' },
// { plugin: 'slack', reason: 'feature flag set to false' },
// ],
// softDepGaps: [],
// }
```

`cause` is one of `'feature_false'`, `'auto_detect_missing'`, `'cascaded'`. Surface this in your boot logs so operators see what came up.
</Step>

<Step title="Or use the CLI">
```sh
qiforge inspect
```

Same data, formatted for the terminal. See the [CLI reference](/build-an-oracle/reference/cli).
Each `excluded` entry is `{ plugin, reason }` — surface the `reason` in your boot logs so operators see why a plugin came up missing. (`softDepGaps` entries are `{ plugin, missing }`.)
</Step>
</Steps>

Expand Down
Loading
Loading