Skip to content

[feature] 蒸馏窗口原始记录留存 + 记忆级溯源:把蒸馏从「替换」降级为「叠加索引」 #399

Closed

Description

@modusensus

问题

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 里写一个做不到的承诺。按会话/工作区裁剪是真实可达、也是唯一诚实的粒度。

已知边界(必须写进域文档「已知坑」)

  1. 无历史回填:该性质从启用当天起成立,不覆盖启用之前的会话。
  2. 从未蒸馏过的窗口不留记录:window-too-small 已消费游标、插件关闭、未触发。缺口可枚举,就要枚举出来。
  3. 不覆盖工具结果与 code 输出。

测试锁

  1. 幂等:同窗口蒸馏两次,records 不翻倍。
  2. 隐私过滤(最该锁):reasoning 块 / AGENTS.md 正文 / skill 正文 / 文件变更通知绝不落盘。
  3. 未截断:超长用户提示在记录里是全文,而喂 LLM 的渲染仍受约束 —— 两条路径的差异要显式测,否则以后有人"顺手统一"就把留存悄悄降级了。
  4. 裁剪不毁资产:purge 后链接悬空、记忆仍在。
  5. enabled=false 时零写入(默认关要行为等价于今天)。
  6. 新表进 reclaim 的 dry-run 报告(maintenance.js:82 三参语义不动,report.cleared 多一项)。

非目标(本刀明确不做)

不嵌入、不进 fuseRecall、不进注入、不做取代判定、不做 value histories 查询、不动那 8000/300/500 三刀(它们保护 LLM 上下文成本,与留存无关)。第一步只买"原文不再消失"这一个性质。

依据与相关

Activity

  1. modusensus commented on Oct 12, 2026

    @modusensus
    MemberAuthor

    这条的主体已经落地,关掉;没做的那部分另开 #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,所以它合了之后这条还开着 —— 不是漏做,是正文漏了引用。

  2. added a commit that references this issue on Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions