## 要解决的问题 PR #417 按 #399 的约定把蒸馏窗口的原始记录落进了 `distill_records`,并把 `memory → (session_id, seq_start, seq_end)` 的归因写进 `memory_provenance`。但那次只做了写:**没有任何读路径** —— 面板不显示、API 没有端点、`service` 没有查询方法。结果是这批数据只进不出:它占了库、进了回收面,却没人能看见。 具体场景:一条记忆的来源可疑(内容与原文对不上,或需要确认它到底是从哪几句里蒸出来的),现在唯一的办法是直接开 sqlite 手写 JOIN。而 #399 里「我们测不出自己丢了多少」这个目标,恰恰要靠读出口才成立。 ## 期望的方案 给一个最小读出口,两个方向: 1. **按记忆看窗口**:给定 `memory_id`,返回它在 `memory_provenance` 里的区间 `[(session_id, seq_start, seq_end)]`,再按区间取出 `distill_records` 的原文行。 2. **按会话看窗口**:给定 `session_id`,列出该会话所有已留存的窗口(按 seq 升序)。 形态上倾向先走 `GET /api/dsh-mneme/memory/provenance?memoryId=`(面板与外部 API 同一份实现);面板侧挂在哪里由维护者定。`distillRecords.enabled=false` 时回 **409 `distill-records-disabled`**,依附既有开关、不新增开关。(**2026-10-29 更正**:本句原文是「按既有习惯返回空或 404」。#509 里 z2Ace0107 指出 404 不成立——本端点设计成永远不出 404(缺 id 是 400、未知 id 是 200 + 空 windows,读端点不替调用方判断记忆是否存在),而状态码是唯一被代理/监控/重试逻辑读取的通道,把「功能关了」写成 404 等于让它读成「这条记忆不存在」。仓里同型先例是 `src/api.js:525-526`:`409 disabled`(用户没开)与 `503 unavailable`(装配缺失)必须是两个可区分的答案,`src/index.js:589-591` 把这句话写死在注释里。判断归我,原文措辞留在这里以免读者以为规格一直是这样。)上面两条是形态建议,不是实现要求。 ## 考虑过的替代方案 - 直接暴露 `distill_records` 全表(连 `text` 一起):数据面积与敏感度最大,且没有解决「从记忆回去」这个真实入口,先不做。 - 面板只显示条数、不返回原文:判断不了「原文里到底有没有这句话」,等于没解决 #399 的原始目标。 ## 实现口径确认 / Implementation gates - [x] 新行为开关 opt-in 默认关(本提案依附既有 `distillRecords.enabled`,不新增开关) - [x] 无 schema 变更(只读既有两张表,无需迁移) - [x] 接受带回归测试的验收线 ## 披露 / Disclosure - [x] 本提案由 AI 辅助整理(人工审核后提交)
Activity
modusensus commentedon Oct 20, 2026
现状(2026-10-21 对照
main实测)读方法已经有了,但没有 HTTP 出口——差最后一段接线。
dsh-mneme/src/service.js:2600:provenanceFor: (memoryId) => store.provenanceFor(memoryId)—— 方法存在(随 [feature] 蒸馏窗口原始记录留存 + 记忆级溯源:把蒸馏从「替换」降级为「叠加索引」 #399 落地,写+读都有)。dsh-mneme/src/api.js:memory/provenance路由 0 命中;distill_records在api.js里 0 命中。所以现在「这条记忆是哪次蒸馏带进来的」在库里有答案、在代码里能查,但外部(面板、standalone API、脚本)拿不到。
还缺什么
api.js与api-standalone.js各加一条只读路由(建议GET /api/dsh-mneme/memory/provenance?id=<id>),响应至少含distill_run_id/run_type/created_at/trigger_source;dream写入的记忆断言distill_run_id非空,对一条memory_save写入的断言为null(而不是报错)。接手建议
这是纯出口的活:
service.js不用改,只加路由 + 面板一行。没有设计分歧,可以直接提 PR。z2Ace0107 commentedon Oct 23, 2026
想接这条。计划分两步,先做后端:
src/api.js与src/api-standalone.js各加一条只读路由GET .../memory/provenance?id=<id>,响应含distill_run_id/run_type/created_at/trigger_source。service.js不用改——provenanceFor已经在:2600;dream写入的记忆断言distill_run_id非空,对memory_save写入的断言为null(而不是报错)。distillRecords.enabled=false时按既有习惯返回空,不新增开关。面板「记忆详情」那一行我想先不动——
lib/client.js是单写者资源,我不想和面板线撞车。如果你希望一起做,我可以在 PR 里留好位置,等你安排面板侧写入者时再补。我同时认了 #461 和 #383,按由易到难串行做(#461 → #383 → #421),两周内都交 PR。
refactor(api): provenance 读出口改用 token 门控并抽共用模块(slow-stack#509 评审)
docs(changelog): 给溯源读出口补 Unreleased 条目(slow-stack#421 / slow-stack#509)
feat(api): distill_records 补 HTTP 读出口,记忆溯源可查(#421)
Merge pull request #509 from z2Ace0107/codex/issue-421-distill-record…
modusensus commentedon Oct 29, 2026
方向 1 已由 #509 落地并合入 main(
6a05618):GET /api/dsh-mneme/memory/provenance?memoryId=,两张皮共用同一份src/provenance-http.js,状态码阶梯401 → 400 缺 id → 409 distill-records-disabled → 503 provenance-unavailable → 200,依附既有distillRecords.enabled、不新增开关,判据与写侧src/summarize.js:988逐字同源并有静态锁。实现与其中最主要的判断(409 而非 404)由 @z2Ace0107 在 #509 完成。本条正文那句「按既有习惯返回空或 404」已按 #509 的论证原地更正为 409,原措辞留在括号里,读者能看到规格是怎么走到这一步的。
方向 2(按
session_id列那一会话的窗口)拆到 #524 —— issue 当时写明那是形态建议不是实现要求,所以本条按「方向 1 已交付」关闭,不把没做的格子算成已兑现。feat(provenance): 溯源读出口补上方向 2——按 session_id 列该会话的蒸馏窗口(slow-stack#524)