From 66d28df948ed048d12479befdbd3b234c4bf0e06 Mon Sep 17 00:00:00 2001 From: David Wong Date: Fri, 2 Oct 2026 14:50:05 +0300 Subject: [PATCH] =?UTF-8?q?feat(guide):=20memory=5Fsave=20=E6=8C=87?= =?UTF-8?q?=E5=BC=95=E8=A1=A5=20scope=20=E5=A3=B0=E6=98=8E=E8=A7=84?= =?UTF-8?q?=E5=88=99=EF=BC=88#249=20=E7=AC=AC=E4=BA=8C=E6=89=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TOOL_GUIDE.memory_save 此前只讲「该不该写」,没讲「写给谁看」。补一句判断规则:只在 记忆只对某个 workspace/agent 成立时才声明 workspace_scope / agent_scope,否则两个 都别填;未标注的处处可见,标了 scope 的离开他 scope 时降权或过滤。 措辞按实际行为写:A2 软隔离是他 scope 降权 x0.5 但保留可见,只有 A3 strictScope (默认关)才硬过滤。初稿里的「静默消失」与默认配置下的行为不符,也与「拒绝可解释、 不静默丢弃」的口径(#254 验收第 2 条)冲突,已改为 filtered or downranked;测试除 原有两条断言外,另加一条反向锁:描述里不得出现 silently disappears。 落在工具描述而不是总则:它是单工具的判据,总则那五条讲的是「何时查 / 何时写 / 何时 no-op」,加第六条会把单工具语义抬成全局纪律。测试同时锁两件事——这句话在 工具描述里、且不落在总则段。 --- dsh-mneme/CHANGELOG.md | 6 ++++++ dsh-mneme/lib/guide.js | 14 +++++++++++++- dsh-mneme/src/guide.js | 14 +++++++++++++- dsh-mneme/test/inject-pools.test.js | 19 +++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/dsh-mneme/CHANGELOG.md b/dsh-mneme/CHANGELOG.md index 34fdc0d4..6bea006d 100644 --- a/dsh-mneme/CHANGELOG.md +++ b/dsh-mneme/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +## 🆕 新增 + +- **能力说明补 scope 声明规则(`memory_save` 工具描述)**:`memory_save` 的 `workspace_scope` / `agent_scope` 是必填面,此前的指引只讲「该不该写」,没讲「写给谁看」。现在在 `memory_save` 的工具描述尾部补一条判断规则:只在记忆确实只属于一个 workspace / 一个 agent 时才标注,否则两个都留空(未标注 = 处处可见,是安全的默认)。写进工具描述而不是总则——它是单工具的判据,总则那五条讲的是「何时查 / 何时写 / 何时 no-op」,加第六条会把单工具语义抬成全局纪律。#249 第二批;注入时机(N0 能力说明 / N1 分池 / N2 尾声提醒)未动工。 + ## [0.8.12] - 2026-10-01 ## 🐛 修复 diff --git a/dsh-mneme/lib/guide.js b/dsh-mneme/lib/guide.js index 8bfd616f..63f1221f 100644 --- a/dsh-mneme/lib/guide.js +++ b/dsh-mneme/lib/guide.js @@ -38,9 +38,21 @@ export const TOOL_GUIDE = { " Use this when the task depends on earlier decisions, preferences, or project history that is not already in context, " + "or to look for a newer memory behind one that seems stale. " + "Skip it for facts you can read directly from the repository.", + // 第二句(#249 第二批:「能力说明里没提 scope」):TOOL_GUIDE.memory_save 此前只 + // 讲「该不该写」,没讲「写给谁看」——而 memory_save 的 scope 参数是**必填面**, + // 漏填的后果是单向的。所以这里给的是一条判断规则而不是一句免责声明:不确定就别填 + // (未标注 = 处处可见,NULL 恒可见,是安全的默认);标了 scope 才在多 scope 检索里 + // 付出代价。那句代价必须按实写:A2 软隔离是「他 scope 降权 ×0.5 但保留可见」 + // (service.js),只有 A3 `strictScope`(默认关)才真的硬过滤。写「静默消失」既不 + // 符合默认配置下的行为,也撞上「拒绝可解释、不静默丢弃」的口径(#254 验收第 2 条), + // 会让模型以为标注有它实际没有的隐私效果。 + // 写进工具描述而不是总则:它是 memory_save 单工具的判据,总则那五条讲的是 + // 「何时查 / 何时写 / 何时 no-op」,加第六条会把单工具语义抬成全局纪律。 memory_save: " Save only durable, cross-session value (a preference, a decision with its rationale, an engineering constraint, " + - "a pitfall with its root cause). Trivial single-turn work does not belong here, and when unsure, do not save." + "a pitfall with its root cause). Trivial single-turn work does not belong here, and when unsure, do not save. " + + "If a memory only holds for one workspace or one agent, declare workspace_scope / agent_scope; otherwise leave both out. " + + "An unscoped memory is visible everywhere; a scoped one is filtered or downranked outside its scope." }; /** diff --git a/dsh-mneme/src/guide.js b/dsh-mneme/src/guide.js index 8bfd616f..63f1221f 100644 --- a/dsh-mneme/src/guide.js +++ b/dsh-mneme/src/guide.js @@ -38,9 +38,21 @@ export const TOOL_GUIDE = { " Use this when the task depends on earlier decisions, preferences, or project history that is not already in context, " + "or to look for a newer memory behind one that seems stale. " + "Skip it for facts you can read directly from the repository.", + // 第二句(#249 第二批:「能力说明里没提 scope」):TOOL_GUIDE.memory_save 此前只 + // 讲「该不该写」,没讲「写给谁看」——而 memory_save 的 scope 参数是**必填面**, + // 漏填的后果是单向的。所以这里给的是一条判断规则而不是一句免责声明:不确定就别填 + // (未标注 = 处处可见,NULL 恒可见,是安全的默认);标了 scope 才在多 scope 检索里 + // 付出代价。那句代价必须按实写:A2 软隔离是「他 scope 降权 ×0.5 但保留可见」 + // (service.js),只有 A3 `strictScope`(默认关)才真的硬过滤。写「静默消失」既不 + // 符合默认配置下的行为,也撞上「拒绝可解释、不静默丢弃」的口径(#254 验收第 2 条), + // 会让模型以为标注有它实际没有的隐私效果。 + // 写进工具描述而不是总则:它是 memory_save 单工具的判据,总则那五条讲的是 + // 「何时查 / 何时写 / 何时 no-op」,加第六条会把单工具语义抬成全局纪律。 memory_save: " Save only durable, cross-session value (a preference, a decision with its rationale, an engineering constraint, " + - "a pitfall with its root cause). Trivial single-turn work does not belong here, and when unsure, do not save." + "a pitfall with its root cause). Trivial single-turn work does not belong here, and when unsure, do not save. " + + "If a memory only holds for one workspace or one agent, declare workspace_scope / agent_scope; otherwise leave both out. " + + "An unscoped memory is visible everywhere; a scoped one is filtered or downranked outside its scope." }; /** diff --git a/dsh-mneme/test/inject-pools.test.js b/dsh-mneme/test/inject-pools.test.js index 27b29390..263ebc55 100644 --- a/dsh-mneme/test/inject-pools.test.js +++ b/dsh-mneme/test/inject-pools.test.js @@ -173,3 +173,22 @@ test("#249: capability guide extends only the judgement-heavy tool descriptions" assert.ok(on.get(name).startsWith(off.get(name)), `${name}: guidance is appended, original text untouched`); } }); + +test("#249: memory_save guidance states the scope default in the safe direction", () => { + // #164 口径:scope 参数漏填的后果是单向的——未标注 = 处处可见(NULL 恒可见), + // 而显式 scope 会让记忆在离开他 scope 时被降权(A2 软隔离 ×0.5,保留可见)或直接 + // 过滤(A3 strictScope,默认关)。所以这一句必须同时给出「什么时候标注」与「不确定 + // 就都别填」,只写前半句会把模型推向「能填就填」,正好制造那个单向损失。 + const store = createStore(":memory:"); + const service = createService({ store, mirror: null, config: {} }); + const registered = []; + createTools({ tools: { register(def) { registered.push(def); return () => {}; } } }, service, { injectGuidanceEnabled: true }, null); + const text = registered.find((t) => t.name === "memory_save").description; + assert.ok(text.includes("declare workspace_scope / agent_scope"), "scope declaration rule is present"); + assert.ok(text.includes("otherwise leave both out"), "the no-scope default is stated, not just the declare case"); + // 措辞回归锁:默认配置(strictScope 关)下他 scope 只是降权可见,不是消失。写成 + // 「静默消失」既是错的,也会造出本来不存在的隐私预期。 + assert.ok(!text.includes("silently disappears"), "must not claim a narrowed memory disappears silently"); + // 回归锁:这句话不能只落在总则里(工具描述才是常驻、零注入成本的那个承载位)。 + assert.ok(!MEMORY_GUIDE_SECTION.includes("declare workspace_scope / agent_scope"), "scope rule stays a per-tool rule, not a sixth general rule"); +});