Skip to content

[Feature] 巩固/总结的高峰感知调度:成本感知级联 + 有界检查点 + 错峰队列 #239

Closed

Description

@modusensus

背景

记忆巩固(autoSummarize / dream)依赖 LLM 调用,属成本大头。除 #229(游标内存态 → 重启重放)外,还有两个放大成本的来源:

  1. 触发频率无上限:总结/巩固按事件触发,长会话内高频执行,缺少频率下限与会话次数上限;
  2. 无成本分级:所有待蒸馏内容一律走 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 的关系

验收

  • 蒸馏前置零 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

  1. modusensus commented on Sep 19, 2026

    @modusensus
    MemberAuthor

    维护者自留认领(help wanted 挂着但先自己排,避免和社区撞车;有不同切法欢迎在下面说)。

    第一批只做第 1、3 项,第 4(错峰队列)与第 5(注入侧不确定性召回)留后续 —— 那两项的配置面与观测面更大,先把「少调 LLM」这条主干落地。

    第 1 项 零 LLM 预判级联(src/summarize.js)

    • 调 LLM 之前先跑一遍零成本预判,命中即 skip 并记 skip 原因计数;
    • 先只做可判定、可回归的规则;去重命中与「类型已知无需蒸馏」两条涉及蒸馏语义判断,留后续单独提。

    第 3 项 有界检查点

    • summarizeMinIntervalMinutes 现有默认保持不变;新增每会话蒸馏次数上限。

    口径(沿用本项目既有纪律)

    • 两项都 opt-in 默认关,默认路径与现状逐字节一致 —— 记忆是用户资产,行为变更必须可回滚;
    • 新配置键按 config.js schema + settings.js 白名单成对出现,test/api.test.js 的旗标计数锁同步;考虑 lightMode。
  2. modusensus commented on Sep 26, 2026

    @modusensus
    MemberAuthor

    关联 #317(审计记账可信性——降本结论的前置检查表):本单验收「日志可见 skip 原因统计」的度量链前提是记账可信,#317 已作为前置检查表发布。

  3. xain commented on Sep 26, 2026

    @xain
    Contributor

    认领:把错峰队列(第 4 项)镜像到 dream

    第 4 项的错峰目前只落在 src/summarize.js(autoSummarize),dream 是另一个 LLM 大户:本机实测单次巩固 372s / 200 条快照(deepseek-v4.1-flash,走 OpenAI 兼容本地端点)。同一套「高峰不调模型、择时补跑」对巩固同样成立,且 dream 是全局单实例,顺延语义比按会话的蒸馏更简单。

    计划

    新增两个配置键(opt-in;空串/默认 = 行为与现状逐字节一致):

    键 默认 语义
    dreamPeakHours "" 复用 summarizePeakHours 的同一份时段语法与解析(parsePeakSpec / isInPeakWindow / nextOffPeakAt 已导出,不另写解析器)
    dreamPeakMaxDeferMinutes 120(与 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 — 旗标计数锁同步 +2
    • README.md — 两个键的文档行

    一个待你定的点

    dreamPeakMaxDeferMinutes 默认取 120(与蒸馏对齐,我的倾向)还是 0(不设上限)?没有异议我就按 120 实现。

    验证方式

    npm test 全绿 + npm run sync(check-sync 平价);新增用例覆盖上表五条;真机侧用等效实现验证过择时补跑与 dispose 清理的行为。

  4. modusensus commented on Sep 26, 2026

    @modusensus
    MemberAuthor

    @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 大小不是硬约束。

    其余口径

  5. added a commit that references this issue on Sep 26, 2026
  6. added a commit that references this issue on Sep 26, 2026
  7. modusensus commented on Sep 26, 2026

    @modusensus
    MemberAuthor

    第 4 项(错峰队列)已随 v0.8.8 发布(PR #320:dreamPeakHours 镜像到巩固,时段解析三件套抽零依赖 src/peak-hours.js,顺带消除与 #316 的循环依赖)。整单其余批次保持开放,进度对账已在上方楼层。

  8. modusensus commented on Sep 26, 2026

    @modusensus
    MemberAuthor

    第 4 项(错峰队列)已随 v0.8.8 发布(PR #320:dreamPeakHours 镜像到巩固,时段解析三件套抽零依赖 src/peak-hours.js,顺带消除与 #316 的循环依赖)。整单其余批次保持开放,进度对账已在上方楼层。

  9. added a commit that references this issue on Oct 11, 2026
  10. modusensus commented on Oct 20, 2026

    @modusensus
    MemberAuthor

    现状(2026-10-21 对照 main 实测)

    四项里三项已落地,第四项「默认配置下调用量可量化下降」按设计不会发生——因为相关开关默认全关。

    已落地

    验收项 状态 证据
    蒸馏前置零 LLM 预判生效(日志可见 skip 原因) ✅ summarize.js:530 「被零 LLM 预判挡下的窗口不消耗预算」;:620 与 :639 写 status: "skipped" 审计行;:626 「先报间隔——间隔是『刚刚跑过』,预算是『额度用尽』,前者信息量更大」
    高峰时段 LLM 调用为 0,任务入队并在非高峰执行 ✅ summarize.js:632 注释「Issue #239(第 4 项,错峰队列):高峰期不调模型——只登记一行 skip 审计并择时」;:484-495 distillQueue 串行队列;:505 从 peak-hours.js 取 parsePeakSpec/isInPeakWindow/nextOffPeakAt
    每会话蒸馏次数有上限、可配置 ✅ config.js:37 summarizeMaxRunsPerSession: 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 自己的验收前提是「默认关:零行为变化」,两条要求互斥。所以第四项应改写为「开启后可量化下降,并给出实测数字」。

    还缺什么

    1. 回填前三项勾选框;
    2. 第四项改写口径:给一组「开启错峰 + 预算后」的实测对比(调用次数 / token),而不是要求默认下降;
    3. 77e9c58 第 5 项(注入条数查询自适应)在 issue 正文里是否算完成,需要确认。

    接手建议

    代码不用动,只差把验收口径改对 + 补一组开启后的实测数。这条现在完全可以关掉,第四项拆成「待实测」的小项。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions