diff --git a/examples/claude-code-memory-plugin/scripts/shared/config-schema.mjs b/examples/claude-code-memory-plugin/scripts/shared/config-schema.mjs index e84b0f3dc4..031b659f39 100644 --- a/examples/claude-code-memory-plugin/scripts/shared/config-schema.mjs +++ b/examples/claude-code-memory-plugin/scripts/shared/config-schema.mjs @@ -93,6 +93,17 @@ export const KNOBS = [ { name: "logRankingDetails", type: "bool", default: false, env: "OPENVIKING_LOG_RANKING_DETAILS", capability: "recall" }, { name: "recallLedger", type: "bool", default: true, env: "OPENVIKING_RECALL_LEDGER", capability: "recall" }, { name: "recallQueryFilters", type: "list", default: [], env: "OPENVIKING_RECALL_QUERY_FILTERS", capability: "recall" }, + // Subtree URIs the recall search must never return. Without this there is no + // way to keep generated directory files (viking://user//skills, + // viking://user//resources, viking://agent/skills) out of the hits: + // they match like ordinary content and carry only boilerplate text. + { + name: "recallExcludeUris", + type: "list", + default: [], + env: "OPENVIKING_RECALL_EXCLUDE_URIS", + capability: "recall", + }, // Digest compression. Claude Code reads this as the tri-state // off/client/server/auto through `normalizeRewriteMode`; Codex reads the same diff --git a/examples/codex-memory-plugin/scripts/shared/config-schema.mjs b/examples/codex-memory-plugin/scripts/shared/config-schema.mjs index e84b0f3dc4..031b659f39 100644 --- a/examples/codex-memory-plugin/scripts/shared/config-schema.mjs +++ b/examples/codex-memory-plugin/scripts/shared/config-schema.mjs @@ -93,6 +93,17 @@ export const KNOBS = [ { name: "logRankingDetails", type: "bool", default: false, env: "OPENVIKING_LOG_RANKING_DETAILS", capability: "recall" }, { name: "recallLedger", type: "bool", default: true, env: "OPENVIKING_RECALL_LEDGER", capability: "recall" }, { name: "recallQueryFilters", type: "list", default: [], env: "OPENVIKING_RECALL_QUERY_FILTERS", capability: "recall" }, + // Subtree URIs the recall search must never return. Without this there is no + // way to keep generated directory files (viking://user//skills, + // viking://user//resources, viking://agent/skills) out of the hits: + // they match like ordinary content and carry only boilerplate text. + { + name: "recallExcludeUris", + type: "list", + default: [], + env: "OPENVIKING_RECALL_EXCLUDE_URIS", + capability: "recall", + }, // Digest compression. Claude Code reads this as the tri-state // off/client/server/auto through `normalizeRewriteMode`; Codex reads the same diff --git a/examples/dsh-memory-plugin/runtime.mjs b/examples/dsh-memory-plugin/runtime.mjs index 892264f250..64847441aa 100644 --- a/examples/dsh-memory-plugin/runtime.mjs +++ b/examples/dsh-memory-plugin/runtime.mjs @@ -139,6 +139,7 @@ export class OpenVikingRuntime { actorPeerId: state.config.peerId, legacyPeerId: state.config.legacyPeerId, sessionId: state.ovSessionId, + excludeUris: state.config.recallExcludeUris, log: (stage, data) => this.log(stage, data), }, ); diff --git a/examples/dsh-memory-plugin/runtime.test.mjs b/examples/dsh-memory-plugin/runtime.test.mjs index 6d3726f8c9..601633628b 100644 --- a/examples/dsh-memory-plugin/runtime.test.mjs +++ b/examples/dsh-memory-plugin/runtime.test.mjs @@ -366,6 +366,53 @@ test("autoRecall false stops the recall request", async () => { assert.equal(await runtime.recallMessage({}, [{ role: "user", content: "what did we decide" }]), null); }); +// recall-core reads options.excludeUris, but the DSH runtime built its options +// without it, so nothing a user configured could stop a subtree from being +// recalled: generated directory files came back as ordinary hits. +test("recallExcludeUris reaches the search request", async () => { + const bodies = []; + const runtime = new OpenVikingRuntime({ + async fetchJSON(path, init) { + if (/\/search\/search$/.test(path)) bodies.push(JSON.parse(init.body)); + return { + ok: true, + result: { + context: "\nrecalled\n", + stats: {}, + }, + }; + }, + }, { ...config(), recallExcludeUris: ["viking://user/default/skills", "viking://agent/skills"] }, { debug() {} }); + runtime.initialize = async () => ({ + ready: true, + config: { ...config(), recallExcludeUris: ["viking://user/default/skills", "viking://agent/skills"] }, + }); + + await runtime.recallMessage({}, [{ role: "user", content: "what did we decide" }]); + + assert.equal(bodies.length, 1); + assert.deepEqual(bodies[0].exclude_uris, ["viking://user/default/skills", "viking://agent/skills"]); +}); + +test("recall sends no exclude_uris when recallExcludeUris is unset", async () => { + const bodies = []; + const runtime = new OpenVikingRuntime({ + async fetchJSON(path, init) { + if (/\/search\/search$/.test(path)) bodies.push(JSON.parse(init.body)); + return { + ok: true, + result: { context: "\nrecalled\n", stats: {} }, + }; + }, + }, config(), { debug() {} }); + runtime.initialize = async () => ({ ready: true, config: config() }); + + await runtime.recallMessage({}, [{ role: "user", content: "what did we decide" }]); + + assert.equal(bodies.length, 1); + assert.equal("exclude_uris" in bodies[0], false); +}); + test("syncTurns false sends nothing: no capture, no commit, no dispose flush, no replay", async () => { const pendingDir = await mkdtemp(join(tmpdir(), "dsh-memory-sync-off-")); tempDirs.push(pendingDir); diff --git a/examples/memory-plugin-shared/lib/config-schema.mjs b/examples/memory-plugin-shared/lib/config-schema.mjs index 156f90895b..9eea92f6b8 100644 --- a/examples/memory-plugin-shared/lib/config-schema.mjs +++ b/examples/memory-plugin-shared/lib/config-schema.mjs @@ -92,6 +92,17 @@ export const KNOBS = [ { name: "logRankingDetails", type: "bool", default: false, env: "OPENVIKING_LOG_RANKING_DETAILS", capability: "recall" }, { name: "recallLedger", type: "bool", default: true, env: "OPENVIKING_RECALL_LEDGER", capability: "recall" }, { name: "recallQueryFilters", type: "list", default: [], env: "OPENVIKING_RECALL_QUERY_FILTERS", capability: "recall" }, + // Subtree URIs the recall search must never return. Without this there is no + // way to keep generated directory files (viking://user//skills, + // viking://user//resources, viking://agent/skills) out of the hits: + // they match like ordinary content and carry only boilerplate text. + { + name: "recallExcludeUris", + type: "list", + default: [], + env: "OPENVIKING_RECALL_EXCLUDE_URIS", + capability: "recall", + }, // Digest compression. Claude Code reads this as the tri-state // off/client/server/auto through `normalizeRewriteMode`; Codex reads the same