Repository navigation
[feature] 蒸馏窗口原始记录留存 + 记忆级溯源:把蒸馏从「替换」降级为「叠加索引」 #399
Description
问题
src/summarize.js 已经拿到完整事件流(snapshotEvents(afterSeq, throughSeq),DSH 0.1.2-rc.1 起),也有稳定的事件地址(distill_cursors(session_id, last_seq),store.js:212)。但渲染完就扔:蒸馏之后,库里不存在原始对话的任何副本,溯源只剩一个字符串 source: "session:<id>"(summarize.js:769),没有回源路径。
后果不是"召回率低",是我们测不出自己丢了多少。"这句话在原文里但没进任何记忆"在有转录时是一个可测比例,在没有转录时永远是暗的。C1 冲突裁决、value histories、图形记忆三件事都卡在这个缺口上。
要买到的性质
外部研究把这条讲清楚了:写入时变换 φ(H) 相对 H 只会丢信息(数据处理不等式),但与原始记录并存的旁路索引 X = ψ(H) 不丢信息 —— I(Y; M, H, X) = I(Y; M, H)(arXiv 2609.36059 §3, Eq.1 及其后续说明)。
所以本 issue 不是要改蒸馏,而是让蒸馏不再销毁原料:记录留着当证据,索引留着指路。
方案
两张新表,不动 memories
CREATE TABLE IF NOT EXISTS distill_records (
session_id TEXT NOT NULL,
seq INTEGER NOT NULL,
role TEXT NOT NULL, -- user | assistant | agent(沿用 lang.js 现有三种公开形态)
text TEXT NOT NULL, -- 未截断公开正文
window_id TEXT,
distilled_at TEXT NOT NULL,
PRIMARY KEY (session_id, seq)
);
CREATE TABLE IF NOT EXISTS memory_provenance (
memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
session_id TEXT NOT NULL,
seq_start INTEGER NOT NULL,
seq_end INTEGER NOT NULL,
created_at TEXT NOT NULL
);- 主键
(session_id, seq)是幂等的:summarize.js:509那条"额度耗尽/宿主重启后仍要能蒸到它"的语义会重跑同一窗口,重跑不得产生重复记录。 - 全是
CREATE TABLE IF NOT EXISTS,无 ALTER、无回填 —— 存量库启动即建。 - 归因用区间不用逐事件外键:蒸馏窗口本身是连续 seq 段,窗口级归因第一步够用;逐事件相关性属于后续的取代判定,不在本刀。
写入点:与渲染共用同一个隐私谓词
抽一个纯函数(publicTranscriptPart(event) 一类)返回"这条事件是否公开可存 + 原文",渲染和落盘都吃它。同一个隐私决策在两条路径各写一遍早晚漂移,漂移的方向是"存下来的比 LLM 看过的更多"—— 那是最糟的方向。
落盘文本不受 distillMaxChars / trim(args, 300) / trim(out, 500) / trimTranscript 约束,保留整段。
范围:用户提示 + 助手公开 text 块 + 子会话交付。reasoning 私有推理块照旧永不采集(summarize.js:219-221,红线不动);插件注入的机器内容(AGENTS.md / skill 正文 / 文件变更通知)照旧不进(summarize.js:205-209);工具调用入参与结果、code 输出不落盘 —— 工具结果常常是整段文件内容或命令输出,落盘它会让数据面积与敏感度爆炸,而真正的痛点是"人说的话被丢了"。
时机:蒸馏时写
在 summarize() 已持有事件、已算出窗口边界处顺带落盘,不新建常驻管道。每轮都写能换来记录完整,代价是多一条写管道、并把从未被蒸馏的对话也变成落盘原文。默认蒸馏时写,完整留存若要做另开 issue。
配置与闸门
distillRecords: z.object({
enabled: z.boolean().default(false),
retentionDays: z.natural().min(1).max(3650).default(90)
})照 llmAudit 的既有形状,不发明第二套(config.js:628-631、index.js:224 的 boot purge、store.js:187-188 的口径注释)。
-
settings.js点号键区加"distillRecords.enabled"(与llmAudit.enabled同区,kv 平铺存、index.js合并时展开回嵌套对象);retentionDays进 ranges 映射,不进开关区 - 默认关 + 进
LIGHT_MODE_OFF -
test/api.test.js旗标计数锁同步(实现时先定位那条计数断言,不凭记忆加数) - 启动即清挂在 llmAudit purge 旁边,同型
- 改
src/后npm run sync
删除语义:记录是凭证,记忆是资产
- 到期裁剪只删
distill_records;memory_provenance保留并指向空区间,面板/CLI 显示"来源已过期"。不级联删记忆。 - 删记忆走
ON DELETE CASCADE只清链接,记录保留 —— 一个窗口的凭证不该因删掉其中一条派生记忆而消失。 - 不给
distill_records加sensitivity列。 机密级是记忆级判断,写记录那一刻它还不存在 —— 一个蒸馏窗口里什么都有。加了就是往 schema 里写一个做不到的承诺。按会话/工作区裁剪是真实可达、也是唯一诚实的粒度。
已知边界(必须写进域文档「已知坑」)
- 无历史回填:该性质从启用当天起成立,不覆盖启用之前的会话。
- 从未蒸馏过的窗口不留记录:
window-too-small已消费游标、插件关闭、未触发。缺口可枚举,就要枚举出来。 - 不覆盖工具结果与 code 输出。
测试锁
- 幂等:同窗口蒸馏两次,records 不翻倍。
- 隐私过滤(最该锁):
reasoning块 / AGENTS.md 正文 / skill 正文 / 文件变更通知绝不落盘。 - 未截断:超长用户提示在记录里是全文,而喂 LLM 的渲染仍受约束 —— 两条路径的差异要显式测,否则以后有人"顺手统一"就把留存悄悄降级了。
- 裁剪不毁资产:purge 后链接悬空、记忆仍在。
enabled=false时零写入(默认关要行为等价于今天)。- 新表进
reclaim的 dry-run 报告(maintenance.js:82三参语义不动,report.cleared多一项)。
非目标(本刀明确不做)
不嵌入、不进 fuseRecall、不进注入、不做取代判定、不做 value histories 查询、不动那 8000/300/500 三刀(它们保护 LLM 上下文成本,与留存无关)。第一步只买"原文不再消失"这一个性质。
依据与相关
- arXiv 2609.36059 §3(Eq.1 及旁路索引不丢信息的说明)、§7(留存与删除须同时覆盖两处,本 issue 未解决,已在删除语义里如实划界)
- [proposed bridge contract] Packet → Memory import contract for source-linked knowledge packets #337 Packet → Memory 的 source-linked 导入契约(同为溯源问题,接口应对齐)
- [Feature] 存储生命周期:归档区出口 + 审计表保留(容量治理议题) #275
reclaim无损回收、[Feature] 写入准入(non-write 判定):把「这条该不该进库」前移到 LLM 之前 #254 写入准入、[Feature] heat 热度衰减(D 批次):访问频率调制的记忆升权与自然降温(opt-in) #218 heat
Activity
test(399): reclaim 报告形状锁随 distill_records 纳入回收面而扩展
test(399): reclaim 报告形状锁随 distill_records 纳入回收面而扩展
test(399): reclaim 报告形状锁随 distill_records 纳入回收面而扩展
release: v0.8.16 发布准备
release: v0.8.16 发布准备
modusensus commentedon Oct 12, 2026
这条的主体已经落地,关掉;没做的那部分另开 #421。
已实现(PR #417,已并入 main):
distill_records/memory_provenance两张表、隐私谓词单份、删除语义两分、src/maintenance.js纳入回收面、8 条回归锁;开关按本条约定落在distillRecords.{enabled,retentionDays}(默认关、settings 白名单、进LIGHT_MODE_OFF)。没做的部分(不在 #417 范围内,已另开 #421):只有写入路径,没有读记录的面板/API 出口 —— 记录落盘了但用户看不到、也没法按记忆回查窗口。
顺手报一个事实:PR #417 的正文里没写
Closes #399,所以它合了之后这条还开着 —— 不是漏做,是正文漏了引用。Merge branch 'main' into fix/write-audit-honesty
test(slow-stack#399): 锁住蒸馏留存的完整回路,并修掉一个能把宿主钉死的轮询
fix(dream): 面板暴露 dreamMaxTokens/sleepMaxTokens,超过服务商上限时自动降级