Skip to content

Commit fe48020

Browse files
authored
Merge branch 'main' into feat/231-organize
2 parents 86111c4 + 44cbf63 commit fe48020

9 files changed

Lines changed: 97 additions & 9 deletions

File tree

‎dsh-mneme/bin/dsh-mneme-mcp.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ const TOOL_SAVE_DESCRIPTION =
155155
"Call this when the user states a durable preference, a project decision is made, or a lesson is learned. " +
156156
"Merges into an existing entry of the same type when the title matches.";
157157
const TOOL_SEARCH_DESCRIPTION =
158-
"Search the cross-session memory store. Use when you need past context: how a problem was solved, user preferences, project decisions. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps.";
158+
"Search the cross-session memory store for past context (solved problems, user preferences, project decisions). IMPORTANT: relevant cross-session memories are already injected into your context every turn — only call this when the injected memory block does NOT contain what you need, or you need to look something specific up. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps.";
159159
const TOOL_LIST_DESCRIPTION =
160160
"List memory entries by type, high-importance first, then newest, paginated. Set include_archived=true to also list archived (hidden) entries so they can be located and restored.";
161161
const TOOL_GET_DESCRIPTION =

‎dsh-mneme/lib/config.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,17 @@ export const Config = z.object({
550550
// recall_runs 滚动清理保留天数。
551551
recallRetentionDays: z.natural().min(1).max(3650).default(90),
552552

553+
// --- tool exposure: 慢模型/轻量模型的工具往返节流(v0.8.6)----------------
554+
// 跨会话记忆已由 inject.js 每轮自动注入系统提示词,memory_search 只用于
555+
// 「注入里没有、需要深挖」的补充检索;memory_archive 是隐藏/恢复条目的整理
556+
// 操作,正常会话里很少需要。轻量模型对「何时该用工具」判断弱,容易每轮
557+
// 顺手调一遍——每次工具调用都是一次串行往返(生成参数→执行→回填→再生成),
558+
// 在慢模型上会被放大成明显卡顿。这两个开关允许直接隐藏对应工具(默认全
559+
// 开=行为不变),隐藏后模型根本看不到它,也就不会调。也走 feature_flags
560+
// 白名单,面板可启停=线上回滚开关。
561+
disableMemorySearch: z.boolean().default(false),
562+
disableMemoryArchive: z.boolean().default(false),
563+
553564
// --- scope: v0.8.0 A1 存储层(issue #17)--------------------------------
554565
// 总开关默认关:关闭时写入不标注 scope、去重维持 (type, title) 现状,行为
555566
// 逐字节不变。开启后 memory_save 写入 agent_scope(session header 的

‎dsh-mneme/lib/settings.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ const FEATURE_FLAG_BOOLEANS = [
8585
// Issue #17(v0.8.0 A3):strictScope 硬过滤——他 scope 完全不可见(关闭时
8686
// 为 A2 软隔离:降权保留可见)。
8787
"strictScope",
88+
// 工具暴露开关(v0.8.6):记忆已每轮自动注入,memory_search/memory_archive
89+
// 在慢/轻量模型上是多余往返,面板可关(默认关=行为不变)。
90+
"disableMemorySearch",
91+
"disableMemoryArchive",
8892
// 嵌套对象开关:config.js 里是 memoryQualityFilter / llmAudit 对象的 enabled
8993
// 子字段。kv 按点号键平铺存("memoryQualityFilter.enabled": false),index.js
9094
// 合并时展开回嵌套对象,api.js 的 effective 从对象子字段取值。

‎dsh-mneme/lib/tools.js‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export function createTools(ctx, service, config, embedder) {
181181
name: "memory_search",
182182
description: withToolGuide(
183183
"memory_search",
184-
"Search the cross-session memory store. Use when you need past context: how a problem was solved, user preferences, project decisions. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps."
184+
"Search the cross-session memory store for past context (solved problems, user preferences, project decisions). IMPORTANT: relevant cross-session memories are already injected into your context every turn — only call this when the injected memory block does NOT contain what you need, or you need to look something specific up. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps."
185185
),
186186
// A2 检索接线:occurred 时间窗 + 会话 scope 加成(见 execute)。
187187
parameters: {
@@ -435,7 +435,8 @@ export function createTools(ctx, service, config, embedder) {
435435
description:
436436
"Archive a memory (hide it from active lists, search, injection and dream consolidation) or restore it. " +
437437
"Archived entries stay in storage and are recoverable: pass archived=false to restore, and use memory_list with " +
438-
"include_archived=true to find archived entries.",
438+
"include_archived=true to find archived entries. " +
439+
"Only archive when the user asks, or an entry is clearly stale/no longer relevant — do not archive proactively mid-session to tidy up.",
439440
parameters: {
440441
id: { type: "string", required: true, description: "Memory id" },
441442
archived: { type: "boolean", description: "Archive (true, default) or restore (false) the entry" }
@@ -676,8 +677,20 @@ export function createTools(ctx, service, config, embedder) {
676677
})
677678
];
678679

680+
// Tool-exposure gate (v0.8.6): 跨会话记忆已自动注入,memory_search /
681+
// memory_archive 在慢/轻量模型上是多余往返,面板开关可直接隐藏工具——
682+
// 模型看不到就不会调,比在描述里劝它少调用更可靠。注意:live patch reload
683+
// 下已注册的工具不会被宿主反注册,排除只对全新会话生效。
684+
const hiddenTools = new Set();
685+
if (config?.disableMemorySearch === true) hiddenTools.add("memory_search");
686+
if (config?.disableMemoryArchive === true) hiddenTools.add("memory_archive");
687+
679688
for (const tool of tools) {
680-
if (registeredTools.has(tool.name)) {
689+
if (hiddenTools.has(tool.name)) {
690+
ctx.logger?.info?.(`[dsh-mneme] tool "${tool.name}" hidden by config (disableMemorySearch/disableMemoryArchive)`);
691+
continue;
692+
}
693+
if (registeredTools.has(tool.name)) {
681694
ctx.logger?.warn?.(`[dsh-mneme] tool "${tool.name}" already registered, skipping duplicate`);
682695
continue;
683696
}

‎dsh-mneme/src/config.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,17 @@ export const Config = z.object({
550550
// recall_runs 滚动清理保留天数。
551551
recallRetentionDays: z.natural().min(1).max(3650).default(90),
552552

553+
// --- tool exposure: 慢模型/轻量模型的工具往返节流(v0.8.6)----------------
554+
// 跨会话记忆已由 inject.js 每轮自动注入系统提示词,memory_search 只用于
555+
// 「注入里没有、需要深挖」的补充检索;memory_archive 是隐藏/恢复条目的整理
556+
// 操作,正常会话里很少需要。轻量模型对「何时该用工具」判断弱,容易每轮
557+
// 顺手调一遍——每次工具调用都是一次串行往返(生成参数→执行→回填→再生成),
558+
// 在慢模型上会被放大成明显卡顿。这两个开关允许直接隐藏对应工具(默认全
559+
// 开=行为不变),隐藏后模型根本看不到它,也就不会调。也走 feature_flags
560+
// 白名单,面板可启停=线上回滚开关。
561+
disableMemorySearch: z.boolean().default(false),
562+
disableMemoryArchive: z.boolean().default(false),
563+
553564
// --- scope: v0.8.0 A1 存储层(issue #17)--------------------------------
554565
// 总开关默认关:关闭时写入不标注 scope、去重维持 (type, title) 现状,行为
555566
// 逐字节不变。开启后 memory_save 写入 agent_scope(session header 的

‎dsh-mneme/src/settings.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ const FEATURE_FLAG_BOOLEANS = [
8585
// Issue #17(v0.8.0 A3):strictScope 硬过滤——他 scope 完全不可见(关闭时
8686
// 为 A2 软隔离:降权保留可见)。
8787
"strictScope",
88+
// 工具暴露开关(v0.8.6):记忆已每轮自动注入,memory_search/memory_archive
89+
// 在慢/轻量模型上是多余往返,面板可关(默认关=行为不变)。
90+
"disableMemorySearch",
91+
"disableMemoryArchive",
8892
// 嵌套对象开关:config.js 里是 memoryQualityFilter / llmAudit 对象的 enabled
8993
// 子字段。kv 按点号键平铺存("memoryQualityFilter.enabled": false),index.js
9094
// 合并时展开回嵌套对象,api.js 的 effective 从对象子字段取值。

‎dsh-mneme/src/tools.js‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export function createTools(ctx, service, config, embedder) {
181181
name: "memory_search",
182182
description: withToolGuide(
183183
"memory_search",
184-
"Search the cross-session memory store. Use when you need past context: how a problem was solved, user preferences, project decisions. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps."
184+
"Search the cross-session memory store for past context (solved problems, user preferences, project decisions). IMPORTANT: relevant cross-session memories are already injected into your context every turn — only call this when the injected memory block does NOT contain what you need, or you need to look something specific up. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps."
185185
),
186186
// A2 检索接线:occurred 时间窗 + 会话 scope 加成(见 execute)。
187187
parameters: {
@@ -435,7 +435,8 @@ export function createTools(ctx, service, config, embedder) {
435435
description:
436436
"Archive a memory (hide it from active lists, search, injection and dream consolidation) or restore it. " +
437437
"Archived entries stay in storage and are recoverable: pass archived=false to restore, and use memory_list with " +
438-
"include_archived=true to find archived entries.",
438+
"include_archived=true to find archived entries. " +
439+
"Only archive when the user asks, or an entry is clearly stale/no longer relevant — do not archive proactively mid-session to tidy up.",
439440
parameters: {
440441
id: { type: "string", required: true, description: "Memory id" },
441442
archived: { type: "boolean", description: "Archive (true, default) or restore (false) the entry" }
@@ -676,8 +677,20 @@ export function createTools(ctx, service, config, embedder) {
676677
})
677678
];
678679

680+
// Tool-exposure gate (v0.8.6): 跨会话记忆已自动注入,memory_search /
681+
// memory_archive 在慢/轻量模型上是多余往返,面板开关可直接隐藏工具——
682+
// 模型看不到就不会调,比在描述里劝它少调用更可靠。注意:live patch reload
683+
// 下已注册的工具不会被宿主反注册,排除只对全新会话生效。
684+
const hiddenTools = new Set();
685+
if (config?.disableMemorySearch === true) hiddenTools.add("memory_search");
686+
if (config?.disableMemoryArchive === true) hiddenTools.add("memory_archive");
687+
679688
for (const tool of tools) {
680-
if (registeredTools.has(tool.name)) {
689+
if (hiddenTools.has(tool.name)) {
690+
ctx.logger?.info?.(`[dsh-mneme] tool "${tool.name}" hidden by config (disableMemorySearch/disableMemoryArchive)`);
691+
continue;
692+
}
693+
if (registeredTools.has(tool.name)) {
681694
ctx.logger?.warn?.(`[dsh-mneme] tool "${tool.name}" already registered, skipping duplicate`);
682695
continue;
683696
}

‎dsh-mneme/test/api.test.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,8 +663,8 @@ test("GET /api/dsh-mneme/features returns empty overrides and effective config d
663663
// issue #257 新增 sleepMaxTokens、issue #258 新增总览路由两键与
664664
// dreamSummaryMaxInputs、issue #230 新增 documentMemoryEnabled/
665665
// documentInjectBudget、issue #249 第一批新增 injectGuidanceEnabled/
666-
// pinnedInjectBudget)
667-
assert.equal(Object.keys(data.effective).length, 59 + 3 + 2 + 1 + 2 + 2);
666+
// pinnedInjectBudget,v0.8.6 新增 disableMemorySearch/disableMemoryArchive)
667+
assert.equal(Object.keys(data.effective).length, 59 + 3 + 2 + 1 + 2 + 2 + 2);
668668
assert.equal(data.effective.dreamSkipInvalid, true);
669669
assert.equal(data.effective.allowCrossTypeMerge, false);
670670
assert.equal(data.effective.dreamMinIntervalMinutes, 0);

‎dsh-mneme/test/tools.test.js‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,38 @@ test("registers ten tools with correct names", () => {
5454
assert.deepEqual(names, ["memory_archive", "memory_delete", "memory_forget", "memory_get", "memory_list", "memory_register_document", "memory_runtime", "memory_save", "memory_search", "memory_update"]);
5555
});
5656

57+
// v0.8.6 tool-exposure gate: disabling a tool hides it from the model entirely
58+
// (it is not registered), which is the reliable lever against over-calling on
59+
// slow/lightweight models — the injected memory block already covers recall.
60+
test("disableMemorySearch hides only memory_search", () => {
61+
const { registered } = setup({}, { disableMemorySearch: true });
62+
const names = registered.map((t) => t.name).sort();
63+
assert.deepEqual(names, ["memory_archive", "memory_delete", "memory_forget", "memory_get", "memory_list", "memory_register_document", "memory_runtime", "memory_save", "memory_update"]);
64+
});
65+
66+
test("disableMemoryArchive hides only memory_archive", () => {
67+
const { registered } = setup({}, { disableMemoryArchive: true });
68+
const names = registered.map((t) => t.name).sort();
69+
assert.deepEqual(names, ["memory_delete", "memory_forget", "memory_get", "memory_list", "memory_register_document", "memory_runtime", "memory_save", "memory_search", "memory_update"]);
70+
});
71+
72+
test("both disable flags hide both tools", () => {
73+
const { registered } = setup({}, { disableMemorySearch: true, disableMemoryArchive: true });
74+
const names = registered.map((t) => t.name).sort();
75+
assert.deepEqual(names, ["memory_delete", "memory_forget", "memory_get", "memory_list", "memory_register_document", "memory_runtime", "memory_save", "memory_update"]);
76+
});
77+
78+
// Description discipline: with all tools exposed, the descriptions must steer a
79+
// weak model away from calling them every turn.
80+
test("memory_search/archive descriptions carry tool-use discipline", () => {
81+
const { registered } = setup();
82+
const search = registered.find((t) => t.name === "memory_search");
83+
const archive = registered.find((t) => t.name === "memory_archive");
84+
assert.match(search.description, /already injected into your context every turn/i);
85+
assert.match(search.description, /only call this when/i);
86+
assert.match(archive.description, /do not archive proactively mid-session/i);
87+
});
88+
5789
test("compiled schemas pass the enforced DSH subset (defineTool projection)", () => {
5890
const { registered } = setup();
5991
assert.equal(registered.length, 10);

0 commit comments

Comments
 (0)