771a053eea
docs(CL-1F): restore A1 semantics and cite V7 logs for G1
...
A1: revert to the 06155d4 wording (recallBlocks stays synchronous, a new
async recallBlocksHybrid is added). The previous basis claimed the async
pre-step handler justified making recallBlocks async, which the code
contradicts: lib/recall.js:229 exports a sync recallBlocks with zero awaits,
and lib/recall.js:613 is the async wrapper.
G1: replace the stale count with the readings already present in the V7
evidence logs (/tmp/v7-order-20261101-173334-t1..t3.log = npm test,
225/225/0; c1..c3.log = npm run check, 12/12), noting that phase A is a
subset of the whole-suite run. No new test/check run was performed.
2026-11-01 17:52:52 +08:00
7fc3d46b9a
docs(CL-1): revert V2 documentation changes
2026-11-01 17:41:52 +08:00
d57c9dc798
feat(memory-b): port V2 non-exact fusion weights
...
Also clear three stale counts the release train authorised: criteria A1
wording (sync + hybrid), criteria G1 counts (173/12), README test-site count.
2026-11-01 12:01:22 +08:00
99d0670ebe
fix(memory-b): 返工 ③④ —— trigger 直判据 + skipped_dim 不再是假字段
...
③ trigger 补直写判据(此前只有间接覆盖)
你的复核准确:摘掉 trigger 的 WHEN 会让 F3/D3 红,所以「间接有守卫」——
但没有一条盯 raw-writer。而 trigger 存在的全部理由就是防 raw-writer
(修复脚本、迁移、运维手敲 sqlite3)。putBlock 自己就能把列写对,
所以真实风险是「trigger 全没了也没人发现」。
MIG5 三条:
- raw UPDATE 把 vector 改成长度不符 ⇒ vector_dim 被拉回真值;清空则归 0
- raw INSERT 带错误 vector_dim ⇒ 被 trigger 覆盖为真值
- MIG5† 分别 DROP 两个 trigger,各自断言「谎言能存活」
真变异:从 DDL 里删掉整段 trigger ⇒ MIG5 前两条红(MIG5† 仍绿,因为它
主动 DROP,不依赖 DDL 是否存在 —— 这是两者分工正确的标志)。
④ skipped_dim 恒 0 是假字段,选 (a) 补维度过滤
你的诊断对,且比我的注释更狠:一个永远不能非零的字段,配合
`typeof === 'number'` 的断言,把 F4「维度失配与指纹失配分开计数」
做成了摆设。这与更早那处 assert.equal(skipped, 2) 是同一类。
实现:
- searchFts / 包含兜底都携带 dimension(来自 vector_dim 列)
- recallBlocks 新增 dimension 选项,在**空间过滤之后**做维度过滤
★ 顺序有意:先空间后维度,保证一块同时 stale 且窄时只计一次(记 space),
否则两个计数会重复计同一行、谁也加不出「被丢弃的块数」
- skippedDim 与 skipped 分别挂在返回数组上(F4 要分开)
- recallBlocksHybrid 把 space.dimension 注入 scoped,六个降级分支自动继承
- lastRecall.skipped_dim 改为读真实值
判据 F4b 四条:
- 同指纹不同宽度 ⇒ skippedDim=1 且 skipped=0(不串桶)
- 同时存在旧空间块与窄向量块 ⇒ skipped=1、skippedDim=1,不双计
- last_recall.skipped_dim 能非零
- F4b† 变异:删维度过滤 ⇒ skippedDim 归 0、窄向量被召回 ⇒ 红
实测:hits=[C 正常],skipped=1,skippedDim=1,两者都能非零。
一处测试构造的调整:窄向量块用 raw 写入而非 commit —— F8(正确地)
拒绝显式传错的宽度,所以「库里存在窄向量行」在现实中只能来自
历史数据或修复脚本,与 trigger 那条判据的假设一致。
文档
- criteria: F4 补「两个都能非零」与恒 0 字段的教训
- dev-notes 增第 6 类「恒为 0 / 恒为真的字段」,含自查问句
「这个字段的取值集合里,有没有一个能让我这条断言失败?」
验证
- npm test 162/162(155 + MIG5 三条 + F4b 四条)
- npm run check 10/10
- 干净副本实数见后续消息
2026-10-30 17:07:19 +08:00
160b5b5139
fix(memory-b): 三项验收整改 —— 去写死数字 / F8 硬报错 / blockStates 去 JSON.parse
...
一、skipped 断言不再写死数字
原 assert.equal(hits.skipped, 2) 属于「手抄数字」——与刚删掉的 README 计数同类:
fixture 一改就悄悄不再描述事实。
改为从构造库现算:expectedSkipped = counts.unknownSpace + counts.spaceMismatch,
并先断言 expectedSkipped > 0(防止变成「0 == 0」的假通过)。
二、F8 从「事后统计」改成「写侧硬报错」
原状:写侧维度校验只降级成「不嵌入」,坏向量仍能落库,靠 blockStates
的 dimensionMismatch 事后兜住 —— 那是 accounting,不是 validation,
与 criteria:165 的「硬报错」不符。
实现:putBlock 新增可选 expectedDimension,声明了宽度且不符 ⇒ 抛错不落库。
commit 在「调用方显式传 vector」时传 expectedDimension。
★ 两个入口必须分清,否则会与 W4 打架:
- 调用方 bug(显式传了宽度不对的向量)⇒ **必须抛**,否则它以
「块在、召回不了」的形式沉默很久
- provider 异常(embedding 返回宽度不对)⇒ 按 W4 降级为不嵌入,块仍写入
(把上游抖动抛给用户 = 让一次 provider 故障变成写入失败)
判据含三例:显式传错宽度抛;显式传对宽度通过;provider 返回错宽度降级不抛。
F8† 变异:把宽度检查改成恒不触发 ⇒ 坏向量落库 ⇒ 判据红。
三、blockStates 不再解析向量(性能账)
实测 2000 块 × 2048 维:
改前 354ms/次(其中 255ms 是纯 JSON.parse)
改后 4ms/次(约 90×)
而 renderIndex 每轮都调它 —— 这是挂在 pre-step 热路径上的固定成本。
做法:memory_blocks 新增 vector_dim 列(维度是行事实,不是负载内容),
- putBlock 写入时一并写
- 迁移时为存量行回填一次(只此一次,不在读路径)
- 加两个触发器保证 vector 与 vector_dim 永不脱节:
★ 理由:既然这一列的存在就是为了「不解析负载」,那每个写入者都背上了
保持一致的义务。漏一个写入者(修复脚本、测试 fixture、未来迁移)
就产生一行「维度是谎话」的数据 —— accounting 报它 active,
而召回静默跳过它。用触发器让这个状态**不可表示**,而不是只劝阻。
- 静态判据:blockStates 函数体内不得出现 JSON.parse(防以后「顺手简化」改回去)
四、embedFailure 记账
不进四态(它是单次调用事件,不是库状态),criteria 已补说明:
四态是库状态、从行派生;embedFailure 属于 recallBlocksHybrid 的 degraded,
由 W 组覆盖。塞进库状态会让「同一时刻读两次得到不同数字」,正是 D4 要避免的。
顺带修:F4† 的变异锚点因 blockStates 改写而失效(withMutatedCopy 显式报
「anchor not found」——这条守卫按设计响亮失败,没有静默变绿)。
验证
- npm test 148/148(143 + F8 四条 + 性能守卫一条)
- npm run check 10/10
- 性能实测见上
- 干净副本实数见后续消息
2026-10-30 16:29:05 +08:00
48c52497c2
feat(memory-b): F6 三段口径固化成判据 + A1b 降级记账
...
裁定:F6 选 (a),措辞改写,不留互斥读法
原措辞「仍可按文本调出、不参与向量召回」读起来像互斥。实际是三层:
「按文本调出」= store 级无空间浏览,**不是生产召回路径**。
不改 F2 去迁就 F6 字面自洽的理由:F2 是 fusion.go:495 同款安全缺陷修复,
优先级高于 F6 的措辞。所以 F6 改成三段并列,每段一条断言,
**同一个用例内全断言**:
数据层:指纹变更后旧块仍在库中 —— 块数不减、按 fp 分组两行都在
浏览层:store 级不带空间参数的文本查询仍能取到旧块
召回层:带 fingerprint 的生产召回路径必须把旧块过滤掉(F2 承担)
为什么必须一个用例:只断言「过滤掉了」会漏掉数据被误删
(删库在召回层看起来就是「过滤正确」);只断言「能调出」会把 F2 挡回去。
F6 判据 3 条
F6 三段全断言 + 反向对照(当前空间块两层都能取到,
防止断言因为「什么都找不到」而假成立)
F6†data 变异:putBlock 顺手 purge 其它空间的行走道 ⇒ 数据层断言红
F6†recall 变异:去掉空间过滤器 ⇒ 召回层断言红
两条变异各自只咬自己那一段,与 F6†data/†recall 的分工一致。
★ F6†data 的变异写法踩了三次坑(据实记)
1. 只写 form(没真改代码)—— 那不是变异,是自测
2. 注入 SQL 里的转义引号把测试文件写成 SyntaxError
3. 最终用 String.fromCharCode(39) 拼引号 + 数组 join 构造注入文本,
避开嵌套模板字符串的转义地狱
裁定 2:A1b 降级要记账
A1b 原先逐字节锁死 recallBlocks 源码,F2 合法地要改它,已降级为
「不传 fingerprint 时行为不变」。但降级后的 A1b-behaviour 只跑 golden
**前 5 个** case,变异 A(非精确带分数微调)下它可能是绿的。
已在判据注释写明:**结论不由 A1b 单独给出,由 A2 兜**
(A2 走全部 45 例、比精确分数);A1b-behaviour 只是烟测,不是权威。
同步进 memo:docs/dev-notes-mutation-criteria.md 增第 5 条
「判据降级时,覆盖面必须一起交代」—— 这不是失效模式,是记账纪律。
自查问句:这条判据降级后,原本由它兜住的结论现在由谁兜?
答案必须是一个具体的、覆盖更广的判据名。
验证
- npm test 131/131、npm run check 10/10
- 干净副本(git archive + link-dsh)实数见后续消息
2026-10-30 15:39:03 +08:00
425c441470
feat(memory-b): F5 固化为 fail-closed 判据 + F2 符号路空间过滤
...
裁定 1(F5):fail-closed,写侧直接拒收
docs/phase-b-criteria.md §0.2 从「待拍板」改为「已定」:
无名向量的块直接拒收,比放过再靠下游兜更能守住空间隔离;
安全语义优先,**不为 HA 的疑似缺陷同构**
(HA 的 block_recall.go:140 三个 && 让空 fp 被放行,这是要修正而非复刻的行为)。
落点分两层并写明「缺一层都不算落实」:
写侧 putBlock 抛错(已实现)
读侧存量行归 unknownSpace、不参与向量召回(F 组)
F5 判据(5 种输入形态 + 变异)
省略 / 空串 / undefined / 数字 / null 一律抛错,且都不落库
合法形态仍工作(无向量无 fp;有向量有 fp)
F5† 变异:删掉写侧守卫 ⇒ 无名向量被接受 ⇒ F5 必红
顺带修一处:lib/store.js 的 vectorFP 加 String() 归一。
原先变异摘掉守卫后会以 SQLite bind 错误崩,掩盖了真正的断言失败
(变异因为错误的原因变红 = 不算证据)。
裁定 2(F2):先过滤,后计数
F2 —— 空间过滤必须覆盖符号路,不只向量路
- store.searchFts 现在返回 fingerprint / hasVector,
两条 intake 路径(FTS 与包含兜底)都携带身份字段
★ 兜底路径第一版漏了这两个字段,导致过滤器看到 hasVector === undefined
而放行 —— 正是「过滤器只覆盖两个入口中的一个」那种 bug
- recallBlocks 新增 fingerprint 选项,在 pool 构建后统一过滤
刻意**不复刻** HA 的三连 &&:本仓 fail-closed,
带向量但无法命名空间的块一律排除(§0.2 裁定)
无向量的块不受影响(它们本来也不参与向量路,文本仍可检索)
- recallBlocksHybrid 在函数入口解析一次空间并注入 options,
六个降级分支全部经 scoped 调用 —— 逐调用点设置会在加第七个分支时漏掉一条
F2 判据(4 条行为 + 2 条独立变异)
F2 符号路命中旧空间块 → 被排除
F2 不传 fingerprint → 过滤器不生效(phase A 行为不变)
F2b 有向量但 fp 为空 → fail-closed 排除(HA 此处会放行)
F2 hybrid 在所有降级路径上应用同一过滤
F2† 删掉过滤器 → 旧空间块回来
F2†b 让 searchFts 不携带身份 → 过滤器失明(过滤只剩一个入口)
A1b 的调整(必须说明)
A1b 原先逐字节锁死 recallBlocks 源码,而 F2 合法地需要改这个函数。
改为守它真正要守的东西:**不传 fingerprint 时行为不变**
- 静态:过滤器必须 gated 在显式传入的 fingerprint 上
- 行为:golden fixture 前 5 例逐字段一致
依据:golden 语料 12 个块全部没有 fingerprint,
所以 F2 对 phase A 是纯增量;A2 仍全绿即为证。
验证(干净副本:git archive → 空目录 → link-dsh → 自跑)
见下一条消息附实数。
未 amend / 未 force push。
2026-10-30 15:29:23 +08:00
0e65dfee64
fix(check): checkpoint 全废时 runner 仍报通过 + 补回丢失的覆盖
...
缺口(评审复现,我独立复现确认)
上一条替换旧 README 判据时,把它的第二条断言一起带走了。
旧 test 同时守两件事:
1) README 计数与实现一致 已判定不值得守,删
2) checkpoints.js 至少注册一条 值得守,跟着 1 一起消失
grep 全仓:没有任何东西守 2。
实测后果(很安静)
把 helper 换成空操作、调用点全保留:
npm run check -> 0/0 checkpoints passed, exit=0
npm test -> 120 条全绿
即 checkpoint 全废、runner 报通过、测试一条不红。
修法:守结果,不守成因
scripts/checkpoints.js 结尾(if (failed > 0) 之前):
if (results.length === 0) {
console.error('no checkpoints were registered ...')
process.exitCode = 1
}
放 runner 而不是补回静态计数的理由:静态计数只能抓 helper 改名;
results.length === 0 抓的是结果,改名、被 stub、条件跳过都会红。
但 process.exit(0) 能绕过任何脚本内检查,所以另加一条外部断言
(test/phase-b-g-static.test.js)三层:
- declared > 0 helper 没被改名
- 源码含 if (results.length === 0)
- 该守卫确实设置 process.exitCode = 1
分工:runner 守结果,测试守 runner 还带着那道守卫。
实测
0 条注册(stub) -> no checkpoints were registered, exit=1
条件跳过 -> 同上, exit=1
正常态 -> 10/10 passed, exit=0
变异:删 runner 守卫 -> not ok 6
变异:helper 全改名 -> not ok 6
docs/dev-notes-mutation-criteria.md 增第 4 类
判据被替换时,旧判据的覆盖静默消失
- 形态:一条 test 守两件事
- 实测后果:0/0 passed / exit 0 / 120 全绿
- 守结果不守成因的四类成因对照表(含 process.exit(0) 是拦不住的那格)
- 自查问句:这条判据除了名字里那件事,还顺手守了什么
- 探针经验:不要用注释掉调用去验零注册,会变成 SyntaxError
验证
- npm test 120/120、npm run check 10/10
- npm test 3 次、npm run check 3 次(带 exit code 核对)全绿
- 无 .orig / .rej / /tmp/mut-* 残留
- 未 amend / 未 force push
2026-10-30 14:44:09 +08:00
fc5fe11590
docs: 去掉 README 计数并在判据侧禁止它 + 变异判据方法论 dev-notes
...
1. README 计数去掉(不留守卫)
去掉「119 tests」与「10 checkpoints / 10/10 checkpoints passed」。
理由按裁定:一个没有守卫、被手改过五次的数字是负资产,
它给人的确定感是假的;加守卫要翻倍套件时间,不值。
守卫反过来断言「README 不得出现计数」:
/\b\d+\s+tests\b/i
/\b\d+\s+checkpoints\b/i
/\b\d+\s*\/\s*\d+\s+checkpoints passed/i
三种写法实测都能抓住(120 tests / 12 checkpoints / 12/12 passed)。
顺带处理一处同类:原文「(6 tests)」是手抄的测试数,删;
「12 blocks × 15 queries = 45 cases」是 fixture 自身内容量,也删并改为
指向 fixture 文件 —— 由文件决定的东西不该在文档里再写一份。
(README 现在的口径:表格说「验什么」,`npm test` / `npm run check` 说「多少」。)
2. docs/dev-notes-mutation-criteria.md(新)
把三轮踩出来的方法论落成仓库文档,README 的 Verify 段加指针。
三类失效模式,每条都给「形态 / 实测证据 / 正确形态 / 自查问句」:
① 自测型假变异(犯四次:W11† / W11d†a / W11d†b / W12†)
变异在 test/ 里自己拼替身,一行 lib/ 没碰 ⇒ 真回退下保持绿
实测:M-a / M-b 下 W11d†a / W11d†b 均绿,只有 W11d 红
② 清单写死(W11f)
手写 MODES 漏掉新 case;future_mode_a/b 测的是「掉进 default 的」,
永远测不到「有人新写了一个 case」
实测:植入 brand_new_mode(零命中时撒谎)→ 写死版全绿,扫描版红
并记下边界:自动推导不能替代「提取本身也要被守卫」,
所以还需要 declared.length >= 4 + W11f-a(guards the guard)
③ 变异共享状态并发撞车(我自己引入的)
就地改写 lib/*.js,而 node --test 并行跑文件 ⇒ 别的文件读到中间态
实测:约 1/30 偶发失败,症状像 flaky 断言;A1b / G2b / G5 这类
读源码的判据会读到被改坏的中间态
修法:在 lib/ 副本上变异(复制 lib + package.json + 软链 node_modules),
finally 清理,锚点失配显式失败
文末给通用自查清单 9 条 + 一句话记住三条。
验证
- npm test 119/119、npm run check 10/10
- 连跑 6 次全量 0 失败
- 无 .orig / .rej / /tmp/mut-* 残留
- 未 amend / 未 force push,199553f 之后是新提交
2026-10-30 14:36:41 +08:00
d972a2d135
fix(memory-b): W11f —— aborted / default 零命中时不再承诺条目
...
接你备好的四个 patch(ready-lib / ready-test / README / docs),
应用前逐个审过内容,dry-run 干净,应用后 md5 与你给的期望值一致:
lib/recall.js c8fdffd25d5340291fadf6cb26e1741f
test/phase-b-h.test.js c06685abb7672fa806ca37e0d1cb8947
README.md 3c183b045dd7de4a54338ed9d01b3738
问题(我上一轮修 embed_error 时只修了那一个分支)
aborted 与 default: 在零命中时仍说「matches below are …」,而下面没有条目。
aborted + 零命中不是边角 —— 用户点停止、且这条查询本来就查不到东西,
是取消最常见的形态。
default: 更糟:它是未来任何未知模式**唯一**会走的路,
最不该在这里开始编。
修法(补丁采用)
把「有没有条目」只问一次,四个分支共用:
const nothing = !Array.isArray(hits) || hits.length === 0
default 用「nothing was found, so treat this as "unknown", not "absent"」——
把「没搜到 ≠ 没有」这条口径落在**兜底分支**上,
而不是每加一个模式才记得补一次。
W11f 判据:表驱动、双向
遍历 [undefined, no_embedder, embed_error, aborted, vector_ready_unused,
future_mode_a, future_mode_b]
零命中:notice 非空时不得含 matches below|below are|entries below
有条目:notice 非空时必须指向条目,且不得说 found nothing
第二条以前完全没人管 —— W11/W11c 只查「出现 FAILED / cancelled」,
不查它是否指向了真实存在的条目。
两条变异各自独立(实测,不是「都红就算数」)
M-A 只删 aborted 分叉
not ok 57 - W11f
not ok 58 - W11f†
ok 59 - W11f†b ← default 那半仍绿
M-B 只删 default 分叉
not ok 57 - W11f
ok 58 - W11f† ← aborted 那半仍绿
not ok 59 - W11f†b
若两条变异下红的是同一批,那就是重复计数(W11d†a/†b 的老毛病)。
验证
- npm test 118/118
- npm run check 10/10
- 连跑 8 次全量,0 失败(按你的要求超过 5 次)
- 变异 runner 零残留:/tmp/mut-?????? 前后均为 0
(/tmp/mut-probe 是 10:17 的历史副本,与本轮无关,未动)
未 amend / 未 force push,80d188e 之后是新提交。
2026-10-30 14:00:07 +08:00
80d188efc1
fix(memory-b): P0 no_embedder 逐轮注入 + 变异改为真改 lib/(并修掉自引入竞态)
...
P0 —— no_embedder 每轮注入(随 82b7546 进了远端)
复现(真链路,config 不给 embed = phase A 出货默认,10 个普通回合)
10/10 回合被注入,共 2130 字符,均值 213 字符/次
注入原文是恒定、无条目的两行记忆头。
根因:W11d 拆两处短路时把 no_embedder 一起放行了。
问题不在「该不该说」,在「不该每轮说」——它是一条永久配置事实,
renderIndex()(systemPrompt.context)已经每轮在讲。
代价不只是 token:每轮一段恒定无条目的记忆头,模型的正确反应是学会跳过
该段,等真降级出现时也不看,正好废掉 W11 的用意。
修法
- no_embedder 返回 ''(注释写明理由与被测数字)
- 顺手修文案矛盾:embed_error + 零命中原来会说「matches below are…」
但下面没有条目。现在分两种措辞:
零命中 -> "FAILED … and the lexical fallback found nothing"
有命中 -> "FAILED …; matches below are lexical/symbolic only"
- aborted / embed_error 不动
判据 W11e:默认配置(无 embedder)+ 零命中 ⇒ 注入必须为 ''
修复后同一探针:10 个回合 0 个被注入,0 字符
W11e 与 W11d 互为反向守卫:真·本轮事件要说,持续状态不该每轮说。
变异纪律(你指出的第四次同型,我认)
W11d†a / W11d†b 原来是测试里自己拼的局部函数,不碰 lib/ ——
真回退下它们保持绿,只有 W11d 变红。已删除,不留作计数。
换成三条真变异:W11e† / W11d† / W11d†ctx,
都真改源码、import 变异版、断言行为翻转。
★ 改真变异时我自己引入了竞态,并被抓到
第一版 withMutatedSource 直接在磁盘上改写 lib/recall.js 再还原。
node --test 是并行跑测试文件的,别的文件正在同时 import 它。
实测 1/30 左右偶发失败,症状看起来像「断言 flaky」,其实是 harness 竞态。
已改为 withMutatedCopy:整体复制 lib/ + package.json + 软链 node_modules
到临时目录,在副本上改、在副本上 import,完全不碰共享树。
验证:连续 30 次全量 0 失败;变异锚点漂移时显式报错(不会静默通过)。
文档
- README:W11e 的理由与实测数字、变异纪律(必须改 lib/,改 test/ 不算)、
以及上面这条「变异共享状态不能与代码并发跑」的竞态记录
- docs/phase-b-criteria.md:W11e 进表、W11d 的变异列改为两条独立真变异、
★★ 变异纪律段(同型四次:W11†/W11d†a/W11d†b/W12†)
验证
- npm test 115/115
- npm run check 10/10
- 连跑 3 次 test + 3 次 check 全绿
- 另跑 30 次全量确认竞态已除(0 失败)
2026-10-30 13:34:18 +08:00
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
eac9f0fca6
docs: phase B 开工前决策建议 —— 异步化回归风险可结构性消除
...
回应四条裁定与一个交叉点。不含实现。
核心结论:草案 A1「recallBlocks 改 async」是我图省事的设计偷懒,
把「要拿向量」和「要打分」混在一个函数里。切分后风险归零:
recallBlocks(store, query, opts) 保持同步,签名行为不变
新增可选 opts.vectorScores = Map<blockId, number>
recallBlocksHybrid(store, embedder, q, opts) 新的 async 包装
实测调用面:生产代码 3 行(lib/index.js:124/140/277),
测试代码 0 行(test/store.test.js 5 处 + scripts/checkpoints.js 4 处
全部继续调同步版)。已实测确认 test/plugin.test.js:119 那个
`contextForStep = () => ''` 的同步桩在 async 化后仍兼容(await 非
Promise 值即其本身),所以那条变异测试继续有效。
建议把 guard 组 A1 改成「recallBlocks 保持同步且实现不变」,
并新增 A3:hybrid 在 embedder 为 null 时返回值与同步版逐字段相同
(含 why 字符串)——切分后最大的新风险是两个入口漂移,A3 钉死它。
四条裁定核对
- §2.1 endpoint 规范化:建议固定四条(去尾斜杠 / host 小写 /
省略默认端口 / 去空白),不做 query 重排与路径归一。
- §2.2 四态:可执行,我草案的三态漏了「向量化失败」,裁定并进来更准。
新增 D7:stats 默认不解析向量 JSON(用 length(vector)),维度从
graph_centroid 取。修正一处我原稿的说漏——HA 为算 Dimensions 必须
逐行 decodeVector(block_recall.go:303/313),所以「维度统计」与
「不解析」互斥,只能靠中心表绕开。
- §2.3 centroid 补 stats 列:已列为第一件事。
- §2.4 失败降级:同意,并建议 mode 三值而非 bool
(no_embedder / embed_error / all_mismatched 对用户意义不同)。
§3 建议把 embedder 焊成单方法窄接口(embedText / info / close),
加 G5 源码 grep 守卫:除 lib/embed/http.js 外不得出现 http/endpoint/
fetch/Bearer 等 provider 专属标识——机器可查,比约定强。
§4 交叉点:建议投完整 phase B,按 §1 切分后 5.5-6.5 人天。
但建议你先确认一件事:是否有真实使用中的 dsh 会话在产生块?
若无,可先投 2.5 人天的中间态(向量写入 + 四态可见,不改召回主路径)。
§6 记录本轮两处自我修正。
参考仓未改动。
2026-10-30 09:52:06 +08:00
0dfa6dcb27
docs: phase B 判据与变异规则草案(待批,不含实现)
...
decision 1(http 转发、不背 ONNX)与 decision 3(跳过不报错但失配计数
必须暴露到工具面)已落实到判据;decision 2 未回,全文按草案状态。
开工前必须先定的 4 件事(各附我的建议与代价):
§0.1 ★ HA 的 http 指纹派生式漏了 endpoint
http_embedder.go:99 只取 "http:"+model+":"+dim。
换 endpoint(换机器/A-B 服务/换 region)而 model 同名同维时,
指纹相同 ⇒ 静默混用两个向量空间。
建议扩展为含 endpoint hash;代价是换 endpoint 触发一次全量重嵌。
§0.2 ★ 空 fingerprint 的带向量块会被静默放行
block_recall.go:140 的跳过条件是三个 && ⇒ fp 为空时不跳过。
phase A 的块全都无向量,phase B 回填后「无向量」与
「有向量但指纹空」是两种不同状态,混在一起用户无法知道怎么修。
建议三态归类 + fail-closed(no_vector / no_fingerprint / stale / active)。
§0.3 graph_centroid DDL 与 HA 不一致(我的疏忽)
少了 stats 列,而 HA 靠 meta.Anomalous 决定要不要启用中心化
(centroid_store.go:205-209)。现在表是空的,改列名零成本。
§0.4 embedding 调用失败是否一并进计数器(与 decision 3 同构,建议纳入)
判据 6 组共 34 条(A 前置重构 / F 指纹空间 / V 融合 / C 中心化 /
D 降级可见性 / H http provider / G 守卫),每条带变异。
变异规则汇总出 12 条必须能红的,其中 M-V1(精确串从布尔降成权重)
是 HA 真实踩过的 bug(0.300 输给 0.534)。
另外三项判断:
- 增加 golden fixture 固化 phase A 召回结果,作为「加向量没顺手改符号路」的守卫
- memory_blocks 要加 vector/fingerprint 两列,phase A 的 DDL 已发布 ⇒
建议 phase B 第一件事补 PRAGMA user_version 迁移框架(现在 0→1 一步,
一天成本;等 L2 落地再补就贵了)
- 工作量从原估 3-4 人天上修到 6-7 人天(原估漏了 schema 迁移、
stablemem_stats 工具、异步化带来的全量测试改动)
不含任何实现。参考仓未改动。
2026-10-30 09:49:07 +08:00
533f801c24
docs: 影子上下文方案在 dsh 上的可行性核实
...
结论:路线②(注册独立 agent 模式)可行;路线①(劫持原有信息流)
只是看着可行。
路线②可行 —— agentPresets 是一等机制
- ctx.agentPresets.register({id, plugins}) 可由任意插件调用
(dsh-agent-preset/lib/index.js:18-26)
- preset 子树是独立 scope + 可选 isolate realm
(dsh-agent-preset-registry/lib/types/mount.js:183-198)
- 已有实证:standard.patch.yml:66-82 的 isolate:{compaction:true}
group 已经在替换 compaction 服务
- 因此「压缩时机」「不压缩」「压缩前抓原文」全部可控
路线①为什么看着可行
- agent/pre-step 看着能注入,其实注入即落盘:
loop 随后 session.append("user/message", ...)
(dsh-agent-loop/lib/index.js:1067-1073),注入内容成为真实历史
- llm/stream 看着能改最终请求,其实 loop 请求深冻结
(dsh-llm/lib/types/index.d.ts:37-42),mutation throws
- 「不装 compaction-basic」看着能旁路,其实 auto:false 会连
溢出恢复一起关掉 (dsh-compaction-basic/lib/index.js:827)
关键机制核实
- compaction 自动触发只是两个 ctx.on 监听器
(dsh-compaction-basic/lib/index.js:839/:861),受 config.auto 控制
- 服务名同 realm 撞名抛错 (cordis/lib/index.js:814),换实现必须 isolate
- surface 支持整段 replace,但被遮蔽节点必须全部列进 sourceEventSeqs
(dsh-session/lib/index.js:314-332)
- 日志 seq 严格连续 (dsh-session/lib/index.js:426),影子副本只能活在
dsh 之外
- MessageSourceMap 可 merge 扩展,自定义字段 lossless-JSON 存活但不进
wire(adapter 只投影 role/content)
- setFactory 独占 (dsh-agent/lib/index.js:431 + agent-loop:1590),
自建 runner 需弃用 2023 行的 agent-loop
三处与路线无关的硬阻塞:surface 不能插中间、llm/stream 深冻结、
前缀时序只能靠末尾追加。
人天:路线① 12-18(仅 60% 子集);路线② 18-26;混合 22-30。
附完整证据索引(6 组,全部 lib/*.js 实现级核对)。
原文只读,未修改 HologramAgent 仓库。
2026-10-30 09:29:37 +08:00
28d9d5e929
docs: 三层记忆移植 dsh 插件可行性评估
...
结论:只能做子集。
逐层结论
- L1 (context 热上下文) ≈20%:dsh 的上下文压缩是「摘要替换区间」
(dsh-compaction-basic 的 compactSurfaceRegion),不是「按 query 相关性
淘汰 + 迁出到冷层 + 按原时间戳迁回」。CompactionTrigger 接口拿不到
query;MessageBase 无 timestamp 字段,surface 是 append-only + replace,
无法语义插入。
- L2 (coldcontext 冷文档) ≈80%:存储/召回/注入可落,缺
「Peek → 回灌 → Remove」的回收站语义。
- L3 (stablemem 图记忆) ≈85%:memory_blocks/memory_block_edges 结构
完全可映射,node:sqlite 实测可用(含 FTS5);受限于无 embedding 服务。
关键发现
- dsh 无任何 embedding/vector service,需自建(推荐复用 HA 的 http
provider HTTP 协议转发,2 人天)。
- systemPrompt.context() 的 AssembleContext 拿不到本步消息正文,
「按本次输入召回后注入」必须走 agent/pre-step waterfall
(参考 dsh-time-context 的用法)。
- dsh attachment 是 CAS,与 HA「媒体字节内联在块里、不做引用账本」
的设计冲突 → 媒体块必须裁掉。
工作量(含判据与实测)
- L2+L3 子集:36-53 人天
- MVP(L3 + 工具 + 注入面):19-25 人天
- 完全等价(含迁出-迁回):不可行,不报价
报告含 7 章 + 两份附录(HA/dsh 双侧证据索引、环境实测结果)。
原文全程只读,未修改 HologramAgent 仓库。
2026-10-30 00:39:44 +08:00