Copy-paste recipe
The whole API is afeatures map handed to createOracleApp. Each key is a bundled plugin name, each value is true / false / 'auto'.
BUNDLED_PLUGINS; features only controls which ones survive resolution. Reference oracle: apps/qiforge-example/src/main.ts.
How resolution works
1
The runtime starts with the bundled list
BUNDLED_PLUGINS is a fixed 14-plugin tuple — memory, portal, firecrawl, domain-indexer, composio, sandbox, skills, editor, agui, tasks, user-preferences, matrix-group-chats, vfs, oracle-payments.You do not import or instantiate the plugins you want at defaults — they are already there. credits, slack, calls, and flows are not in the tuple: features never reaches them, so you construct each one and pass it in plugins.2
Each plugin gets a feature decision
3
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).4
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.What every bundled plugin does by default
Each plugin’sautoDetect predicate decides whether to opt in when you leave it on 'auto'.
Full per-plugin env vars: plugin catalog and environment variables reference.
Opt out of a plugin
1
Set the feature flag to false
autoDetect runs. Its configSchema is also removed from the merged env schema, so its env vars become optional.2
Check for dependents
If another loaded plugin lists the disabled plugin in its
dependsOn, boot fails with a boot.plugin.dep_missing error naming both. Disable the dependent too, or keep the dependency loaded.Force a plugin on
1
Set the feature flag to true
autoDetect would skip it.2
Set every env var the plugin needs
With See the plugin’s page in the plugin catalog for its full env requirements.
features: { composio: true } and no COMPOSIO_API_KEY, the runtime throws at boot:Plugins that are not bundled
credits, slack, calls, and flows ship inside @ixo/oracle-runtime but are absent from BUNDLED_PLUGINS, so features never reaches them. Load one by constructing it and passing it in plugins — anything in that array loads unconditionally.
The same array is how you replace a bundled plugin with a custom-constructed instance (the loader dedupes by name, and yours wins). editor is the usual case: the bundled instance builds its own Matrix client from the MATRIX_* admin env vars, so pass matrixClient only to reuse a client your app already keeps synced.
credits without a Redis client loads in pass-through mode and skips the settlement cron. Since it is also where LLM token metering lives, an oracle that never wires it has no metering at all.Retune a bundled plugin’s manifest
features decides whether a bundled plugin loads. To change how it’s advertised — most usefully its visibility tier, but also summary, tags, or whenToUse — use manifestOverrides instead of forking the plugin. The override is shallow-merged onto the plugin’s own manifest at boot, validated like any authored manifest, and seen by every downstream reader (Tier-1 prompt, list_capabilities / load_capability, visibility index).
boot.plugin.manifest_override_unknown) and ignored — so disabling a plugin via features and leaving its override entry behind is safe.
Inspect what loaded
1
Read app.plugins.status() after boot
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 }.)Where to read next
Plugin catalog
Every bundled plugin in detail.
Environment variables
Core vars plus per-plugin vars.
createOracleApp reference
Every option, exhaustively.
Write a plugin
Build your own next to the bundled set.