diff --git a/build-an-oracle/develop/enable-bundled-plugins.mdx b/build-an-oracle/develop/enable-bundled-plugins.mdx index 9178c26..3879966 100644 --- a/build-an-oracle/develop/enable-bundled-plugins.mdx +++ b/build-an-oracle/develop/enable-bundled-plugins.mdx @@ -1,6 +1,6 @@ --- title: "Enable bundled plugins" -description: "Toggle the 15 bundled plugins via the features map. Opt out, force on, or let auto-detect handle it." +description: "Toggle the 15 bundled QiForge plugins via the features map — opt out, force on, or let auto-detect handle it — and retune their manifests." icon: "boxes-stacked" --- @@ -167,6 +167,24 @@ Live example: [apps/qiforge-example/src/main.ts](https://github.com/ixoworld/ixo The bundled `editorPlugin` and `creditsPlugin` instances boot in stub form (for testing). For production behaviour, instantiate them yourself and pass the live objects in. +## Retune a bundled plugin's manifest + +`features` decides **whether** a bundled plugin loads. To change **how it's advertised** — most usefully its [visibility tier](/build-an-oracle/understand/visibility-tiers), but also `summary`, `tags`, or `whenToUse` — use [`manifestOverrides`](/build-an-oracle/reference/createoracleapp#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). + +```ts +const app = await createOracleApp({ + config, + manifestOverrides: { + // Take a noisy `always` bundled plugin out of the Tier-1 prompt. + 'domain-indexer': { visibility: 'on-demand' }, + // Hide a transport plugin entirely; its tools still bind. + portal: { visibility: 'silent' }, + }, +}); +``` + +Override keys that don't match a loaded plugin are logged (`boot.plugin.manifest_override_unknown`) and ignored — so disabling a plugin via `features` and leaving its override entry behind is safe. + ## Inspect what loaded diff --git a/build-an-oracle/reference/createoracleapp.mdx b/build-an-oracle/reference/createoracleapp.mdx index 8ce74b6..9c8f9a8 100644 --- a/build-an-oracle/reference/createoracleapp.mdx +++ b/build-an-oracle/reference/createoracleapp.mdx @@ -20,6 +20,7 @@ import { createOracleApp } from '@ixo/oracle-runtime'; export interface CreateOracleAppOptions { config: OracleConfig; features?: Partial>; + manifestOverrides?: Partial>; plugins?: OraclePlugin[]; nestModules?: Array; authExcludedRoutes?: AuthExcludedRoute[]; @@ -139,6 +140,63 @@ export interface CreateOracleAppOptions { ``` + + - **Type:** `Partial>` where `PluginManifestOverride = Partial` + - **Required:** no + - **Added in:** `@ixo/oracle-runtime` 1.2.0 + + Per-plugin overrides for fields on a loaded plugin's [manifest](/build-an-oracle/reference/manifest-schema). Use this to retune a bundled plugin's discovery — most commonly its [`visibility`](/build-an-oracle/understand/visibility-tiers) — **without forking the plugin source**. Typical uses: flip a noisy `always` plugin to `on-demand`, hide one behind `silent`, or relabel its `summary` / `tags` / `whenToUse`. + + Keys are plugin **names** (the same kebab-case identifiers used by [`features`](#features), e.g. `'domain-indexer'`). Values are a `Partial` — set only the fields you want to change. + + ```ts + import { createOracleApp } from '@ixo/oracle-runtime'; + + const app = await createOracleApp({ + config, + manifestOverrides: { + // Drop a noisy bundled plugin out of the Tier-1 prompt. + 'domain-indexer': { visibility: 'on-demand' }, + // Hide a transport plugin entirely; its tools still bind. + portal: { visibility: 'silent' }, + // Sharpen the trigger phrases the agent sees for memory. + memory: { + summary: 'Long-term user memory store.', + whenToUse: ['When the user refers to a past conversation or stored preference.'], + }, + }, + }); + ``` + + **Merge semantics** + + Each override is **shallow-merged** onto the plugin's own manifest before validation and registration: + + - Keys you set win; keys you omit keep the plugin default. + - `undefined` values are ignored — a sparse override never blanks out a field. + - Arrays and nested objects are replaced wholesale (not deep-merged). For example, supplying `whenToUse: [...]` **replaces** the plugin's full list rather than appending to it. + + The merged manifest is what the runtime validates and what every downstream reader sees — the Tier-1 prompt block, the `list_capabilities` / `load_capability` meta-tools, and the agent's visibility index — so an override is the single source of truth from boot onwards. + + **Validation** + + Because the merged manifest is run through the same `validateManifest` rules as an authored one (see [Manifest schema](/build-an-oracle/reference/manifest-schema)), an override is held to the same constraints. For example, an override that empties `whenToUse` on a non-silent plugin fails boot with a clear `Plugin manifest validation failed` error. + + **Unknown keys** + + Override keys that don't match a loaded plugin (e.g. the plugin is excluded by `features` or simply misspelt) are **logged and ignored** — they do not fail boot: + + ```text + [boot] manifestOverrides references 'composio', which is not a loaded plugin — ignored (event: boot.plugin.manifest_override_unknown) + ``` + + Watch for that event when a retune doesn't appear to take effect. + + + The same option is available on [`createTestRuntime`](/build-an-oracle/develop/test-your-oracle) with identical shape and semantics, so a test can exercise a plugin under a retuned visibility (e.g. forcing `silent`) without a bespoke fixture. + + + - **Type:** `OraclePlugin[]` - **Required:** no @@ -435,7 +493,7 @@ The function executes these steps in order: 1. Validates `config` (`name` required). 2. Resolves plugin set: bundled + your plugins, deduped by `name`, with `features` toggles and `autoDetect` predicates applied. Excluded plugins surface in `plugins.status().excluded` with their reason. 3. Topologically sorts by `dependsOn`. Cycles or missing hard deps fail boot. -4. Validates every plugin's manifest. Hard violations fail boot. +4. Shallow-merges any `manifestOverrides` onto each loaded plugin's manifest, then validates the **merged** manifest. Hard violations fail boot. Override keys that don't match a loaded plugin are logged (`boot.plugin.manifest_override_unknown`) and ignored. 5. Composes env schema (base + all loaded plugins' `configSchema`) and validates `process.env`. Missing required vars fail boot with `[boot-error] Plugin '' env validation failed for ''`. 6. Cross-field check: the API key for the selected `LLM_PROVIDER` is present. (Schema-merge can't express this.) 7. Builds the internal `OracleIdentity` from `config` + validated env. diff --git a/build-an-oracle/reference/manifest-schema.mdx b/build-an-oracle/reference/manifest-schema.mdx index 3c869a7..1b38348 100644 --- a/build-an-oracle/reference/manifest-schema.mdx +++ b/build-an-oracle/reference/manifest-schema.mdx @@ -217,8 +217,13 @@ const manifest: PluginManifest = { }; ``` +## Retuning a bundled plugin's manifest + +You don't have to fork a bundled plugin to change its manifest. `createOracleApp` accepts a [`manifestOverrides`](/build-an-oracle/reference/createoracleapp#manifestoverrides) option that shallow-merges fork-supplied fields onto each loaded plugin's manifest before validation and registration — so the override is what the Tier-1 prompt, the meta-tools, and the visibility index all see. This is the supported way to flip a bundled plugin's `visibility` (e.g. drop a noisy `always` plugin to `on-demand`) or relabel its `summary` / `tags` / `whenToUse` without touching the plugin source. + ## Related references - [Manifest concept](/build-an-oracle/understand/manifest) - [Visibility tiers](/build-an-oracle/develop/plugin-recipes/set-visibility) - [Plugin API](/build-an-oracle/reference/plugin-api) +- [`manifestOverrides` on `createOracleApp`](/build-an-oracle/reference/createoracleapp#manifestoverrides)