摘要 :181 轮 autoDream 里 169 轮失败 (no json array in llm output 146 轮、invalid decisions: 1 errors 22 轮),只有 5 轮真正合并过任何东西。
本机定位到两处互相独立的缺陷 ,并各自验证了修复:
主缺陷(本轮新发现) :模型把 36 位 UUID 回填成前 8 位前缀 ,而校验器用 snapshot.get(id) 做整串精确匹配 → 逐条 unknown id → claimed=0 → 覆盖率闸整单拒绝 。模型的语义判断其实是对的(实测 78/78 个前缀都能精确对应到窗口里的真实记忆)。
次缺陷(承接 [Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 ) :dreamReasoningEffort 默认 none 会让 harness 补上模型的 defaultEffort(v4-flash 系 = high),叠加 dreamMaxTokens 的 schema 默认值 32768 ,思考型模型把预算烧在推理上 → 正文为空。
关键放大器 :这两类失败既不落库也不落日志 (见「观测缺口」),所以问题被藏了 140+ 轮。
修复后首轮即成功:status=degraded, applied=34, merge-archived=62。
环境
项
值
DSH
0.1.5-rc.1(win32,web profile)
@modusensus/dsh-mneme
0.7.31 (npm latest,GET /api/dsh-mneme/info 确认)
agent 默认模型
deepseek-official / deepseek-flash(~/.dsh/settings.yaml → agent-default-model)
dream 路由(复现期)
先 deepseek-official / deepseek-v4-flash(思考型,声明 off/low/high/max,default = high ),后落到 agent 默认 deepseek-flash
dream 生效预算
dreamMaxTokens = 32768(schema 默认值,未配置 )
候选窗口
dreamMaxSnapshotSize = 200(默认)
记忆库
总 1010 / 活跃 376 / 归档 634
现象
dream_runs 全量(2026-08-28 → 2026-09-12 01:41,共 181 轮):
status
轮数
applied 合计
说明
failed
169 (93.4%)
0
全部无产出
ok
8
6
其中 7 轮是「模型零有效决策」被隐式 keep 补齐(见附录 B)
reconcile
3
100
决策部分未提交(CAS/回滚)
degraded
1
34
修复补丁落地后的首轮
失败原因:
error
轮数
no json array in llm output
146
invalid decisions: 1 errors
22
llm failed
1
真正合并过的轮次全库只有 5 轮 ,累计 merge-archived 295 条。
缺陷 1 的实证:8 位前缀 vs 36 位整串
在失败路径落库的校验明细里(见「观测缺口」,这是本机打补丁后才拿到的):
errors = ['explicit decision coverage 0% < minimum 1%']
skipped = 27 条
decision[0] merge ids=5 → unknown id "f77a0d17"; …(5 个全 unknown)
decision[1] merge ids=4 → unknown id "23178b3b"; …
…
三向归类(window_ids 取该轮 dream_runs.input 的 200 个 id;all_ids 取 memories 全表 1002 个 id):
分类
条数
在窗口内
0
不在窗口但在库中(活跃/归档)
0
库中根本不存在
77(去重后)= 全部
但被拒 id 的长度分布是 {8: 78} ,而窗口 id 与库中 id 的长度分布都是 {36: 200} / {36: 1002} 。把它们按「窗口 id 的前 8 位」匹配:
被拒 id 共 78 个,能在窗口前 8 位中找到的:78 / 78
f77a0d17 → f77a0d17-2586-41fe-a789-d69f4d1cee83
bc17c9ed → bc17c9ed-e84c-4b9f-bc36-48f8e9a6b3e0
fe6199e6 → fe6199e6-1933-4520-a028-9ceb2cf79011
而提示词给模型看的是完整 36 位 (src/dream.js:693 的聚类分支与 :706-708 的兜底分支都是 id=${m.id})。
⇒ 模型认对了要合并哪几条记忆,只是把 id 缩写成了前 8 位;校验器按整串查表,于是整轮成果被判无效。
缺陷 2 的实证:no json array in llm output
失败点是 src/dream.js:762-765:extractJsonArray(text) 返回 null → 整轮丢弃。
streamText 只收集 text-delta;思考型模型把 maxTokens 全花在 reasoning-delta 时正文为 0 —— 该机制已由 [Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 用 chunk 分布实测({reasoning-delta: 32768, text-delta: 0})并获维护者认可。
本机命中该模式的两个条件都成立:
(a) dreamMaxTokens 停在 schema 默认 32768 (src/config.js:67 的 .default(32768);[Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 只把 max 抬到 131072,默认值没抬 )。
(b) dreamReasoningEffort 默认 "none"(config.js:81),而 resolveDreamEffort 在 src/dream.js:392 直接短路 if (!configuredEffort || configuredEffort === "none") return null; → 不发送 reasoningEffort 字段 → harness 补上模型声明的 defaultEffort(v4-flash 系 = high )。即开箱默认配置恰好把思考预算拉满 ,也让 v0.7.26 声称的「defaultEffort 陷阱根治」默认不生效。
换非思考模型只压低、未消除 :把路由换到 deepseek-flash 后,失败率从 98.0% 降到约 50%(重启后 6 轮里仍有 3 轮 no json array),输入 8 万字符量级。所以预算/档位仍是活的隐患。
观测缺口(本机为了让根因可见,不得不先打补丁)
与 #104 的关系 :该 issue 已包含「失败审计缺明细 」这一项,本 issue 不主张它是新问题 ;这里提供的是可复现的量化证据(168 轮失败零现场、明细 warn 因写在 return 之后而不可达),可直接并入 #104 处理。
失败轮次不落任何可诊断信息 ,且日志路径不可达:
// src/dream.js:779-786
if ( ! ok ) {
logger ?. warn ?. ( `dsh-mneme dream: invalid decisions: ${ errors . join ( "; " ) } ` ) ;
return finish ( { ok : false , error : `invalid decisions: ${ errors . length } errors` , summary : false } ) ; // ← 不传 decisions
}
const skippedInvalid = skipped . length > 0 ;
if ( skippedInvalid ) {
logger ?. warn ?. ( `dsh-mneme dream: ${ skipped . length } invalid decision(s) skipped …` ) ; // ← 在 return 之后,永远执行不到
}
finish 在失败分支不传 decisions → 落库为 NULL;而 validateDecisions 明明已经把逐条明细 skipped: [{index, action, ids, error}] 返回给了调用方,却被丢弃 。
唯一的明细日志写在 return 之后 → 不可达 。
本机实测该插件的 ctx.logger.warn 也不上 stdout(终端里看不到 mneme 的任何日志)。
结果:146 + 22 = 168 轮失败,没有一轮留下「模型到底输出了什么」的痕迹。
根因
主因:校验器不接受 id 前缀
src/dream/decisions.js 的 validateDecisions 用整串匹配判定 id 合法性,任何未精确命中的 id 都记 unknown id 并把该条决策跳过(dreamSkipInvalid 默认 true)。当模型把所有 id 都缩写成 8 位前缀时,全部决策被跳过 → claimed.size = 0 → 覆盖率闸(explicit decision coverage 0% < minimum …)整单拒绝 → 整轮丢弃、applied=0。
这是「校验器过严 + 模型习惯」的组合缺陷 ,而且因为上文的观测缺口,运维侧完全看不出是 id 格式问题(错误串只有一句覆盖率,明细被丢)。
次因:预算与默认档位(承接 #9 )
见「缺陷 2 的实证」。
第三层:候选窗口(已由 #125 覆盖)
dreamMaxSnapshotSize(默认 200)按 updated_at DESC 截断候选,窗外旧记忆永不进视野。本机候选池 375 条、窗口覆盖约 53%,且写入速率可达单小时 140 条。此处不复述、不重复提交。
附属发现:memoryQualityFilter 的两个问题(与 dream 失败无关,但同属「静默归档 + 审计线索被销毁」)
机制 (src/service.js:960-978):每次写入由 LLM 打分(0–100,本机全库均分 79.2)并给出理由标签(low_quality / duplicate / meta / repetitive / short_content);score < archiveThreshold(默认 30 )即无条件归档 ,标签以并集写入,作为「为什么被归档」的审计痕迹。
本机实测(2026-09-12):
口径
条数
quality_score < 30
150
其中带 low_quality 标签
148
已归档
149
其中 importance ≥ 4
128 (126 条带标签)
问题 1:importance 完全不参与归档决策
150 条低分条目 100% 被归档 ,其中 51 条 importance=5、77 条 importance=4 —— 一条被标为最重要的记忆,会被一个 LLM 分数静默归档 :无感知、无通知、无豁免。建议 importance ≥ 4(或 ≥5)时只降权不归档,或至少归档时给出可见通知。
但这些低分条目并非误伤 :判断器自己给出的理由标签里 duplicate 占 79%(101/128) ,meta 37%、repetitive 34%,且无一条 short_content(说明不是按长度误杀)。它归档的确实多是重复/元信息。
真正的上游问题在 #127 :本机这 150 条低分条目的 source 几乎全是 session:*,即 autoSummarize 的蒸馏产物——每轮都在铸造注定被立即归档的条目,白烧 LLM 配额 。
问题 2:信号标签会被后续 update 覆盖
applyQualityDisposition 用并集 写标签(src/service.js:967),但更新路径是整组替换 :
memory_update(args) lib/tools.js:233
→ service.update(id, {…, tags}) src/service.js:1485
→ store.update(id, p) (p.tags 直接写入,不再并集)
于是任何带 tags 的更新都会冲掉判断器留下的 low_quality/duplicate/meta 等标记。实证对账 :150 条低分条目中 148 条带 low_quality,缺的 2 条恰好都是被 memory_update 改过的 ;而另一条创建后未再更新的同类条目标签完好。
后果:「这条记忆为什么被归档」变得不可追溯 —— 无法区分「质量门自动归档」「用户手动去重归档」「dream 合并归档」。本机就因此一度误判为"是被 dream 合并归档的",不得不额外排查全部 dream 轮次的 outcome/decisions 才排除。建议更新时把系统信号标签并集保留(用户自传的标签照常生效)。
附属发现 2:向量层从未配置 —— 五个功能静默失效,而 /semantic 报 ready: true
机制 (src/embedding.js:85-86 与 :111-112;local-embedder.js 的各后端同理):
const cfg = settings . getVectorConfig ( ) ;
if ( ! cfg ?. enabled || ! cfg . baseUrl || ! cfg . apiKey || ! cfg . model ) return ; // ← 静默返回
本机实测(2026-09-12) :
GET /api/dsh-mneme/vector-config → {"enabled": false, "baseUrl": "", "apiKey": "", "model": ""}
kv(user_settings) → 只有 external_api 与 feature_flags,没有任何向量配置
memories → 总 1035 / 无向量 1035(100%)
vector_meta → 空表(0 行)
llm_audit_logs → 435 行里没有一种与嵌入沾边的 operation_type
即一次嵌入请求都没有发出去过 (在第 86 行就 return 了),因此不产生任何错误日志 —— 连"未配置"这个事实都无处可查。
静默失效的五个功能 :
功能
表现
语义召回(向量检索)
无向量可搜,只能靠关键词 / BM25 兜底
searchSemanticDedup
开关为 true 但不生效
rerankEnabled
reranker: null
sleep 的冲突检测
被跳过(报告里写 "reason": "no usable vectors")
dream 的语义聚类预分组
退化到全量窗口列表兜底(dream.js:705-708)
对照:不依赖向量的功能是正常的 —— entities 121 行、recall_runs 2177 行、llm_audit_logs 435 行。所以问题精确定位在向量这一层。
问题:GET /api/dsh-mneme/semantic 报 ready: true ,但同一响应里 index.embeddedCount = 0 / totalCount = 346、modelHash / dimension / reranker 全为 null。ready 只反映"嵌入器对象已构造",不反映索引里有没有数据。建议:ready 结合 embeddedCount / totalCount 计算;并在 getVectorConfig() 为空时至少记一条 info/warn 。
补充 :本机原先 embedProvider 取 openai,而该分支必须 依赖上表那四个字段(index.js:210-213);把 embedProvider 改成 local/ollama 则走 local-embedder.js(本机已装 @huggingface/transformers + onnxruntime-node,但 HF 模型缓存为空,首次使用需下载)。
本 issue 涉及的「绿色假阳性」共性清单
这一类问题在本仓库已经出现 5 次,模式完全一致:状态显示正常,功能实际是死的。
#
表面状态
实际情况
1
/semantic → ready: true
向量索引 0/346,嵌入器从未被配置
2
面板「测试连通性」→ 通过
maxTokens: 1024 + 一句提示词;空 reply 也算通过;与 dream 路径无关
3
dream_runs.status 静默记 ok
模型零有效决策被隐式 keep 补成 200 条(dreamMinExplicitCoverage: 0 时)
4
llm_audit_logs 有 435 行
input_tokens / output_tokens / cost_usd 全部为 0
5
记忆带 low_quality 标签、看似可审计
一次 memory_update 就把信号标签冲掉
建议给这五处各加一条"真实状态"断言或启动自检(例如启动时校验 embeddedCount > 0,否则明确警告),比逐个修更省事。
修复与验证
本机通过两个本地补丁验证(pnpm patch,合并进同一份 @modusensus__dsh-mneme@0.7.31.patch):
让失败可观测 :!ok 分支改为 finish({ …, decisions: [{ _validationFailed: true, errors, skipped }] }),并把 skipped 明细的 warn 提到 return 之前。
让前缀可解析 :在 validateDecisions 入口把「唯一前缀」解析回完整 id(git 短哈希式 )——仅在 snapshot 内解析,前缀有歧义或匹配不到则原样保留、仍由后续校验如实报 unknown id;就地改写 ids / keepSource / winner / loser,使下游 applyDecisions 也拿到完整 id。
修复后首轮 (补丁需重启 dsh 生效;cordis.patch.yml 那类配置是 live 重载,插件源码不是):
status = degraded (skipInvalid 语义:单条非法只跳过、合法子集照常应用)
applied = 34
decisions= keep=103 merge=31 archive=2 conflict=1
outcome = merge-archived: 62 · merge-keep: 31 · conflict-winner: 1 · conflict-archived: 1
库况 = 活跃 436 → 376(−60)/归档 566 → 634(+68) ← 单轮
全库成功合并轮次复盘:
#
时间(北京)
status
applied
merge-archived
1
08-28 21:48
ok
6
6
2
09-10 20:39
reconcile
31
90
3
09-10 20:43
reconcile
35
65
4
09-12 00:52
reconcile
34
72
5
09-12 01:41
degraded
34
62
前 4 轮都发生在 deepseek-v4-flash 时代(那时模型回填完整 id),第 5 轮是首次在 deepseek-flash 上成功、也是首次由前缀解析解开的。
复现
dream 路由指向一个会缩写 id 的模型(本机 deepseek-flash);
记忆库涨到候选窗口 200 条、单轮输入 8 万字符量级;
等 autoDream 触发,查 dream_runs:status='failed'、error='invalid decisions: 1 errors'、applied=0、decisions IS NULL;
想看到真正的原因,需要先让失败路径落库(见上文补丁 1)——否则 skipped 明细不可得。
窗口口径(与 src/dream.js:600-612 一致):活跃非 summary、按 updated_at 倒序取前 dreamMaxSnapshotSize 条。
建议(按优先级)
校验器接受「唯一前缀」 :在 validateDecisions 入口把短 id 解析回完整 id(git 短哈希式),歧义/无匹配才判 unknown id。这是本机解锁合并的决定性一步,且不改变任何用户可见语义 。
(替代方案:在 CONSOLIDATION_PROMPT 里明确要求「原样复制 36 位完整 id」,并把 few-shot 示例里的 "m1"/"m2" 换成真实 UUID —— 当前示例的短占位符很可能在诱导模型缩写。)
失败路径必须落 decisions / skipped :把 validateDecisions 已经返回的逐条 {index, action, ids, error} 写进 dream_runs.decisions,并把那行 skipped warn 提到 return 之前。否则上百轮失败级的故障完全没有现场。
把 dreamMaxTokens 的 schema 默认值从 32768 抬到 131072 (或按输入长度自算预算)。[Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 只放开了 max,default 未动,等于把已修好的问题留给了默认配置。
dreamReasoningEffort 默认不应是 none :对"必须产出 JSON"的后台调用,默认发送该模型支持的最低档,而不是省略字段让 defaultEffort 顶上。
resolveDreamEffort 对 none 的短路(dream.js:392)应区分"用户显式选了 none"与"默认未配置" :前者尊重省略语义,后者取最低支持档。这是 v0.7.26 那处"defaultEffort 陷阱根治"真正生效的前提。
修复 llm_audit_logs 的 token/cost 恒为 0 (见附录 B)。这是定位本类问题唯一的量化面。
可选:失败轮持久化原始 LLM 文本(或至少 raw length + 前 300 字节),现在只进不可达的日志。
memoryQualityFilter:让 importance 参与归档决策 (≥4/≥5 只降权不归档,或归档时给出可见通知);并让系统信号标签不被 update 覆盖 。详见「附属发现」。
向量层:让 /semantic 的 ready 反映索引覆盖度 (embeddedCount / totalCount),并在嵌入器未配置(getVectorConfig() 为空)时至少记一条 info/warn。否则"从未配置"会让五个依赖向量的功能长期静默失效,而状态页一直是绿的。详见「附属发现 2」。
可选(更强):把「附属发现 2」里那张绿色假阳性清单 做成启动自检 —— 状态页只在功能真能工作 时才显示绿。
与既有 issue 的关系
附录 A:生效配置
GET /api/dsh-mneme/features 的 effective(2026-09-11 实测):
{
"autoDream" : true , "autoInject" : true , "autoSummarize" : true , "hotMemoryEnabled" : true ,
"sleepModeEnabled" : true , "heatEnabled" : false , "conflictFreezeEnabled" : false ,
"allowCrossTypeMerge" : false , "dreamSkipInvalid" : true , "llmAudit.enabled" : true ,
"memoryQualityFilter.enabled" : true ,
"dreamMinIntervalMinutes" : 0 , "dreamMaxTokens" : 32768 ,
"dreamProvider" : " " , "dreamModel" : " " ,
"distillMaxChars" : 24000 , "rerankEnabled" : true , "searchSemanticDedup" : true
}
另注:面板的「测试连通性」按钮不能代表巩固可用 ——它 maxTokens: 1024、提示词仅 Reply with exactly one word: ok,且只在流报错时返回 ok:false (空 reply 也算通过)。作者注释已点破机制(16 会被 reasoning 全部吃掉,reply 恒为空):他们把测试按钮 的预算 16→1024 修了,却没动 dream 那条路径。
附录 B:审计方法与库况
方法 :把 memory.db + -wal + -shm 复制到临时目录后以只读方式打开,不触碰运行中的库 ,用 Python sqlite3 分析 dream_runs / llm_audit_logs / memories。
判定 keep 是模型输出还是代码补齐 :取 dream_runs.decisions 的 id 序列与 dream_runs.input 快照的 id 序列(updated_at DESC)做尾对齐 —— 代码 decisions.js:183 补的 keep 严格按快照顺序、落在数组末尾。实测:真实合并轮只有尾部对齐(补 1/6/1/3 条),空转轮从头就对齐(整条 200 条都是补的)。另:200 × 64 + 199(逗号) + 2(括号) = 13,001,即那批 keep=200 的 decisions 长度是确定性代码产物 ,不能当作模型行为指标。
判定 unknown id 的性质 :把被拒 id 分别与「窗口 id 集」「库中全表 id 集」比对,并额外做「窗口 id 前 8 位」匹配 —— 本机正是靠这一步把根因锁定到"8 位前缀"。
库况(2026-09-12 01:42):总 1010 / 活跃 376 / 归档 634。
llm_audit_logs 全部 435 行里(dream_consolidate 183 + summarize_compress 238 + dream_summarize 14),input_tokens / output_tokens / cost_usd 三项为 0 —— runAuditedLlm 的 reportUsage 从未拿到 usage,token 计量与成本核算完全失效(见建议 6)。
环境
0.1.5-rc.1(win32,web profile)0.7.31(npm latest,GET /api/dsh-mneme/info确认)deepseek-official / deepseek-flash(~/.dsh/settings.yaml→agent-default-model)deepseek-official / deepseek-v4-flash(思考型,声明off/low/high/max,default = high),后落到 agent 默认deepseek-flashdreamMaxTokens = 32768(schema 默认值,未配置)dreamMaxSnapshotSize = 200(默认)现象
dream_runs全量(2026-08-28 → 2026-09-12 01:41,共 181 轮):failedokreconciledegraded失败原因:
no json array in llm outputinvalid decisions: 1 errorsllm failed真正合并过的轮次全库只有 5 轮,累计
merge-archived295 条。缺陷 1 的实证:8 位前缀 vs 36 位整串
在失败路径落库的校验明细里(见「观测缺口」,这是本机打补丁后才拿到的):
三向归类(
window_ids取该轮dream_runs.input的 200 个 id;all_ids取memories全表 1002 个 id):但被拒 id 的长度分布是
{8: 78},而窗口 id 与库中 id 的长度分布都是{36: 200}/{36: 1002}。把它们按「窗口 id 的前 8 位」匹配:而提示词给模型看的是完整 36 位(
src/dream.js:693的聚类分支与:706-708的兜底分支都是id=${m.id})。⇒ 模型认对了要合并哪几条记忆,只是把 id 缩写成了前 8 位;校验器按整串查表,于是整轮成果被判无效。
缺陷 2 的实证:
no json array in llm outputsrc/dream.js:762-765:extractJsonArray(text)返回null→ 整轮丢弃。streamText只收集text-delta;思考型模型把maxTokens全花在reasoning-delta时正文为 0 —— 该机制已由 [Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 用 chunk 分布实测({reasoning-delta: 32768, text-delta: 0})并获维护者认可。dreamMaxTokens停在 schema 默认 32768(src/config.js:67的.default(32768);[Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 只把max抬到 131072,默认值没抬)。dreamReasoningEffort默认"none"(config.js:81),而resolveDreamEffort在src/dream.js:392直接短路if (!configuredEffort || configuredEffort === "none") return null;→ 不发送reasoningEffort字段 → harness 补上模型声明的defaultEffort(v4-flash 系 = high)。即开箱默认配置恰好把思考预算拉满,也让 v0.7.26 声称的「defaultEffort 陷阱根治」默认不生效。deepseek-flash后,失败率从 98.0% 降到约 50%(重启后 6 轮里仍有 3 轮no json array),输入 8 万字符量级。所以预算/档位仍是活的隐患。观测缺口(本机为了让根因可见,不得不先打补丁)
失败轮次不落任何可诊断信息,且日志路径不可达:
finish在失败分支不传decisions→ 落库为NULL;而validateDecisions明明已经把逐条明细skipped: [{index, action, ids, error}]返回给了调用方,却被丢弃。return之后 → 不可达。ctx.logger.warn也不上 stdout(终端里看不到 mneme 的任何日志)。根因
主因:校验器不接受 id 前缀
src/dream/decisions.js的validateDecisions用整串匹配判定 id 合法性,任何未精确命中的 id 都记unknown id并把该条决策跳过(dreamSkipInvalid默认true)。当模型把所有 id 都缩写成 8 位前缀时,全部决策被跳过 →claimed.size = 0→ 覆盖率闸(explicit decision coverage 0% < minimum …)整单拒绝 → 整轮丢弃、applied=0。这是「校验器过严 + 模型习惯」的组合缺陷,而且因为上文的观测缺口,运维侧完全看不出是 id 格式问题(错误串只有一句覆盖率,明细被丢)。
次因:预算与默认档位(承接 #9)
见「缺陷 2 的实证」。
第三层:候选窗口(已由 #125 覆盖)
dreamMaxSnapshotSize(默认 200)按updated_at DESC截断候选,窗外旧记忆永不进视野。本机候选池 375 条、窗口覆盖约 53%,且写入速率可达单小时 140 条。此处不复述、不重复提交。附属发现:
memoryQualityFilter的两个问题(与 dream 失败无关,但同属「静默归档 + 审计线索被销毁」)机制(
src/service.js:960-978):每次写入由 LLM 打分(0–100,本机全库均分 79.2)并给出理由标签(low_quality/duplicate/meta/repetitive/short_content);score < archiveThreshold(默认 30)即无条件归档,标签以并集写入,作为「为什么被归档」的审计痕迹。本机实测(2026-09-12):
quality_score < 30low_quality标签importance ≥ 4问题 1:
importance完全不参与归档决策150 条低分条目 100% 被归档,其中 51 条
importance=5、77 条importance=4—— 一条被标为最重要的记忆,会被一个 LLM 分数静默归档:无感知、无通知、无豁免。建议importance ≥ 4(或 ≥5)时只降权不归档,或至少归档时给出可见通知。问题 2:信号标签会被后续
update覆盖applyQualityDisposition用并集写标签(src/service.js:967),但更新路径是整组替换:于是任何带
tags的更新都会冲掉判断器留下的low_quality/duplicate/meta等标记。实证对账:150 条低分条目中 148 条带low_quality,缺的 2 条恰好都是被memory_update改过的;而另一条创建后未再更新的同类条目标签完好。后果:「这条记忆为什么被归档」变得不可追溯 —— 无法区分「质量门自动归档」「用户手动去重归档」「dream 合并归档」。本机就因此一度误判为"是被 dream 合并归档的",不得不额外排查全部 dream 轮次的
outcome/decisions才排除。建议更新时把系统信号标签并集保留(用户自传的标签照常生效)。附属发现 2:向量层从未配置 —— 五个功能静默失效,而
/semantic报ready: true机制(
src/embedding.js:85-86与:111-112;local-embedder.js的各后端同理):本机实测(2026-09-12):
即一次嵌入请求都没有发出去过(在第 86 行就
return了),因此不产生任何错误日志 —— 连"未配置"这个事实都无处可查。静默失效的五个功能:
searchSemanticDeduptrue但不生效rerankEnabledreranker: null"reason": "no usable vectors")dream.js:705-708)对照:不依赖向量的功能是正常的 ——
entities121 行、recall_runs2177 行、llm_audit_logs435 行。所以问题精确定位在向量这一层。问题:
GET /api/dsh-mneme/semantic报ready: true,但同一响应里index.embeddedCount = 0 / totalCount = 346、modelHash/dimension/reranker全为null。ready只反映"嵌入器对象已构造",不反映索引里有没有数据。建议:ready结合embeddedCount / totalCount计算;并在getVectorConfig()为空时至少记一条 info/warn。本 issue 涉及的「绿色假阳性」共性清单
这一类问题在本仓库已经出现 5 次,模式完全一致:状态显示正常,功能实际是死的。
/semantic→ready: truemaxTokens: 1024+ 一句提示词;空 reply 也算通过;与 dream 路径无关dream_runs.status静默记okdreamMinExplicitCoverage: 0时)llm_audit_logs有 435 行input_tokens/output_tokens/cost_usd全部为 0low_quality标签、看似可审计memory_update就把信号标签冲掉建议给这五处各加一条"真实状态"断言或启动自检(例如启动时校验
embeddedCount > 0,否则明确警告),比逐个修更省事。修复与验证
本机通过两个本地补丁验证(pnpm patch,合并进同一份
@modusensus__dsh-mneme@0.7.31.patch):!ok分支改为finish({ …, decisions: [{ _validationFailed: true, errors, skipped }] }),并把skipped明细的 warn 提到return之前。validateDecisions入口把「唯一前缀」解析回完整 id(git 短哈希式)——仅在 snapshot 内解析,前缀有歧义或匹配不到则原样保留、仍由后续校验如实报unknown id;就地改写ids/keepSource/winner/loser,使下游applyDecisions也拿到完整 id。修复后首轮(补丁需重启 dsh 生效;
cordis.patch.yml那类配置是 live 重载,插件源码不是):全库成功合并轮次复盘:
前 4 轮都发生在
deepseek-v4-flash时代(那时模型回填完整 id),第 5 轮是首次在deepseek-flash上成功、也是首次由前缀解析解开的。复现
deepseek-flash);dream_runs:status='failed'、error='invalid decisions: 1 errors'、applied=0、decisions IS NULL;skipped明细不可得。窗口口径(与
src/dream.js:600-612一致):活跃非 summary、按updated_at倒序取前dreamMaxSnapshotSize条。建议(按优先级)
validateDecisions入口把短 id 解析回完整 id(git 短哈希式),歧义/无匹配才判unknown id。这是本机解锁合并的决定性一步,且不改变任何用户可见语义。(替代方案:在
CONSOLIDATION_PROMPT里明确要求「原样复制 36 位完整 id」,并把 few-shot 示例里的"m1"/"m2"换成真实 UUID —— 当前示例的短占位符很可能在诱导模型缩写。)decisions/skipped:把validateDecisions已经返回的逐条{index, action, ids, error}写进dream_runs.decisions,并把那行skippedwarn 提到return之前。否则上百轮失败级的故障完全没有现场。dreamMaxTokens的 schema 默认值从 32768 抬到 131072(或按输入长度自算预算)。[Bug] autoDream 在记忆量较大且路由为思考型模型时 100% 失败:no json array in llm output #9 只放开了max,default未动,等于把已修好的问题留给了默认配置。dreamReasoningEffort默认不应是none:对"必须产出 JSON"的后台调用,默认发送该模型支持的最低档,而不是省略字段让defaultEffort顶上。resolveDreamEffort对none的短路(dream.js:392)应区分"用户显式选了 none"与"默认未配置":前者尊重省略语义,后者取最低支持档。这是 v0.7.26 那处"defaultEffort 陷阱根治"真正生效的前提。llm_audit_logs的 token/cost 恒为 0(见附录 B)。这是定位本类问题唯一的量化面。raw length+ 前 300 字节),现在只进不可达的日志。memoryQualityFilter:让importance参与归档决策(≥4/≥5 只降权不归档,或归档时给出可见通知);并让系统信号标签不被update覆盖。详见「附属发现」。/semantic的ready反映索引覆盖度(embeddedCount / totalCount),并在嵌入器未配置(getVectorConfig()为空)时至少记一条 info/warn。否则"从未配置"会让五个依赖向量的功能长期静默失效,而状态页一直是绿的。详见「附属发现 2」。与既有 issue 的关系
no json array),已修方案 A/B。本 issue 是"修复在默认配置下不生效"的补充,并补上"换非思考模型只压低未消除"的实测。resolveRouteconfig-first 已修,本机路由确实按配置生效,不涉及。updated_at时间窗口(实测:45 对高相似候选 0 对能同时进窗口) #125(open):候选窗口盲区。本 issue 不重复提交,仅作为第三层引用。memory_delete传截断 id 时静默空转 —— 同一类「截断 id」问题,当时修的是 delete 路径;本 issue 报的是校验器路径(validateDecisions只认整串)。建议统一处理(或在两处都做唯一前缀解析)。invalid decisions整单拒绝。本机那 22 轮同一错误串属同一族,但成因不同(这里是 id 格式,不是模型能力),故不重复主张。turn/end都触发且无产出上限——建议新增节流、条数上限与同会话主题抑制(实测单会话日铸 198 条) #127(open):写入洪泛,是"重复不断产生"的上游放大器。autoReindexOnBoot的模型指纹门 +needsEmbedding未过滤归档行,导致部分记忆的 embedding 永久缺失 #128(open):其附录 A 提到/features的effective优先于cordis.patch.yml。补充实测:该"优先"只对下次启动成立(src/index.js:146把 kvfeature_flags合到配置之上,而index.js:129-130注明只在下次启动生效);本机 kv 里存着"dreamProvider":""/"dreamModel":"",重启后会把 patch 里的路由静默清空、回落 agent 默认模型(已实测POST /api/dsh-mneme/test-model返回的modelId从deepseek-v4-flash变为deepseek-flash)。建议effective区分live/nextBoot两个口径。附录 A:生效配置
GET /api/dsh-mneme/features的effective(2026-09-11 实测):{ "autoDream": true, "autoInject": true, "autoSummarize": true, "hotMemoryEnabled": true, "sleepModeEnabled": true, "heatEnabled": false, "conflictFreezeEnabled": false, "allowCrossTypeMerge": false, "dreamSkipInvalid": true, "llmAudit.enabled": true, "memoryQualityFilter.enabled": true, "dreamMinIntervalMinutes": 0, "dreamMaxTokens": 32768, "dreamProvider": "", "dreamModel": "", "distillMaxChars": 24000, "rerankEnabled": true, "searchSemanticDedup": true }另注:面板的「测试连通性」按钮不能代表巩固可用——它
maxTokens: 1024、提示词仅Reply with exactly one word: ok,且只在流报错时返回ok:false(空 reply 也算通过)。作者注释已点破机制(16 会被 reasoning 全部吃掉,reply 恒为空):他们把测试按钮的预算 16→1024 修了,却没动 dream 那条路径。附录 B:审计方法与库况
memory.db+-wal+-shm复制到临时目录后以只读方式打开,不触碰运行中的库,用 Pythonsqlite3分析dream_runs/llm_audit_logs/memories。keep是模型输出还是代码补齐:取dream_runs.decisions的 id 序列与dream_runs.input快照的 id 序列(updated_at DESC)做尾对齐 —— 代码decisions.js:183补的 keep 严格按快照顺序、落在数组末尾。实测:真实合并轮只有尾部对齐(补 1/6/1/3 条),空转轮从头就对齐(整条 200 条都是补的)。另:200 × 64 + 199(逗号) + 2(括号) = 13,001,即那批keep=200的decisions长度是确定性代码产物,不能当作模型行为指标。unknown id的性质:把被拒 id 分别与「窗口 id 集」「库中全表 id 集」比对,并额外做「窗口 id 前 8 位」匹配 —— 本机正是靠这一步把根因锁定到"8 位前缀"。llm_audit_logs全部 435 行里(dream_consolidate183 +summarize_compress238 +dream_summarize14),input_tokens/output_tokens/cost_usd三项为 0 ——runAuditedLlm的reportUsage从未拿到 usage,token 计量与成本核算完全失效(见建议 6)。