Files
HologramAgent-DSH-Memory-Port/docs
jianf 82b754661d fix(memory-b): W11d 两处短路 + W12 abort/失败分流 + 文档订正
W11d —— 你说得对,不是一行。两处短路各能独立吃掉提示
  ① lib/index.js contextForStep: `if (hits.length === 0) return ''`
  ② lib/recall.js renderRecall:  `if (!Array.isArray(hits) || hits.length === 0) return ''`

修法(按你给的形态):「有没有条目」与「有没有话要说」分开判:
    const notice = degradationNotice(hits)
    if (lines.length === 0 && notice === '') return ''
不写成「notice 非空就总出 header」——那样健康路径零命中回合每轮灌一行记忆头,
白烧 token 还教模型忽略这一段。

四场景实测(真 Context/SystemPrompt/ToolRuntime/真 adapter/真 http)
  A 降级+命中   -> empty=false failed=true  entries=true   54ms
  B 降级+零命中 -> empty=false failed=true  entries=false  51ms   ← W11d
  C1 健康+命中  -> empty=false failed=false entries=true    7ms
  C2 健康+零命中-> empty=true  failed=false entries=false   2ms   ← 正确沉默
与你实测四条一致。

W11d 判据 + **两条独立变异**
  W11d†a 只把 renderRecall 短路改回去  -> not ok 50 - W11d
  W11d†b 只把 contextForStep 短路加回来 -> not ok 50 - W11d
两条分别实测,证明「只修一处无效」,也证明每处都有人守着。

W12 abort 与 embed_error 分流(你上一条四项里的 ③)
原来两者都报 embed_error ⇒ 用户主动取消时提示「搜索失败」,会诱导无谓重试。
现在:options.signal.aborted 或 AbortError ⇒ 'aborted',其余 ⇒ 'embed_error'。
degradationNotice 新增 aborted 分支,措辞是「was cancelled」而不是「FAILED」。
W12 判据:两种标签、两种文案、必须不同。
变异(合并成 'embed_error')→ W10b / W11c / W12 三条同时红。
已同步 W10b/W11c/W11d 的断言(它们原假设 embed_error)。

② README:224 订正
`recall('etcd 端口', 5)` 已失效——第二形参变成 options 后,数字不是 {topK},
默认值生效。改为 `recall(q, { topK: 5 })`,并补 await(commit/recall 都是 async),
另加一段说明这个静默失效,以及「不 await 也能写」正是 W6 守着的脆弱假设。

④ docs/phase-b-criteria.md 补 W 组(原 0 命中)
新增 §W 表:W1/W5/W4/W6/W7/W8/W9/W9b/W10/W11/W11d/W12,
每条写「逼出它的 bug」与变异,并记下这组的共同教训:
判据用替身/短路/自造形状时,验的是替身而不是真代码。
(W9 假 embedder、W11d 两处短路互相掩盖、V3b 自造 2 段 batch 是同一形态。)

★ 你那条附带发现我也记下了:探针把成功响应写成 {vector:[...]},
而适配器只认 {embedding} 或 {data[0].embedding}(lib/embed/http.js:199-202),
于是「健康路径」实际一直在走 embed_error,C1 误报 FAILED。
自造 mock 时形状必须照适配器真读的字段抄,否则验的是自己的 mock。

验证
- npm test 113/113(108 + W11d/W11d†a/W11d†b + W12/W12†)
- npm run check 10/10
- 提交前连跑 3 次 test + 3 次 check,6 次全绿
2026-10-30 11:24:17 +08:00
..