Repository navigation
Description
背景
记忆巩固(autoSummarize / dream)依赖 LLM 调用,属成本大头。除 #229(游标内存态 → 重启重放)外,还有两个放大成本的来源:
- 触发频率无上限:总结/巩固按事件触发,长会话内高频执行,缺少频率下限与会话次数上限;
- 无成本分级:所有待蒸馏内容一律走 LLM,没有「轻规则预判 → 才升级 LLM」的级联;高峰期(LLM 服务繁忙/配额紧张)也没有延迟到非高峰再执行的机制。
目标:减少 LLM 调用次数,且能避开高峰时段。
提案
1. 成本感知级联(论文:2608.22215 Dual-Layer Agentic Memory)
路由用「小模型先筛、大模型兜底」级联(原文 1.7B/8B,<50% 输入升级大模型)。对应到蒸馏/巩固管线:
- 蒸馏前先做零 LLM 预判:类型已知可跳过(constraint/preference 类已有独立类型)、去重命中(saveWithDedupe 三元组)、低价值条目(quality_score / importance 低于阈值)→ 直接 skip,不进 LLM;
- 只有预判「不确定 / 高价值」的才调 LLM 蒸馏。
2. 零 LLM 构建(论文:2609.05553 EdgeMem)
原子证据条目原样保留、不做生成式压缩(构建与检索全程零生成调用)。对应:原子条(evidence 层)保持原样,只有叙述条 / Primary 视图聚合时才走 LLM——从结构上减少必须调 LLM 的内容量。
3. 有界检查点(同类工具 dsh-mnemon idle review)
频率下限 + 每会话次数上限(参照其 5min 下限 + 20 次/会话上限)。对应:
summarizeMinIntervalMinutes(已存在,default 0 → 建议给合理默认值并文档化);- 新增每会话蒸馏次数上限。
4. 错峰队列(「避开高峰期」的核心诉求)
高峰时段仅做规则预判 + 任务入队(零 LLM),LLM 蒸馏攒到配置的非高峰时段执行;或按最近调用失败 / 429 自适应退避。高峰期可配置。
5. 不确定性驱动读路径(论文:2604.00131 Oblivion,token 成本 -73%)
注入侧:确定性强的话题少召回、模糊话题多召回——降低每次对话的检索注入量。(后补项,非阻塞)
与 #229 的关系
- [Bug] lastDistilledSeq 为进程内存态:宿主重启后蒸馏窗口从头重放,已蒸馏历史被重复处理 #229 修「重启后游标归零 → 重放历史窗口」(进程内存态问题,@z2Ace0107 已预定);
- 本 issue 修「触发频率无上限 + 无成本分级 + 无错峰」(调度策略问题);
- 两件事正交,建议 [Bug] lastDistilledSeq 为进程内存态:宿主重启后蒸馏窗口从头重放,已蒸馏历史被重复处理 #229 先行。
验收
- 蒸馏前置零 LLM 预判生效(日志可见 skip 原因统计)
- 高峰时段 LLM 调用为 0,任务入队并在非高峰执行
- 每会话蒸馏次数有上限、可配置
- (口径已修正,见下) 相关开关开启后,LLM 调用量较关闭时可量化下降
第 4 项原文写的是「默认配置下 LLM 调用量较现状可量化下降」,这一口径按设计不可能成立。
错峰、每会话上限、零 LLM 预判三者的开关默认全关(summarizeMinIntervalMinutes默认 0、summarizeMaxRunsPerSession默认 0、summarizeMinWindowChars默认 0),因此默认配置下行为与改动前逐字节一致 —— 这是刻意的「默认关零回归」纪律,不是缺陷。现把口径改为「开启后可量化下降」。该口径下的实测数字仍然缺(需要一个开启后的真实负载样本)。这属于研究性数据补充,不阻塞代码交付。
落地记录(对照 main,2026-11-02)
| 提案 | 实现位置 |
|---|---|
| 1. 零 LLM 预判 | dsh-mneme/src/summarize.js:831-863 —— 窗口内可蒸馏字符数低于 summarizeMinWindowChars 时直接跳过,纯规则判定(字符数)、无模型参与;审计 error_message: "window-too-small" 带 metadata.distillChars / minWindowChars |
| 3. 频率下限 | summarize.js:744-754 —— min-interval(失败/degraded 的 run 也占用间隔) |
| 3. 每会话上限 | summarize.js:775-785 —— max-runs-per-session,排在间隔门之后(间隔是「刚刚跑过」,预算是「额度用尽」,前者信息量更大) |
| 4. 错峰队列 | summarize.js:762-774 —— 高峰写 error_message: "peak-hours" + scheduleDeferredRun;被 summarizePeakMaxDeferMinutes(默认 120)截断后到点仍处高峰时由 bypassPeak 放行,游标刻意不消费 |
| 5. 不确定性驱动读路径 | 确定查询收缩注入条数到一半(下限 1),模糊查询维持上限;拿不到查询时维持上限 |
补一条实现细节:被零 LLM 预判挡下的窗口不消耗 summarizeMaxRunsPerSession 预算(summarize.js:610 注释:「调用时自增,因此被零 LLM 预判挡下的窗口不消耗预算」)—— 否则规则跳过的越多,真该跑的时候额度越少,激励是反的。
参考
- 2608.22215 Dual-Layer Agentic Memory with Fast Write Routing and Slow Consolidation
- 2609.05553 EdgeMem: LLM-Free Agent Memory Construction and Retrieval via Evidence-Preserving Multi-Anchor Hypergraph
- 2604.00131 Oblivion: Self-Adaptive Agentic Memory Control through Decay-Driven Activation
Activity
modusensus commentedon Sep 19, 2026
维护者自留认领(
help wanted挂着但先自己排,避免和社区撞车;有不同切法欢迎在下面说)。第一批只做第 1、3 项,第 4(错峰队列)与第 5(注入侧不确定性召回)留后续 —— 那两项的配置面与观测面更大,先把「少调 LLM」这条主干落地。
第 1 项 零 LLM 预判级联(
src/summarize.js)第 3 项 有界检查点
summarizeMinIntervalMinutes现有默认保持不变;新增每会话蒸馏次数上限。口径(沿用本项目既有纪律)
config.jsschema +settings.js白名单成对出现,test/api.test.js的旗标计数锁同步;考虑 lightMode。feat(summarize): 成本感知级联的零 LLM 预判 + 每会话 run 预算(issue #239)
feat(summarize): 成本感知级联的零 LLM 预判 + 每会话 run 预算(issue #239) (#251)
feat(summarize): 错峰队列——高峰顺延到非高峰补跑(issue #239 第 4 项)
feat(inject): 注入条数的查询自适应——确定少注入、模糊维持上限(issue #239 第 5 项)
feat(summarize): 错峰队列——高峰顺延到非高峰补跑(issue #239 第 4 项)
Merge pull request #263 from slow-stack/feat/239-inject-uncertainty-b…
feat(summarize): 错峰队列——高峰顺延到非高峰补跑(issue #239 第 4 项)
Merge pull request #259 from slow-stack/feat/239-peak-aware-scheduling
modusensus commentedon Sep 26, 2026
关联 #317(审计记账可信性——降本结论的前置检查表):本单验收「日志可见 skip 原因统计」的度量链前提是记账可信,#317 已作为前置检查表发布。
xain commentedon Sep 26, 2026
认领:把错峰队列(第 4 项)镜像到 dream
第 4 项的错峰目前只落在
src/summarize.js(autoSummarize),dream 是另一个 LLM 大户:本机实测单次巩固 372s / 200 条快照(deepseek-v4.1-flash,走 OpenAI 兼容本地端点)。同一套「高峰不调模型、择时补跑」对巩固同样成立,且 dream 是全局单实例,顺延语义比按会话的蒸馏更简单。计划
新增两个配置键(opt-in;空串/默认 = 行为与现状逐字节一致):
dreamPeakHours""summarizePeakHours的同一份时段语法与解析(parsePeakSpec/isInPeakWindow/nextOffPeakAt已导出,不另写解析器)dreamPeakMaxDeferMinutes120(与summarizePeakMaxDeferMinutes对齐)行为:写入触发命中高峰时不调 LLM,登记一行
status='skipped'/error_message='peak-hours'审计(沿用第 4 项口径),并按上限择时补跑到最近的「高峰结束」时刻;被上限截断后到点仍处高峰则以bypassPeak放行——不进入无限顺延。审计诚实性:skip 行如实反映「没跑」,绝不虚报 ok。改动面
src/config.js— 两个 schema 键(注释写动机与消融数字)src/settings.js— 白名单成对注册(strings + int ranges)src/dream.js— 调度器加高峰闸门与择时补跑(只 import./summarize.js的 helper,无循环依赖;dispose清理定时器)lib/client.js— 面板「巩固与睡眠」组下补时段输入;顺带把此前只落配置层的summarizePeakHours一并渲染(否则两个错峰键一个能调、一个不能)。这一处属可选,若你希望 PR 更小我可以摘掉。test/dream-peak-hours.test.js— 新增回归:命中高峰跳过且写审计、择时补跑到最近非高峰、上限截断后放行、非法时段串按「未配置」处理(宁可不省也不误停)、dispose 清理定时器test/api.test.js— 旗标计数锁同步 +2README.md— 两个键的文档行一个待你定的点
dreamPeakMaxDeferMinutes默认取 120(与蒸馏对齐,我的倾向)还是 0(不设上限)?没有异议我就按 120 实现。验证方式
npm test全绿 +npm run sync(check-sync 平价);新增用例覆盖上表五条;真机侧用等效实现验证过择时补跑与 dispose 清理的行为。modusensus commentedon Sep 26, 2026
@xain 认领批准——计划质量很高:复用
parsePeakSpec三件套不另写解析器、默认值与 summarize 侧对齐、非法时段串按「未配置」处理(宁可不省也不误停)这个口径尤其对。审计诚实性(skip 行如实反映没跑)也符合仓库的 fail-safe 约定。老号背景免叙。循环依赖:#316 合并后,import 方向要反过来
你写这条计划时
dream → summarize单向还是成立的,但今天合并的 PR #316(#315 蒸馏思考强度)让summarize.js反向 import 了 dream:import { withEffortFallback, describeStreamFailure, EFFORT_REJECT_RE } from "./dream.js"(withEffortFallback是 dream/sleep/entity 三链路共享的 effort 拒收降级,蒸馏复用了它)。现在 dream 再 import summarize 就是真循环——ESM 活绑定下未必立刻炸,但两模块任一在模块求值期触到对方绑定就是隐雷,不埋这种。建议落法(比改 import 方向更对):把
parsePeakSpec/isInPeakWindow/nextOffPeakAt三个纯函数抽到独立模块(如src/peak-hours.js,零依赖),summarize.js保留 re-export(export { parsePeakSpec, ... } from "./peak-hours.js",test/summarize.test.js与其他调用方零改动),dream 从新模块 import——循环消失,helper 的归属也干净(它们本来就是时段调度语义,不是蒸馏语义)。抽离是纯搬移,顺手的事。dreamPeakMaxDeferMinutes默认 120:同意与
summarizePeakMaxDeferMinutes对齐(config.js 同款默认),且两者语义一致——防长高峰饿死巩固。0(无上限)会让整天高峰期间 dream 完全停摆,与「错峰不是罢工」的第 4 项初衷相悖。面板顺带渲染
summarizePeakHours:建议保留否则「两个错峰键一个能调一个不能调」的不一致确实该顺手抹掉,放在巩固组下与
dreamPeakHours相邻即可。若实际写下来发现牵动过大(i18n 键成对、strRow 复用面)再摘不迟,PR 大小不是硬约束。其余口径
dispose清定时器这条记得落测试;error_message用'peak-hours'与 summarize 侧的'min-interval'/'window-too-small'系列对齐成同一套 skip 语义;git rebase origin/main(今天 main 动了 feat(summarize): 蒸馏思考强度设置项 summarizeReasoningEffort(#315) #316/feat(continuity): 压缩边缘双落点(#249 N3) #314 两个 PR)。feat(dream): 错峰队列镜像到巩固(slow-stack#239 第 4 项)
Merge pull request #320 from xain/feat/dream-peak-hours
modusensus commentedon Sep 26, 2026
第 4 项(错峰队列)已随 v0.8.8 发布(PR #320:
dreamPeakHours镜像到巩固,时段解析三件套抽零依赖src/peak-hours.js,顺带消除与 #316 的循环依赖)。整单其余批次保持开放,进度对账已在上方楼层。modusensus commentedon Sep 26, 2026
第 4 项(错峰队列)已随 v0.8.8 发布(PR #320:
dreamPeakHours镜像到巩固,时段解析三件套抽零依赖src/peak-hours.js,顺带消除与 #316 的循环依赖)。整单其余批次保持开放,进度对账已在上方楼层。Merge pull request #406 from slow-stack/chore/239-distill-window-metrics
chore(distill): 窗口字符数无条件进审计 metadata——为 slow-stack#239 下一级预判定标
modusensus commentedon Oct 20, 2026
现状(2026-10-21 对照
main实测)四项里三项已落地,第四项「默认配置下调用量可量化下降」按设计不会发生——因为相关开关默认全关。
已落地
summarize.js:530「被零 LLM 预判挡下的窗口不消耗预算」;:620与:639写status: "skipped"审计行;:626「先报间隔——间隔是『刚刚跑过』,预算是『额度用尽』,前者信息量更大」summarize.js:632注释「Issue #239(第 4 项,错峰队列):高峰期不调模型——只登记一行 skip 审计并择时」;:484-495distillQueue串行队列;:505从peak-hours.js取parsePeakSpec/isInPeakWindow/nextOffPeakAtconfig.js:37summarizeMaxRunsPerSession: z.natural().min(0).max(1000).default(0),注释写明「0 = 不限,等同现状」,且「被预判挡下的窗口不占预算」落地批次:
8a7a0e5(零 LLM 预判 + 每会话 run 预算,PR #251)→2578e4b(错峰队列,PR #259)→26b5619(错峰镜像到巩固,PR #320)→300eb84(窗口字符数进审计 metadata,PR #406)→77e9c58(注入条数查询自适应,第 5 项)。第四项为什么默认不下降
summarizeMaxRunsPerSession默认 0(不限),错峰队列的空串默认关闭、行为「与现状逐字节一致」。这是刻意的:issue 自己的验收前提是「默认关:零行为变化」,两条要求互斥。所以第四项应改写为「开启后可量化下降,并给出实测数字」。还缺什么
77e9c58第 5 项(注入条数查询自适应)在 issue 正文里是否算完成,需要确认。接手建议
代码不用动,只差把验收口径改对 + 补一组开启后的实测数。这条现在完全可以关掉,第四项拆成「待实测」的小项。