Quickstart

Scaffold a working oracle, boot it, and watch the agent call a plugin tool — in about ten minutes.

What you'll have at the end

You: what's the weather in Berlin?
Oracle: (calls list_capabilities → load_capability(weather) → get_current_weather)
        Berlin is currently 14°C with light rain.

A live oracle on localhost:3000 with:

  • The 16 bundled plugins resolved at boot.
  • A custom Weather plugin loading on demand.
  • A GET /weather/now?city=X public HTTP route.
  • A streaming chat endpoint over SSE.

Step 0 — prerequisites

  • Node.js 22+
  • pnpmnpm install -g pnpm
  • The IXO Mobile App or a 12/24-word mnemonic (for offline auth)
  • An OpenRouter API key — get one at openrouter.ai

Step 1 — install the CLI

pnpm add -g qiforge-cli           # npm package name
pnpm approve-builds -g            # approve protobufjs when prompted
qiforge-cli --help                # the installed binary is `qiforge-cli`

If it prints the help text, the CLI is ready.

Step 2 — scaffold a project

qiforge-cli new my-oracle

The CLI walks through:

  1. Auth — pick SignX (QR code) or offline (mnemonic). Offline is faster for local dev.
  2. Network — pick devnet for development.
  3. Oracle profile — name, description, price, model. Defaults are fine.
  4. Entity creation — the CLI writes a transaction to register your oracle's entity DID on chain. Confirm when prompted (SignX needs the mobile app; offline signs locally).
  5. Matrix bot — provisioned automatically.

The result is a plugin-runtime-shaped project:

my-oracle/
├── src/
│   ├── main.ts                 # calls createOracleApp({ config, plugins })
│   ├── config.ts               # OracleConfig
│   └── plugins/                # your plugins go here
├── .claude/
│   └── skills/qiforge-oracle/  # Claude Code skill (project-local)
├── .env                        # Matrix creds, ORACLE_*, mnemonic
├── oracle.config.json          # name, model, prompt, DID
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── CLAUDE.md                   # bootstrapping for Claude Code

Step 3 — fill in .env

Open your project's .env (my-oracle/.env from qiforge-cli new, or apps/qiforge-example/.env if you cloned the boilerplate). The CLI populated identity vars; you need to add the LLM key and any plugin vars:

- OPEN_ROUTER_API_KEY=
+ OPEN_ROUTER_API_KEY=sk-or-v1-...

  # Optional — enables specific plugins:
+ MEMORY_MCP_URL=https://memory-mcp.your-deployment.example/sse
+ MEMORY_ENGINE_URL=https://memory-engine.your-deployment.example
+ FIRECRAWL_MCP_URL=https://firecrawl-mcp.your-deployment.example/sse
+ SANDBOX_MCP_URL=https://sandbox-mcp.your-deployment.example/sse
+ WEATHER_DEFAULT_UNITS=celsius

Plugins whose env vars you leave blank simply don't load — they're skipped with a [boot] excluded: <plugin> (<env-var>) line.

See Environment variables reference for the complete list.

Step 4 — install + boot

cd my-oracle    # or qiforge/apps/qiforge-example
pnpm install
pnpm dev

You should see something like:

[Nest] Starting Nest application...
[Nest] OraclePlugin {memory} loaded (always)
[Nest] OraclePlugin {domain-indexer} loaded (always)
[Nest] OraclePlugin {editor} loaded (on-demand)
[Nest] OraclePlugin {user-preferences} loaded (always)
[Nest] OraclePlugin {weather} loaded (on-demand)
[boot] excluded plugins: composio (COMPOSIO_API_KEY), slack (SLACK_BOT_OAUTH_TOKEN), tasks (REDIS_URL)
Oracle 'QiForge Example Oracle' (runtime v0.X.Y) listening on :3000
[plugin] matrix pending → loaded

The excluded plugins are normal — they need env vars you didn't set. To enable one, fill its env vars and restart.

Step 5 — hit a public plugin endpoint

The Weather plugin exposes GET /weather/now. It's marked auth-excluded via getAuthExcludedRoutes(), so no UCAN header needed:

curl 'http://localhost:3000/weather/now?city=Berlin'

You should get back JSON:

{
  "ok": true,
  "city": "Berlin",
  "temp_c": 14.2,
  "units": "celsius",
  "conditions": "Slight rain",
  "latitude": 52.52,
  "longitude": 13.405
}

This proves: the plugin loaded, its Nest module mounted, the auth exclusion worked, and the upstream call to Open-Meteo succeeded.

Step 6 — send a chat message

For interactive chat, use the CLI's streaming TUI:

qiforge-cli --chat

Then type:

what's the weather in Berlin?

The agent walks this path:

  1. list_capabilities — sees weather is on-demand, not loaded yet.
  2. load_capability({ names: ['weather'] }) — gets the manifest back with whenToUse and the tool list. loadedPlugins state field now contains 'weather'.
  3. get_current_weather({ city: 'Berlin' }) — the actual tool fires.
  4. Final response — natural-language summary of the result.

Watch the server logs at the same time — you'll see the weather middleware print model call started / model call complete (Xms) for each LLM step.

What you just verified

You sawWhat it proves
Boot log listing pluginsLoader + topo sort + manifest validation worked.
excluded plugins: line with reasonsEnv-driven opt-in (autoDetect) is wired correctly.
GET /weather/now returning JSONPlugin Nest modules mount + auth exclusions apply.
Agent calling load_capability then get_current_weatherDynamic discovery and loadedPlugins state work end-to-end.
Middleware logs around each model callgetMiddlewares hooks are firing.

Where to go next

Troubleshooting

SymptomLikely fix
[boot-error] Plugin '<name>' env validation failed for '<X>'Either set the env var or disable the plugin with features: { name: false }.
Boot hangs after Nest application startedMatrix init is still pending. Check MATRIX_BASE_URL and MATRIX_ORACLE_ADMIN_* env vars.
All authenticated requests return 401UCAN signing mnemonic not loaded — see Identity and auth.
The agent doesn't call my plugin's toolCheck it's loaded in app.plugins.status(). If it's on-demand, prompt the agent more clearly so it calls load_capability.

Full coverage: Troubleshooting.