What you'll have at the end
An oracle on @ixo/oracle-runtime-workers running locally on http://localhost:8787, with:
- Its own on-chain entity and DID (with its Ed25519 UCAN signing key), Matrix bot account and account room.
- The bundled plugins resolved at boot.
- The Weather plugin added, with its public
GET /weather/now?city=Xroute answering.
Step 0 — prerequisites
- Node.js 22+
- pnpm 10+ —
npm install -g pnpm - The IXO Mobile App or a 12/24-word mnemonic (for offline auth), with some IXO on the network you pick
- An OpenRouter API key — get one at openrouter.ai
- To deploy later: a Cloudflare account on the Workers Paid plan (local development needs no account)
Step 1 — install the CLI
pnpm add -g qiforge-cli # npm package name
pnpm approve-builds -g # approve protobufjs when prompted
qiforge-cli --version # 2.0.0 or laterStep 2 — provision and scaffold
qiforge-cli newThe CLI walks through:
- Auth — pick SignX (QR code) or offline (mnemonic). Offline is faster for local dev.
- Oracle profile — name, description, organisation, avatar. Defaults are fine.
- Network — pick
devnetfor development. - Install — let the CLI run
pnpm installat the end, or run it yourself. - Relayer node and Agent Card — keep the IXO relayer, and describe the services your oracle offers.
- Provisioning — enter a 6-digit PIN that encrypts the oracle's secrets, then confirm the transactions when prompted (SignX needs the mobile app; offline signs locally). The CLI creates the oracle's account and DID (with its UCAN signing key), its Matrix bot and account room, its entity, domain card, Agent Card and encryption key.
The result is a Cloudflare Workers project with the oracle's identity already in its wrangler configs:
my-oracle/
├── src/
│ ├── index.ts # createOracleWorker({ config, plugins })
│ ├── gateway.ts # the Matrix gateway as its own Worker script
│ ├── config.ts # identity + prompt slots
│ └── plugins/ # your plugins go here
├── .claude/
│ └── skills/qiforge-oracle/ # Claude Code skill (project-local)
├── wrangler.jsonc # oracle script ("minify": true, vars, Durable Objects)
├── wrangler.gateway.jsonc # gateway script
├── .dev.vars # the Worker's secrets (gitignored)
├── oracle-credentials.env # the oracle's wallet mnemonic + Matrix credentials (gitignored; back it up)
├── package.json # @ixo/oracle-runtime-workers 0.37.0
└── CLAUDE.mdThe full layout and every flag are in the CLI reference.
The scaffold drops a Claude Code skill at .claude/skills/qiforge-oracle/. Open the project in Claude Code (or any agent that reads .claude/skills/) and the skill auto-loads — your AI helper gets dense, project-local references for adding plugins and tools, env vars and secrets, testing inside workerd, and deploying. Nothing to install separately.
To try the scaffold without provisioning anything, run qiforge-cli new --no-interactive --name my-oracle --network devnet --skip-entity. It needs no login and writes placeholder identity values; copy .dev.vars.example to .dev.vars and fill it in yourself.
Step 3 — add your LLM key
qiforge-cli new wrote the Matrix secrets to .dev.vars. Add your OpenRouter key:
- OPEN_ROUTER_API_KEY=
+ OPEN_ROUTER_API_KEY=sk-or-v1-...The non-secret configuration — the oracle's DIDs, Matrix user and account room, and the network's memory engine, sandbox, Firecrawl, domain indexer and VFS URLs — is already in the vars of wrangler.jsonc. Plugins whose variables are missing don't load; the boot log names them and why. See Environment variables for the complete list.
Step 4 — boot
cd my-oracle
pnpm dev # wrangler dev, both scriptsYou should see something like:
[wrangler:info] Ready on http://localhost:8787
[my-oracle] [boot] oracle 'my-oracle' loaded plugins: [ixo-chain, memory, sandbox, firecrawl, domain-indexer, vfs, tasks, editor, user-preferences, portal, agui, attachments, matrix-group-chats, pod-creator] excluded: [action-proposals (...), hub-actions (...), composio (... (COMPOSIO_API_KEY))]The excluded plugins are normal — they need configuration you didn't set. Check the oracle answers:
curl http://localhost:8787/health
# {"status":"ok", ...}Every other route needs a UCAN invocation: curl http://localhost:8787/sessions answers 401.
Step 5 — add a plugin and call its public route
The runtime ships a demo WeatherPlugin that exercises every plugin hook. Add it in src/index.ts:
import {
BUNDLED_WORKERS_PLUGINS,
createOracleWorker,
WeatherPlugin,
} from '@ixo/oracle-runtime-workers';
import { config } from './config';
const oracle = createOracleWorker({
config,
plugins: [...BUNDLED_WORKERS_PLUGINS, new WeatherPlugin()],
});wrangler dev reloads. The boot line now lists weather. Its GET /weather/now route is excluded from UCAN auth through getAuthExcludedRoutes():
curl 'http://localhost:8787/weather/now?city=Berlin'{
"ok": true,
"city": "Berlin",
"temp_c": 5.3,
"units": "celsius",
"conditions": "partly cloudy",
"latitude": 52.52437,
"longitude": 13.41053
}This proves the plugin loaded, its route mounted on the shell, the auth exclusion applied, and the call to Open-Meteo went out from the Worker. To write your own plugin, run qiforge-cli plugin new <name> — see Write a plugin.
Step 6 — deploy and chat
Chat clients (the Portal, Matrix) authenticate every request with a UCAN invocation addressed to the oracle, so you chat with the deployed oracle:
pnpm exec wrangler login
pnpm run deploy # the gateway script, then the oracle script
pnpm exec wrangler secret bulk .dev.vars
pnpm exec wrangler secret put MATRIX_ORACLE_ADMIN_PASSWORD -c wrangler.gateway.jsonc
pnpm exec wrangler secret put MATRIX_RECOVERY_PHRASE -c wrangler.gateway.jsonc
pnpm exec wrangler secret put MATRIX_VALUE_PIN -c wrangler.gateway.jsonc
qiforge-cli update-oracle-api-url # point the entity at https://my-oracle.<subdomain>.workers.devBoth wrangler configs set "minify": true; keep it on. See Deploy → Cloudflare Workers runtime.
What you just verified
| You saw | What it proves |
|---|---|
qiforge-cli new finishing | The oracle's DID, signing key, Matrix account and entity exist, and the project carries them. |
| Boot line listing plugins | Plugin loading, env validation and auto-detection worked. |
excluded: with reasons | Env-driven opt-in (autoDetect) is wired correctly. |
/health 200, /sessions 401 | The shell runs and enforces UCAN auth. |
GET /weather/now returning JSON | A plugin's getRoutes and getAuthExcludedRoutes apply. |
Where to go next
Task-by-task recipes — plugins, test, deploy.
Write a pluginRecreate the Weather plugin from scratch.
CLI referenceEvery command and flag, and what new writes.
The plugins shipped with the runtime.
For AI agentsDense one-pager with every signature inlined.
Understand the modelOptional. The mental model, runtime layers, plugins vs skills.
Troubleshooting
| Symptom | Likely fix |
|---|---|
| Boot fails naming an env key | Set it in wrangler.jsonc vars (or .dev.vars for a secret), or turn the plugin off with features: { name: false } in createOracleWorker. |
[matrix] ensureStarted failed in the log | The gateway cannot log the bot in: check MATRIX_BASE_URL, MATRIX_ORACLE_ADMIN_USER_ID and MATRIX_ORACLE_ADMIN_PASSWORD. |
| Memory, VFS or sandbox tools refuse with no signing key | The oracle has no Ed25519 UCAN signing key: run qiforge-cli setup-signing-key --env oracle-credentials.env (oracles created before CLI 2.0.0). |
Durable Object's isolate exceeded its memory limit | Keep "minify": true and use runtime 0.37.0 or later — see Deploy. |
Full coverage: Troubleshooting.