Skip to content

弱模型(qwen3.8-flash)驱动 autoDream 时 invalid decisions 整单拒绝——建议恢复宽容路径或提供 dreamSkipInvalid #89

Closed

Description

@483218131

现象

qwen3.8-flash 驱动 autoDream(input_count 100/200)连续 4 次失败,error 为
"invalid decisions: 6/9/8 errors"——每次输出的决策清单中少量决策非法
(unknown id / 跨类型 merge 等),validateDecisions 整单拒绝,占绝大多数的
合法决策一并作废。

背景

normalizeDecisions 在 v0.7.11 随近重写移除(勘误已确认)。README v0.7.5
描述的「跨类型合并默认跳过并应用合法子集(run 记 degraded)」在当前版本
不存在,实测为整单拒绝。

影响

flash 级低价模型(如 qwen3.8-flash 半价 ¥0.4/¥1.4)本适合高频后台任务,
但决策遵循度抖动使其失败率 100%(实测 4/4),弱模型完全无法驱动 autoDream。

建议

  1. 恢复宽容路径:非法决策跳过、应用合法子集(run 记 degraded),或提供
    dreamSkipInvalid 配置;
  2. 可选:autoDream 触发频率节流配置(最小间隔分钟数),避免高频写入场景
    下失败调用堆积。

环境

0.7.16 / token-rhythm + qwen3.8-flash / 2026-09-08 实测
(可附 dream_runs 的 invalid decisions 明细样本)

Activity

  1. 483218131 commented on Sep 8, 2026

    @483218131
    ContributorAuthor

    0.7.20 main 源码复核确认未恢复

  2. self-assigned this
    on Sep 8, 2026
  3. added
    bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)
    on Sep 8, 2026
  4. moved this from 📋 Backlog to 🔨 In Progress in dsh-mneme 任务看板on Sep 8, 2026
  5. added a commit that references this issue on Sep 8, 2026
  6. modusensus commented on Sep 8, 2026

    @modusensus
    Member

    报告完全成立,感谢这份高质量的定位——三点都核实了:

    1. 回归属实:dreamSkipInvalid(单条非法决策跳过 + 合法子集应用 + run 记 degraded)与 allowCrossTypeMerge 开关是 v0.6.9 为 [Bug] autoDream 恒失败 (applied=0):跨类型 merge 触发 fail-safe 整单拒绝 #26 实现的,v0.7.11 重写 validateDecisions 时一并丢失,我们漏了迁移;
    2. 文档错讹属实:README「Fail-safe」一节至今承诺「默认跳过该条并应用合法子集(run 记为 degraded)」,代码却整单拒绝;
    3. 影响评估同意:弱模型的决策合规抖动是常态而非异常,整单拒绝不仅让弱模型不可用,还白烧每次调用的 token。

    修复已合入 main(16f1f97,随下一版发布):

    • validateDecisions 恢复 v0.6.9 的 skipInvalid 双轨路径:单条非法决策跳过(skipped 明细含 index/action/ids/error,并进 warn 日志)、不 claim 任何 id、合法子集照常应用;run 状态如实记为 degraded(ok: true,不破坏基线刷新语义);
    • 全局信号保持整单拒绝:update/create 条数上限、显式覆盖率下限(防截断输出被隐式 keep 洗白)不受宽容开关影响——刷爆上限的输出是模型坏了,不是轻微 schema 漂移;
    • allowCrossTypeMerge 开关一并恢复(默认 false);
    • dreamSkipInvalid 默认 true(与 README 承诺一致),已进 feature flags 白名单,设置面板可直接改;
    • 你提的第 2 点也做了:新增 dreamMinIntervalMinutes(默认 0 = 不限),autoDream 最小触发间隔;失败/degraded 的 run 同样占用间隔,专门防失败调用连发。

    如果方便,请贴几组 dream_runs 里的 invalid decisions 明细样本(invalid 决策原文 + 对应 snapshot id),我们用它们补几个真实分布的测试用例——比构造的更接近弱模型的真实失败形态。

  7. added a commit that references this issue on Sep 8, 2026
  8. Anans-Amarre commented on Sep 8, 2026

    @Anans-Amarre
    Collaborator

    已随 v0.7.22 修复并发布 ✅

    • 恢复 v0.6.9 的 skipInvalid 宽容校验路径(v0.7.11 近重写时丢失的回归):dreamSkipInvalid(默认 true)单条非法决策跳过 + 合法子集应用 + run 记 degraded(warn 日志带跳过明细);allowCrossTypeMerge(默认 false)显式放宽跨类型合并。
    • 弱模型(qwen3.8-flash 等)决策合规抖动下不再 4/4 整单拒绝白烧 LLM 调用;严格模式(不传开关)/ sleep 路径行为不变,全局上限/覆盖率下限仍整单拒绝(刷爆上限说明模型坏了,不是轻微 schema 漂移)。
    • 请求 2 也一并做了:新增 dreamMinIntervalMinutes(0-10080,默认 0=不限)——autoDream 最小触发间隔,失败/degraded run 也占用间隔,防失败调用连发,间隔内触发静默跳过。
    • 新开关已入 feature_flags 白名单(34 键),可在设置页/PUT /api/dsh-mneme/features 调整。696 测试全绿(v0.6.9 五用例移植 + degraded/严格 e2e + 节流 + 白名单计数)。

    欢迎升级到 v0.7.22 实测,有任何问题请反馈,此 issue 保持开放。

  9. 483218131 commented on Sep 9, 2026

    @483218131
    ContributorAuthor

    0.7.22 实测补充:skipInvalid 生效确认 + 三个新发现

    ✅ skipInvalid 生效确认(qwen3.8-flash + dreamMaxSnapshotSize: 100)

    连续两条 degraded run,合法子集真实应用:

    run 决策构成 applied
    #1 merge 5 组(2/3/4/4/2 ids)+ archive 1 + keep 73(implicit) 17
    #2 merge 1 组(8 ids)+ archive 4 + keep 79(implicit) 14

    merge 质量做了 content 级核对:重复识别准确(如 4 条同主题 pitfall 合 1、8 条
    近重复项目记忆合 1),合并后条目保留了各源的独有细节,未见信息丢失。
    dreamSkipInvalid 默认值下弱模型可用性大幅改善,感谢快速修复。

    ⚠️ 发现 1:dreamMinExplicitCoverage 下限会误伤「挑重点」型弱模型

    qwen3.8-flash 面对快照倾向只显式决策真正需要动的条目(约 20%),其余按提示词
    「无问题的条目无需输出」留空。在 dreamMinExplicitCoverage: 0.5 / 0.2 下,这种
    良性输出触发 coverage 全局信号被整单拒绝(实测 "invalid decisions: 1 errors"
    连续出现,该条即 coverage 检查);降为 0 后同样输入转为 degraded 正常应用。
    建议:文档标注该键对「挑重点」型模型的误伤面,或评估默认值。

    ⚠️ 发现 2:archive 决策存在类型语义越界

    实测一轮中 12 条互不相关主题的记忆被批量 archive,LLM 自述理由为价值判断
    ("多为一次性调试细节……对核心知识库长期参考价值较低,保持整洁和高信噪比"),
    而非 prompt 预期的「重复或过时」——12 条中无一条与保留条目构成重复关系。
    其中 8 条倾向误伤,包括:

    • rejected_solution ×2(该类型的存在意义即长期保留被否决方案防重蹈)
    • constraint ×3(长期有效的工程约束与环境事实)
    • pitfall ×1(与保留条目同级的长期踩坑记录)
    • 活跃任务直接相关条目 ×2(数据冷热分层判断依据、备份介质决策)

    建议:archive 决策对 rejected_solution / constraint 等长保留语义类型增加
    确认门槛或批量上限;或 prompt 明确 archive 仅限「重复」与「过时」两类依据。

    ⚠️ 发现 3:dreamMinIntervalMinutes: 30 疑似未生效

    配置 30 分钟最小间隔后,间隔 8.7 分钟的两次 auto run 均执行
    (lib/dream.js:493 闸门存在、lib/index.js:286 传参链完整,但未拦截)。
    0.7.22 实测,请核查节流闸门的调用路径是否覆盖全部触发源。

    审计建议

    0.7.16 的 dream_runs 对 validate 失败 run 不留存决策明细(decisions 列为空),
    弱模型失败形态无法离线分析;0.7.22 已改善(degraded run 存完整决策清单)。
    残留:全局信号(coverage/update 上限)的 errors 数组仍未持久化,error 字段
    仅存计数("invalid decisions: N errors"),建议将 errors 数组一并落库,
    便于定位是哪类全局信号触发。

  10. 483218131 commented on Sep 9, 2026

    @483218131
    ContributorAuthor

    补充(09-09 观察期新样本):0.7.22 下两条自然触发的 run 间隔 35/40 分钟(≥30 阈值),
    均正常执行,提示闸门在常规路径并未整体失效;8.7 分钟连发疑似发生在特定触发源
    (如密集 memory_save 场景)。建议核查各触发源到 dreamMinIntervalMinutes 闸门的
    调用分支覆盖。注:当前尚无「触发时刻 <30min 且被拦截」的直接样本,以上为行为
    对照推断,供排查方向参考。

  11. modusensus commented on Sep 9, 2026

    @modusensus
    Member

    感谢这组观察期对照样本,非常有价值

    与代码核查结论互相印证。

    我们复核了 0.7.22的完整触发链:runDream 的唯一入口就是节流闸门(service.setDreamHook → maybeSchedule),不存在绕过路径;闸门逻辑有回切测试覆盖(dream.test.js | minIntervalMs throttles re-triggering regardless of run outcome),失败/degraded run 同样占用间隔。您观察到的「35/40 分钟(≥30 阈值)」正常执行!与该实现一致。


    8.7 分钟连发的最大嫌疑:进程内状态丢失

    闸门的 lastRunAt 是调度器闭包内的进程内存——DSH 插件 reload / 桌面端重启 / 设置变更重启后归零,闸门随之失效。这与「常规路径未整体失效、但特定场景未触发」的现象吻合:密集 memory_save 场景下若伴随插值重载,重启后的第一次触发闸门形同虚设。


    请求直接证据

    如您所述,目前还没有「触发时刻 <30min 且未被拦截」的直接样本。若能再次发现连续,请提供:

    1. 两条 run 的 dream_runs.created_at 精确时刻 + run_type;
    2. 对应时段是否有插值重载 / DSH 重启(DSH 日志或桌面端重启记录)。

    若能稳定复现(如密集 save 场景固定触发),我们就直接把 lastRunAt 持久化到 db(reload 安全),随下版发布。

  12. pinned this issue on Sep 9, 2026
  13. unpinned this issue on Sep 9, 2026
  14. 483218131 commented on Sep 9, 2026

    @483218131
    ContributorAuthor

    补充连发直接证据(0.7.22 实装确认):

    样本 A(进程重启场景,机制确认):01:17:01.207Z degraded(auto) 与 01:25:44.536Z degraded(auto) gap=8.7min,两条正好横跨 09:20:10 的 DSH web 进程重启(0.7.22 于 08:27:41 npm 安装完成)——后一条属重启后新进程,lastRunAt 归零,与您的假设一致。

    样本 B(无重启场景,更有区分度):04:33:27.390Z failed(auto, error="llm failed",上游流断) → 04:45:15.292Z degraded(auto) gap=11.8min,未被 30min 闸门拦截。期间 DSH web 进程未重启(09:20:10 启动的进程持续存活),也无配置文件变更(12:25-12:50 全盘 mtime 扫描,仅 memory/*.md 导出早于 run 4 秒)。

    行为对照(指向异常路径漏记):04:45:15Z degraded 成功后,04:50Z 一次 memory_save 触发(间隔 5min < 30min)被正常静默拦截(无新 run、无 audit 记录),截至 05:15Z 窗口关闭无任何新连发——说明 degraded 成功路径正常记录了 lastRunAt;对照 04:33:27Z llm failed 之后 11.8min 即有触发执行,异常点集中在 llm failed 的抛出路径(可能与测试覆盖的 validate 失败路径不同),建议核查该路径是否绕过了 lastRunAt 更新。

    另两个小问题:1) run_type=sleep 的 run 是否应占用 dreamMinIntervalMinutes?(01:31:26Z sleep → 02:00:36Z auto gap=29.2min);2) error 字段仍只存计数,errors 数组未落库(上次审计建议的残留)。

  15. Anans-Amarre commented on Sep 9, 2026

    @Anans-Amarre
    Collaborator

    这组对照质量很高,尤其 Sample B 用「04:50 的 5min 触发被正确静默拦截」当对照组,把 degraded 成功路径与 llm failed 路径分开,比单看时间戳可靠得多。三点回应:

    1. llm failed 路径不绕过 lastRunAt(代码证据)

    lastRunAt 在调度器计时器触发时、调用 onRun 之前就无条件打点(src/dream.js:553,紧邻闸门 src/dream.js:547),与 run 结果无关;设计注释明确写了「失败/degraded 的 run 也占用最小间隔——节流的目的正是防止失败调用连发」。这条路径有专门用例保护:test/dream.test.js:1062「minIntervalMs throttles re-triggering regardless of run outcome」模拟的正是 onRun 返回 {ok:false, error:"llm failed"} 后、间隔内再次触发被拦截——v0.7.27 上刚实测通过。

    所以 Sample B 的 11.8min 空档,代码上排除了「llm failed 绕过 lastRunAt」这一假设。更可能的指向是调度器实例被重建、lastRunAt 归零——这不需要完整进程重启,也不需要配置文件 mtime 变化(面板/API 热应用、宿主侧插件重载这类内存级重初始化,你 12:25–12:50 的 mtime 扫描捕捉不到)。能否麻烦补一个证据:12:33–12:45(本地)窗口内 dsh-mneme/DSH 的 logger 有没有调度器重建/插件 re-init 的痕迹?有它就能直接闭环。

    2. sleep 与 dreamMinIntervalMinutes:有意隔离

    auto 与 sleep 是两个独立调度器、独立闸门(auto 用 dreamMinIntervalMinutes,sleep 用 sleepMinIntervalHours),sleep run 从不触碰 auto 的 lastRunAt。设计意图是两种触发语义本就不同(写入累积 vs 空闲触发),合闸会让一次空闲深整理把高频写入期的 auto 节流错位。如果你希望它计入 auto 间隔,加个开关即可,说一声。

    3. errors 数组未落库:属实,且比描述更严重

    确认了:整单拒绝路径上 finish() 只传 error: "invalid decisions: N errors",validateDecisions 的 errors 数组和已解析的 decisions 都没有落库(saveDreamRun 的 decisions 列在此路径为空)。这正好放大了发现1 的定位成本——coverage 误伤时只能靠手动降配试出来。这个残留我们列入 follow-up,连同发现1 的 coverage 误伤面、发现2 的 archive 语义越界一起评估(默认值/文档/prompt 可能要三处都动),评估完开 issue 同步你。

  16. Anans-Amarre commented on Sep 9, 2026

    @Anans-Amarre
    Collaborator

    三个发现(coverage 误伤 / archive 类型越界 / 失败审计缺明细)已汇总为 follow-up issue #104(v0.8 硬化批次,含拟改方向)——coverage 不足拟降级为 degraded 而非整单拒绝、长保留类型加 archive 护栏、失败路径 errors/decisions 落库。欢迎继续补充数据,评估完会在 #104 里给结论。

  17. 483218131 commented on Sep 9, 2026

    @483218131
    ContributorAuthor

    三点跟进:

    1. H2 撤回 + 源码复核确认:本地 0.7.22 复核 src/dream.js——lastRunAt = Date.now() 在 setTimeout 回调内、onRun() 之前无条件同步执行(本地行号 L499/L493,与你们 v0.7.27 的 L553/L547 逻辑一致),onRun 抛异常不影响已打点。此前怀疑的「llm failed 异常路径漏记」不成立,收回该假设。

    2. 调度器实例重建方向与本地代码结构吻合:lastRunAt 是 dream 工厂闭包变量(L479 let lastRunAt = 0),src/index.js:291 每次 activate 重新 setDreamHook——任何插件 re-init 必然归零。关于你们要的 12:33-12:45 窗口日志:补不了,如实说明——mneme 的 activate/dispose 路径零日志语句(grep 全 src 仅 dream.js:517 一处 baseline refresh warn),DSH 宿主侧 ~/.dsh 下无 logs 目录、web 日志走 stdout 无文件落盘,窗口内 GUI 操作也无法精确回忆。建议:给 activate/dispose 各加一条 debug 日志(一行改动),下个连发样本即可直接闭环;我这边 sentinel 探针(5min 粒度,检测 <30min auto 连发)持续值守,抓到新连发会立即对照当时的操作。

    3. auto/sleep 独立闸门收到,按「设计如此」归档该边界样本,暂不需要合闸开关。autoDream 宽容路径补强(0.7.22 实测三发现):coverage 误伤 / archive 类型越界 / 失败审计缺明细 #104 收到,观察期持续供数。

  18. 483218131 commented on Sep 10, 2026

    @483218131
    ContributorAuthor

    Sample C(进程重启场景,第二次独立确认 + 一个意外发现):

    09-10 本地 09:22:44 dsh web 重启(晨间开机启动)。旧进程最后一条 auto run 为
    01:05:23.640Z degraded(auto),新进程第一条为 01:28:27.089Z gap=23.1min——
    与 Sample A 同构(横跨重启边界,新实例 lastRunAt=0 直接放行)。

    增量:这条 run 的 status="reconcile"(此前未见过),且恰好只在重启后第一条
    auto run 出现。若 reconcile 语义是「实例初始化/状态不一致时的对账 run」,则
    dream_runs 的 reconcile 行本身就是 re-init 的可观测指纹——定位重建时刻无需
    依赖日志:SELECT * FROM dream_runs WHERE status='reconcile'。请确认 reconcile
    的确切语义;若相符,建议文档标注,并可作为 lastRunAt 持久化(issue #89 修复
    方向)的辅助判定依据。

    旁证:重启前后各 12h+ 的 auto 间隔全部合规(74.5/677/36min),异常仅发生在
    重启时刻,支持「闸门逻辑本身正确、失效仅由实例重建引起」的结论。

  19. Anans-Amarre commented on Sep 10, 2026

    @Anans-Amarre
    Collaborator

    感谢 Sample C,第二次独立的重启边界确认很有价值 👍 源码复核后两点回应:

    1. reconcile 的确切语义:是「部分提交冲突」的逐 run 审计状态,不是「实例初始化/对账」

    src/dream.js 里 reconcile 的判定是 partial = conflicts.length > 0 || failures.length > 0——决策已通过校验、但提交阶段有部分未落地(CAS 冲突/回滚),与实例重建无关:

    • src/dream.js(status 注释):reconcile — some decisions validated but did not commit (CAS/rollback)
    • src/store.js dream_runs.status 列注释同为 ok | noop | degraded | reconcile | failed

    所以「reconcile 行 = re-init 指纹」这个推断不成立。它出现在重启后第一条 auto run,更合理的解释是:新进程冷启动后第一批 commit 与并发写入撞上 CAS 冲突(快照是启动时拍的,提交时基线已变),部分决策回滚 → 记 reconcile。这是冲突信号,恰好落在重启边界上,而非初始化信号。因此不建议用它做 lastRunAt 持久化的辅助判定依据。

    2. 重启穿透的根因就是 lastRunAt 未持久化,修复方向不变

    src/dream/sleep.js 中 let lastRunAt = 0; 是纯内存态,进程重启即归零,min-interval 闸门(lastRunAt > 0 && at - lastRunAt < minInterval)在重启后第一条 auto run 处自然放行——Sample A / Sample C 的时间戳与代码完全吻合,机制确认成立。

    自然的修法:store 已持久化 dream_runs.created_at(且有索引 idx_dream_runs_created),启动时用最近一条 auto run 的 created_at 预填 lastRunAt 即可,无需依赖 reconcile。这条已列入 #89 的修复方向,会与 #104 的硬化批次一起排期;排期落地后会在 #104 里给结论。

  20. added a commit that references this issue on Sep 23, 2026
  21. moved this from 🔨 In Progress to ✅ Done in dsh-mneme 任务看板on Sep 23, 2026
  22. added a commit that references this issue on Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions