### 现象 `src/summarize.js:356-365`:监听 `session/event`,`event.type !== "turn/end"` 直接返回,否则执行蒸馏;只有同一会话的 `inFlight` 互斥,**没有频率下限**(对照 autoDream 有 `dreamMinIntervalMinutes`)。 产出条数**没有上限**:`parseSummaryJson` 之后逐条落库(`summarize.js:318-330`),条数完全由模型输出决定。 本机实测(0.7.31): - 09-11 当天 `autoSummarize` 成功 **84 次**(`llm_audit_logs`,`trigger_source='autoSummarize'`); - 单个会话 `session-39c42c2b-…` 一天落库 **198 条**; - 同一事实「Win7 PowerShell 2.0 下 OpenSSH 官方脚本失效」在 **6 小时内被铸出 28+ 条**(先后含 36 条,后由 dream 归档)。 ### 为什么写入端去重拦不住 `service.js:903-905` 的去重条件是「**同类型** + 标题 `trim()` 后**完全相等** + 该类型 **importance 排序前 100 条**」。而蒸馏每次都会重写标题措辞——本次审计中,cos≥0.85 的重复簇里「标题归一化(去空格与标点)后完全相同」的簇为 **0 个**。也就是说这类重述在写入端必然穿透。 ### 影响 09-11 当天新增 409 条、`receipt_chain` 合并 59 条;活跃记忆 3 天内从 276 涨到 855。清理速度追不上写入速度,重复与冲突队列(见 #126)持续累积。 ### 建议 1. 新增 `summarizeMinIntervalMinutes`(语义对齐 `dreamMinIntervalMinutes`); 2. 单次蒸馏的产出条数上限(可在 prompt 与落库两侧同时约束); 3. 同一会话内的主题抑制:同一事实在 N 小时内不重复落库(可用标题归一化 + 向量相似度近似实现); 4. 或让 summarize 落库前走一次向量查重(与"写入端去重改为向量"是同一处实现,可合并落地)。 ### 复现方式 按 `llm_audit_logs.trigger_source='autoSummarize'` 统计调用次数;按 `memories.source` 分组并对标题做归一化聚类,即可看到同一会话的重述率。 --- ### 相关 issue - #47(open)询问「如何做到临时关闭记忆模式」——本 issue 建议的细粒度节流可作为全局开关之外的中间档。 - #59(open)是 DSH 0.1.2 兼容问题(autoSummarize 不执行),与本 issue 的「执行过于频繁」是两个方向。 ### 环境与现场 - dsh-mneme **v0.7.31**(npm latest)· DSH 0.1.1-rc.2 · Windows · Node v22.22.2 - embedding:OpenAI 兼容(`nvidia/llama-nemotron-embed-vl-1b-v2:free`,2048 维,`/semantic` 返回 ready:true) - 库况快照(2026-09-11):总 2804 / 活跃 818 / 归档 1986 / `conflict_pending` 挂起 121(已解决 0) - 生效配置(`GET /api/dsh-mneme/features`,优先于 cordis.patch.yml):`autoDream=true`、`sleepModeEnabled=true`、`heatEnabled=true`、`conflictFreezeEnabled=true`、`allowCrossTypeMerge=false`、`dreamSkipInvalid=true`、`dreamMinIntervalMinutes=3`、`dreamMaxSnapshotSize=100`、`memoryQualityFilter.enabled=true` - 审计方法(可复现):用 `node:sqlite` 只读打开 `memory.db`,对活跃记忆读取 `embedding`(JSON 数组,2048 维)归一化后做余弦聚类;窗口口径与 `src/dream.js:600-612` 一致(活跃非 summary,按 `updated_at` 倒序取前 `dreamMaxSnapshotSize`)
Activity
Anans-Amarre commentedon Sep 11, 2026
感谢实测数据(单会话日铸 198 条、同一事实 6 小时铸 28+ 条)。节流 + 条数上限 + 同会话主题抑制三个方向我们都认同,对照 autoDream 已有
dreamMinIntervalMinutes,autoSummarize 确实该补上。这个属于 v0.8 的增强方向,欢迎直接提 PR,也可以先在 issue 里把节流参数设计讨论清楚再动手。
483218131 commentedon Sep 12, 2026
一、现状事实
session/event且event.type === "turn/end",每轮都跑;仅同一会话inFlight互斥src/summarize.js:356-365、199-205dreamMinIntervalMinutes)parseSummaryJson后逐条saveWithDedupe,条数由模型输出决定src/summarize.js:318-330saveWithDedupe= 同类型 + 标题trim()后全等 + 该类型 importance 前 100 条src/service.js:903-905distillMaxChars(默认 24000,截断语义)src/summarize.js:223实测代价(本机,0.7.31):09-11 单日
autoSummarize成功 84 次;单会话session-39c42c2b…一天落库 198 条;同一事实(Win7 PowerShell 2.0 下 OpenSSH 官方脚本失效)6 小时内被铸出 28+ 条。当天新增 409 条、receipt_chain合并 59 条 → 活跃记忆 3 天从 276 涨到 855。二、三个参数(默认值全部 = 零行为变化)
summarizeMinIntervalMinutes0= 现状(不限)。与dreamMinIntervalMinutes语义对齐:失败也占用间隔,防止失败连发summarizeMaxEntriesPerRun0= 不限summarizeDedupeModeoff|title|vectoroff= 现状summarizeDedupeMinSimvector模式的并入阈值(title模式不使用)summarizeDedupeWindowHours五项都进
feature_flags白名单(src/settings.js:74-83的FEATURE_FLAG_INT_RANGES+FEATURE_FLAG_ENUMS),使面板可即时开关、也是线上回滚开关——沿用仓库既有风格。三、实现要点
3.1 间隔门(per-session)
createSummarizer内部已有 per-sessioninFlightMap(summarize.js:199),旁边加一个同样以session.id为键的lastRunAtMap:dreamMinIntervalMinutes的既有语义一致(config.js:52注释:失败/degraded 的 run 也占用间隔)。AbortError)不写点,避免会话关闭误伤下一次。inFlight.clear()处一并清)。3.2 条数上限
parseSummaryJson之后、落库循环之前截断:被截断的条数写审计(见 §4),便于用户调整上限。
3.3 落库前去重(
summarizeDedupeMode)先纠正一个我起初写偏的地方:本条最初写成"纯确定性标题归一化",理由是"确定性逻辑不用 LLM"。那是把仓库「确定性流程禁用 LLM」的规则过度套用了——那条规则针对的是路由 / 重试 / 状态码这类规则性流程;而"两条记忆是不是同一件事"是语义判断,本来就该由向量或 LLM 来做。而且本设计引用的实测数据自己就否证了纯标题方案:cos≥0.85 的重复簇里"归一化标题全同"的簇为 0 个。
因此改为分层,默认不带 LLM,但把语义能力放在正确的位置:
toLowerCase+ 去空白/标点)embedding列 +store的余弦比对)merge/differentiate出口vector模式流程(写入端,无 LLM):summarizeDedupeWindowHours内落库的记忆(store.list({ source: \session:${session.id}` })后按created_at过滤,source` 字段已带会话 id);sim ≥ summarizeDedupeMinSim→ 并入既有条目(service.update追加内容,沿用appendContent的\n\n---\n[ts]形式,content_history留痕);saveWithDedupe(跨会话的同类事实仍交给 autoDream / sleep 的合并路径)。为什么不把这个交给 LLM:写入端每次蒸馏都要判定,调用量与 84 次/天同量级,成本与延迟都会显著抬升;而 dream / sleep 已有批量裁决通道与 receipt 审计,语义判断放那里更划算——这也是本设计不去抢 #126 职责的原因。
title模式仅作可选的极低成本档(默认off),保留它是因为"完全同名"虽少但确实存在(例如同一会话里反复出现的同名结论),且它零成本、零风险。3.4 与
distillMaxChars的关系distillMaxChars限制输入(转录截断),本设计限制输出与频率。两者互补,互不替代;文档里建议并列说明。四、可观测性
llm_audit_logs.status已有skipped取值(schema 注释明确列了success | error | skipped),但当前只写 success/error。建议:status='skipped'、error_message='min-interval';metadata记{parsed: N, capped: M};metadata记{deduped: K, mode: 'vector'|'title', maxSim: 0.94}。这样升级后可以用一条 SQL 直接验证节流是否生效(现在完全不可观测,也是本 issue 难以被用户自查的原因)。
五、向后兼容
saveWithDedupe语义不动);主题抑制开启后才走 update 分支。FEATURE_FLAG_INT_RANGES驱动,前端已有同类控件)。六、测试清单(建议)
summarizeMinIntervalMinutes=0→ 与 v0.7.31 行为一致(连续两次 turn/end 都触发)。=30→ 第二次 turn/end 在 30 分钟内不触发;第一次失败也一样占间隔。summarizeMaxEntriesPerRun=5、模型返回 12 条 → 只落 5 条,审计 metadata 记{parsed:12, capped:7}。summarizeDedupeMode='vector'、MinSim=0.92:构造同一事实的两种措辞(本机实测余弦 0.94 量级)连落三次 → 只落 1 条,后两次并入(content_history有记录),不新增记忆行;同时断言 sim 低于阈值的一组确实新建。4b.
summarizeDedupeMode='title':仅完全同名被并入,措辞不同的正常新建(用于确认该档的边界,避免误以为它能替代向量档)。4c. 向量缺失(
embedding IS NULL)的既有条目不参与比对,不抛错。feature_flags白名单内(设置→读取 round-trip 通过校验)。七、与 #47 的关系(顺带回答)
#47「如何做到临时关闭记忆模式?」目前只有
autoSummarize: false这种全局极端开关。本设计的summarizeMinIntervalMinutes提供了一个中间档(例如设 1440 = 一天最多蒸馏一次),可以在不彻底关闭记忆的前提下把产量压下来;文档里可以把它列为 #47 的一个可选答案。八、待确认的几个点
dreamMinIntervalMinutes/distillMaxChars)?是否需要把summarizeMaxEntriesPerRun与distillMaxChars合并成一个"预算"概念?0(零行为变化)?还是希望summarizeMinIntervalMinutes直接给一个非零默认(会改变现有用户行为)?off可以接受吗?如果希望开箱即用,是否把vector作为默认(阈值 0.92),还是担心小 embedding 模型误折叠而保持 opt-in?命中时的动作:并入既有条目(本文档建议,content_history兜底)还是直接跳过丢弃?vector档是写入端语义去重的实现之一(作用域 = 同会话 + 时间窗)。若维护者更希望它做成全局的写入端去重(不限会话),请指定;我会把参数与作用域改成对应形态——两者共用同一段比对逻辑,不重复实现。status='skipped'+metadata(现有表结构即可,无需 schema 变更)?codingRetrospect路径:已核实它只切换 prompt(src/summarize.js:243),入口、触发与落库循环完全共用同一个summarize()→ 本设计的三项参数天然同时覆盖两条路径,无需单独处理。若维护者希望两者分开节流,才需要额外设计。modusensus commentedon Sep 12, 2026
设计帖照准,五个参数与实现要点全部按你的拟案实施 👍 逐条回应「待维护者确认」:
dreamMinIntervalMinutes/distillMaxChars风格一致;summarizeMaxEntriesPerRun不与distillMaxChars合并——一个限输入(截断转录)、一个限输出(落库条数),单位与语义都不同,按你 §3.4 的说法文档并列说明即可。off可接受(仓库 opt-in 惯例,searchSemanticDedup同款);命中动作取并入既有条目(content_history兜底可回溯),不做直接丢弃。作用域先按「同会话 + 时间窗」——全局写入端语义去重是另一个更大的话题(且与searchSemanticDedup的检索端去重容易混淆),不并进这个 PR,将来若要做会单独开 issue 并复用你这段比对逻辑。status='skipped'+metadata,零 schema 变更,这个设计让节流效果一条 SQL 可验证——比参数本身更有价值。可观测性那节(§4)和「把节流档位列为 #47 的中间档答案」(§7)都是加分项,尤其后者正好补上"全局开关之外没有中间态"的空缺。
落地顺序:建议本条最先做(改动面最小、完全独立于另外两条),PR 直接对 main。测试按你 §六 的清单走,额外补一条「间隔门在会话 dispose 后重置」(新会话不受旧会话 lastRunAt 影响——如果你已经在 §3.1 的 Map 清理里覆盖了就忽略这条)。
feat(summarize): 节流、产出上限与同会话写入端去重 (#127) (#143)
test(summarize): 补 slow-stack#127 三处 fail-safe 与打点回滚的覆盖 (slow-stack#127)
test(summarize): 按 review 强化 slow-stack#127 两条补测的行为断言
test(summarize): 补 #127 三处 fail-safe 与打点回滚的覆盖 (#144)
modusensus commentedon Sep 12, 2026
#127 已在 v0.7.32 落地(PR #143):summarize 节流 + 每会话上限 + 同会话去重;PR #144 补 fail-safe 边界。
feat(scope): 写入端 scope 标注存储层(A1,issue #17) (#153)