Skip to content

[Feature] 巩固/蒸馏的维护窗口:正向定点 + 债务上限 + 手动触发入口 #424

Open

Description

@modusensus

背景

巩固(autoDream)与蒸馏(autoSummarize)目前是写入驱动 + 排除式时段窗:

  • 写入累积到阈值(dreamThresholdCount 10 / dreamThresholdChars 5000,且相对上次成功轮有净增长)→ 2 秒后跑一轮;dreamMinIntervalMinutes 是频率下限;
  • dreamPeakHours 是排除窗:命中就顺延到「下一个非高峰时刻」,最多顺延 dreamPeakMaxDeferMinutes(默认 120 分钟,上限 1440)。

这套机制能「避开某段时间」,但做不到「每天在一个固定点跑一次」,而后者才是省 token 的大头:写入驱动下巩固一天跑约 14.6 轮,每轮输入仍是 top-200 快照(平均约 37.8K token),大量轮次的输入高度重叠。

实测证据(现状的三个坑)

  1. 写错静默失效,且失效方向是多花钱。 想用补集表达「只在 03:00–04:00 跑」时,"00:00-03:00,04:00-24:00" 里的 24:00 非法 → parsePeakSpec 整体返回 null,而 null 等价于「未配置」= 不限制。写错的人是奔着省钱去的,结果回到全天候。
  2. 能用的补集写法反直觉且带缝。 "00:00-03:00,04:00-00:00" 解析为 [{start:0,end:180},{start:240,end:0}],允许窗 = 03:00–04:00(实测 03:30 放行、12:00/23:58 拦),但 23:59:00–23:59:59 这一分钟不落在任何窗内,是个缝。
  3. 只改 dreamPeakHours 会白设。 dreamPeakMaxDeferMinutes 默认 120 会让顺延在高峰期中间 bypassPeak 开跑——想真正定点,必须把它一起抬到不小于窗口跨度。

另外,插件侧没有任何手动触发口:CLI 只有 config/status/list/search/get/add/delete/reclaim,HTTP 面只有 dream-status 读端点,runDream 目前只能被调度器调用。

提案

  1. 正向维护窗口:新增「允许跑」的窗口配置(默认空 = 不限制,opt-in 默认关),语义是正向的——填 03:00-04:00 就读作「每天这一点跑」,不再要求用户做补集;底层复用现有 parsePeakSpec 时段语法与面板入口。
  2. 债务上限安全阀:待巩固规模超过硬顶(如 200 条 / 60K 字符)或距上次成功超过 N 小时 → 提前跑一轮,别让「一天一次」在异常写入量下把记忆准确度饿死。
  3. 定点靠事件搭便车,不引入常驻 cron(src/dream/sleep.js:683 已写明 DSH 插件没有常驻定时器):每次写入/空闲事件做一次 O(1) 判断「今天那一轮跑了吗」,没跑且已过点就补跑——这样关机、休眠、DSH 没开都能在下次唤醒时补上;纯 setTimeout 挂 24 小时在休眠/时钟跳变下不可靠。
  4. 失败短重试:一天一次时「一次失败 = 浪费一整天」。近 7 天 102 轮里 12 轮失败(11.8%),需要保留失败后的短重试(与现有 failureBackoff 对齐)。
  5. 手动触发入口:斜杠命令 + POST /api/dsh-mneme/dream/run(安全阀、排障与「我现在就想清理一下」共用同一个入口)。

收益与代价(样例库实测 + 同口径估算)

方案 7 天巩固输入 token 相对现状
A 现状(写入驱动,约 14.6 轮/天) 3,856,785 —
B 一天一次定点,仍送 top-200 窗口 ~264,681 −93%
C 一天一次定点,只送当天变更 ~398,297 −90%

代价要说清:重复记忆的滞留窗口从约 99 分钟拉长到 ≤24 小时;近 7 天约 13% 的轮次 applied=0(跑了等于白跑),改成一天一次后这类浪费更贵,所以第 2、4 项安全阀不是可选项。

与 #239 的关系

#239 第 4 项「错峰队列」已落地为 dreamPeakHours / summarizePeakHours(排除式 + 顺延补跑)。本 issue 是它的下一步:正向窗口语义 + 债务上限 + 补跑语义 + 手动入口,与 #239 其余四项(成本感知级联、零 LLM 构建、有界检查点、不确定性驱动读路径)正交。

验收

  • 正向维护窗口可配置,默认不改行为(opt-in 默认关)
  • 窗口外不发起巩固/蒸馏 LLM 调用,且债务不丢
  • 硬顶/N 小时安全阀可配置并有审计行
  • 关机/休眠跨过窗口后能在下次唤醒补跑一次
  • 手动触发入口可用(斜杠命令 + HTTP)
  • 默认配置下 LLM 调用量较现状可量化下降

附:今天就能用的临时办法

dreamPeakHours = "00:00-03:00,04:00-00:00" + dreamPeakMaxDeferMinutes = 1440 可以近似「每天 03:00–04:00 一次」。之所以建议不要长期依赖它,见上文第 1–3 条坑。

(相关但可另开的一条:parsePeakSpec 对非法串返回 null 且无任何告警,建议在面板与日志侧给出「时段串未生效」的显式提示——本 issue 不吞并它。)

Activity

  1. modusensus commented on Oct 20, 2026

    @modusensus
    MemberAuthor

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

    零实现。四个关键词全 0 命中,也没有任何提交引用过 #424。

    实测

    maintenanceWindow  0 命中
    debtCap            0 命中
    positiveFixpoint   0 命中
    manualTrigger      0 命中
    

    git log --grep "#424" → 0 条(main 与所有分支均无)。

    相关的既有能力只有:a7874e0 存储无损回收(手动)、config.js:692 retentionDays 默认 90(时间维度保留)、summarizeMaxRunsPerSession 与错峰队列(#239,成本维度)。没有「维护窗口 / 债务上限 / 正向定点」这一层。

    还缺什么

    issue 提的三件,一件都没做:

    1. 正向定点:明确「巩固到什么状态算达标」,而不是跑完就结束;
    2. 债务上限:未处理候选 / 待合并条数到阈值时阻止新写入或强制整理;
    3. 手动触发入口:面板上「现在巩固一次」——注意 这个在 v0.9.3 已经有「现在沉淀一次」(M.O. 线,#489),但那触发的是沉淀(distill),不是巩固(dream),两者不要混。

    接手建议

    这条的价值在「债务上限」:现在巩固失败会重试、会退避(d2df61d 指数退避),但没有「积压到多少就该停手」的判定。建议先只做债务度量(只读、进 /health),再谈上限与强制整理。

  2. louisjar commented on Nov 4, 2026

    @louisjar

    00:00-03:00,04:00-24:00,中的逗号貌似也需要分全角半角,之前全高峰期在巩固,应该是写了全角逗号。

  3. modusensus commented on Nov 4, 2026

    @modusensus
    MemberAuthor

    复核了这条线索,方向对,但成因比「逗号全角半角」宽一层。用 main 上的 src/peak-hours.js 直接跑 parsePeakSpec(纯函数,不需要起插件):

    输入 结果
    00:00-03:00,04:00-06:00 ✅ 两段
    00:00-03:00,04:00-06:00(全角逗号) null
    00:00-03:00,04:00-06:00(全角冒号) null
    00:00-03:00(全角数字) null
    00:00~03:00(波浪号) null
    09.00-18.00(点号当冒号) null
    00:00-03:00,04:00-24:00 null(24:00 小时越界)

    只要串里出现任何非 ASCII 的等价符号,整串就解析成 null,而 null 等价于「未配置 = 不限制」,于是巩固/蒸馏回到全天候——正是 @louisjar 描述的现象。

    根因两处

    1. parsePeakSpec 按 rest.split(",") 切段(src/peak-hours.js:59),只认 ASCII 逗号。全角 , 切不开,整串会作为一个 part 去匹配下一行的正则,必然失败。
    2. 段正则 /^\s*(\d{1,2}):(\d{2})\s*-\s*(\d{1,2}):(\d{2})\s*$/(src/peak-hours.js:60)只认 ASCII 数字、半角冒号、半角连字符;\s* 只吃空白,不吃这些等价符号。

    关于举例的那条串

    00:00-03:00,04:00-24:00 其实同时踩了两个坑:24:00 本身非法(:63 的 eh > 23 直接判 null),所以即便逗号写成半角也照样失效。

    想表达「只在 03:00–04:00 跑」,正确写法是:

    00:00-03:00,04:00-00:00
    

    04:00-00:00 是跨零点段(end=0),解析为 {start:240, end:0},允许窗 = 03:00–04:00。

    已知的缝(本 issue 正文第 13 行也提过):这种写法下 23:59:00–23:59:59 不落在任何窗内。

    关于静默失效

    「解析失败无任何告警」本 issue 正文第 53 行已注明不吞并、建议另开。确认它值得单开一条可观测性 issue——解析失败时在日志与面板给显式提示,零行为变化、不新增配置键。本 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