## 动机 社区讨论里有一条值得正面回应的批评:**记忆插件的存储容易退化成"记事本"——写进去容易,被实际复用很难,且用户看不见复用情况**。这条批评在 dsh-mneme 里恰好是可量化的:recall 回执(`recordRecall`,检索场景已落库)、`llm_audit_logs`、`dream_runs` 三处数据都在。缺的不是数据,是把"记忆被用了没有"变成用户与维护者都看得见的信号。 三个用途: 1. **用户侧**:第一次能看见"我的记忆被用上了吗",而不是对着列表猜; 2. **调参侧**:僵尸记忆率是巩固/归档策略的直接依据(哪些该降权、哪些该进巩固候选); 3. **产品侧**:为后续 heat(热度升权)与图谱(结构化关联)提供基线数据——复用率改善与否,要有 before/after 才说得清。 ## 形态建议(v1) **数据面**:一个只读聚合路由(如 `GET /api/dsh-mneme/recall-stats?window=30d`): - 近 N 天被召回 / 被注入的 Top-N 记忆(含次数); - **僵尸记忆率**:活跃记忆中"窗口期内零召回"的占比,按 type / source 拆分; - 注入槽位命中率(实际注入条数 / 槽位 × 轮数); - 数据源:现有 `recall` 回执表 + `llm_audit_logs`,**纯读现有表,不新增写入路径**(聚合缓存表可选,不阻塞)。 **面板**:状态页加一张「记忆复用」卡——僵尸率大数字 + Top-N 召回列表 + 按类型拆分条。 **可选延伸(不阻塞 v1)**:autoDream 候选集把"长期零召回"作为排序信号;CLI `dsh-mneme stats` 输出同口径摘要。 ## 关键口径(实现前先定,防误报) 1. **僵尸判定要有豁免期**:写入后 N 天内(建议 7 天)没机会被召回的记忆不算僵尸,否则新记忆全是僵尸; 2. **零召回的两种语义要分开**:从未进入过检索/注入上下文(没机会)vs 进入了但没命中(不相关)——前者是机会问题,后者是质量信号; 3. **历史数据可能不全**:`recallRecordDefault` 开启前的窗口算不到,卡片对短窗口数据要标注可信度; 4. 面板与路由都走只读,失败静默降级(与状态页其他卡同风格)。 ## 验收 - 开箱默认可用(不新增配置也能出数,窗口有默认值); - 聚合查询在大库(5k+ 行)上不引入可感知卡顿(现有检索路径的性能基线见 #202/#203); - 中英双语、i18n 守卫通过; - 回归测试覆盖聚合口径(含豁免期与两种零召回语义的断言)。 改动面:一个聚合查询 + 一个路由 + 一张面板卡 + 测试,适合作为独立 issue 认领;动工前在 issue 里说一声即可。
Activity
heptaspirit commentedon Sep 17, 2026
认领(heptaspirit)。这个和我们正打算做的一件事正好合流:我们准备给真实库(1800+ 条、recall_runs 全留痕、recall_evals 表已就位)建一套检索回归基线,仪表盘要的三个信号(召回 Top-N / 僵尸记忆率 / 注入命中率)与它是同一套数据面,可以一起铺。它和 #218 heat 也是上下游(访问频率信号源),两个我们都认领了,做的时候会把口径对齐。
refactor(search,bootstrap): 复杂度自查收尾——实体召回与冷启动 5 处瘦身 (#224)
heptaspirit commentedon Sep 18, 2026
口径对齐(认领跟进)。把数据面逐处核实了一遍:三个指标里两个能从现有表直接出,第三个的口径需要拍板。 数据面现状(锚 upstream/main @ 5bd2dab) -
recall_runs是唯一已落库的召回回执:面板搜索、standalone/search、模型工具召回三条路径都走searchMemories,recallRecordDefault默认开,滚动清理默认 90 天。注意 candidates 存的是最终返回集(topK 截断后,service.js:862),不是完整候选池。 - 注入无留痕:注入候选只在内存里记跨轮轮换(rotationHistory),不落任何表;llm_audit_logs只记 LLM 调用。所以「注入槽位命中率」从现有表算不出来。 口径提案 1. 召回 Top-N:窗口内recall_runs.candidates按 id 聚合次数,join memories 补 type/source(记忆已删的标 deleted)。纯读。 2. 僵尸记忆率:活跃(未归档未遗忘)且窗口内零候选出现,按 type/source 拆分;豁免期 7 天按当前时刻计(写入不足 7 天不入分母,单独报数);历史数据不全用「窗口内 recall_runs 最早时间戳」标注覆盖率。 3. 两种零召回语义:A「从未进入检索上下文」(零曝光)可直接算;B「进入了但没命中」需要候选池留痕,而现有 candidates 是最终集——v1 先只报 A,卡片口径写明;B 留给后续(要动 recordRecall 的记录形状,不阻塞 v1)。 4. 注入命中率:两个选项。(a) 严格纯读:只能退化成「检索填充率」(topK 配额 vs 实际返回数),不是真注入命中;(b) 注入选中后复用store.recordRecall落一行mode='inject'——现成表加现成滚动清理,一处调用点,跟随recallRecordDefault开关。倾向 (b):它同时是 #218 heat 的访问频率信号源(注入触达是最大访问面),两个 issue 的衔接点就在这。是否接受这处写入,请你定。 形态:GET /api/dsh-mneme/recall-stats?window=30(只读聚合,窗口天数 + 扫描行数双钳制,超出标降级口径);面板「记忆复用」卡进状态页网格,自门控风格与热度分布卡同款(接口失败/数据不可信整卡不渲染,脚注标口径)。默认窗口 30 天,不新增配置键出数。测试覆盖豁免期、零召回语义拆分、聚合窗口钳制。modusensus commentedon Sep 18, 2026
口径确认:接受方案 (b)——注入命中后复用
store.recordRecall落一行mode='inject'。接受理由(含文献依据):
v(t+) = v(t) + Δv·(1-v(t))·exp(-n/N)里的n是累计访问次数、Δt按事件序列取间隔——现有touchRecalled只留last_accessed_at单时刻,次数/间隔/来源都没有账。注入是最大的访问面(曝光型访问),不落账则 heat 后续的间隔效应与 λ 重校准([Feature] heat 热度衰减(D 批次):访问频率调制的记忆升权与自然降温(opt-in) #218 已排为前置两步)就没有数据前提。mode字段正好把两者分开记账,不混口径。边界确认(对应你方案里的三条):
recallRecordDefault开关,不新增配置键;验收观察点补一条:
heatEnabled=false时写入行为不变(记录照落,只是 heat 不消费)——留痕与消费解耦,两 issue 各自独立验收。三个指标的口径按你的提案执行,免豁免期、窗口钳制、测试覆盖照单全收。heptaspirit commentedon Sep 18, 2026
增量已实现:注入终选集落
mode='inject'审计行(query存空串——该列 NOT NULL,主动注入没有查询词;跟随recallRecordDefault不设新配置键;heatEnabled=false时照写,留痕与消费解耦,与您补充的验收点一致),recall-stats新增注入口径(轮数 / 注入条数 /slotFillRate= 注入条数/(轮数×槽位数),注入候选计入 Top-N 与僵尸零曝光判定),面板「记忆复用」卡追加注入段(窗口内无注入行时省略)。分支heptaspirit:feat/inject-recall-217(基于 #233,避免 stacked diff)——#233 合并后立即 rebase 到 main 开 PR 闭合 #217。全量套件绿(1151 例,含注入落账门控/解耦/聚合口径 6 例新测试)。feat(stats): 记忆复用统计端点与面板卡(#217) (#233)
feat(stats): 注入命中留痕 mode='inject' 与注入命中率口径(slow-stack#217 增量)
feat(stats): 注入命中留痕 mode='inject' 与注入命中率口径(slow-stack#217 增量)
feat(stats): 注入命中留痕 mode='inject' 与注入命中率口径(#217 增量) (#247)
feat(standalone-api): GET /context 注入候选端点 + /search scope 透传(slow-sta…