dsh-memu-memory 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -6
- package/README.md +50 -5
- package/README.zh.md +49 -5
- package/docs/active-memory-ab.md +385 -0
- package/docs/binding-lifecycle.md +99 -0
- package/docs/contributing.md +6 -4
- package/docs/emotion-model-spec.md +281 -0
- package/docs/env-and-commands.md +13 -0
- package/docs/host-api.md +35 -3
- package/docs/memory-semantics.md +40 -11
- package/docs/recall-benchmark.md +238 -0
- package/docs/troubleshooting.md +395 -366
- package/lib/active-memory.js +165 -0
- package/lib/activity-log.js +190 -0
- package/lib/backup.js +1103 -0
- package/lib/binding-diagnostics.js +635 -0
- package/lib/client.js +728 -58
- package/lib/consolidation-ledger.js +347 -0
- package/lib/consolidation-receipt.js +284 -0
- package/lib/consolidation-snapshot.js +274 -0
- package/lib/consolidation-worker.js +115 -0
- package/lib/curator.js +81 -37
- package/lib/cycle.js +43 -6
- package/lib/embedding-catalog.js +763 -0
- package/lib/embedding-download.js +407 -0
- package/lib/embedding-runtime.js +710 -0
- package/lib/engine.js +2 -2
- package/lib/index.js +36 -0
- package/lib/inject.js +232 -103
- package/lib/local-embedding-entry.js +145 -0
- package/lib/local-embedding-manager.js +267 -0
- package/lib/local-embedding-protocol.js +173 -0
- package/lib/local-embedding-worker.js +652 -0
- package/lib/local-vector-service.js +283 -0
- package/lib/memory.js +599 -57
- package/lib/memu-config.js +10 -22
- package/lib/memu.js +53 -19
- package/lib/mood.js +268 -39
- package/lib/paths.js +24 -0
- package/lib/preset-scope.js +1 -1
- package/lib/private.js +450 -29
- package/lib/prompts/private-consolidation.coding.md +1 -0
- package/lib/prompts/private-consolidation.md +1 -0
- package/lib/prompts/private-consolidation.mixed.md +1 -0
- package/lib/prompts/private-consolidation.rp.md +1 -0
- package/lib/prompts.js +4 -4
- package/lib/remote-summary.js +51 -25
- package/lib/rpc.js +54 -5
- package/lib/state.js +137 -172
- package/lib/tools.js +58 -7
- package/lib/vector-migration.js +878 -0
- package/lib/vector-profile.js +968 -0
- package/package.json +4 -2
- package/scripts/acceptance-local.mjs +7 -5
- package/scripts/active-memory-ab-live.mjs +655 -0
- package/scripts/active-memory-ab.mjs +1302 -0
- package/scripts/chain-matrix.mjs +537 -0
- package/scripts/check-all.mjs +29 -1
- package/scripts/check-doc-drift.mjs +353 -0
- package/scripts/diagnose-bindings.mjs +176 -0
- package/scripts/local-runtime-install.mjs +142 -0
- package/scripts/local-runtime-verify.mjs +56 -0
- package/scripts/local-vector-verify.mjs +93 -0
- package/scripts/recall-bench.mjs +813 -0
- package/scripts/repetition-ab-scenarios.mjs +93 -0
- package/scripts/repetition-detector-snapshot.mjs +314 -0
- package/scripts/repetition-eval-live.mjs +360 -0
- package/scripts/repetition-eval.mjs +702 -0
- package/tests/activation.test.mjs +89 -0
- package/tests/active-memory-ab.test.mjs +745 -0
- package/tests/active-memory.test.mjs +129 -0
- package/tests/activity-log.test.mjs +126 -0
- package/tests/activity-wiring.test.mjs +103 -0
- package/tests/appendix-b.test.mjs +212 -0
- package/tests/appendix-b2.test.mjs +166 -0
- package/tests/audit-regression.test.mjs +272 -0
- package/tests/audit-safety.test.mjs +227 -0
- package/tests/backend-compensation.test.mjs +272 -0
- package/tests/backup.test.mjs +793 -0
- package/tests/binding-diagnostics.test.mjs +720 -0
- package/tests/browser.test.mjs +366 -0
- package/tests/client-render.test.mjs +892 -0
- package/tests/coalesce.test.mjs +113 -0
- package/tests/config.test.mjs +159 -0
- package/tests/consolidation-credential.test.mjs +320 -0
- package/tests/consolidation-fork.test.mjs +357 -0
- package/tests/consolidation-ledger.test.mjs +298 -0
- package/tests/consolidation-snapshot.test.mjs +321 -0
- package/tests/consolidation-value.test.mjs +89 -0
- package/tests/context-scope.test.mjs +40 -0
- package/tests/cue.test.mjs +44 -0
- package/tests/curator-probe.test.mjs +433 -0
- package/tests/curator.test.mjs +150 -0
- package/tests/cycle.test.mjs +411 -0
- package/tests/disclosure.test.mjs +231 -0
- package/tests/doc-drift.test.mjs +222 -0
- package/tests/embedding-catalog.test.mjs +266 -0
- package/tests/embedding-download.test.mjs +391 -0
- package/tests/embedding-runtime.test.mjs +389 -0
- package/tests/emotion-chain.test.mjs +84 -0
- package/tests/emotion-contract.test.mjs +445 -0
- package/tests/engine.test.mjs +35 -0
- package/tests/fixtures/active-memory-ab/README.md +56 -0
- package/tests/fixtures/active-memory-ab/cases/01-clean-dense-turn.json +351 -0
- package/tests/fixtures/active-memory-ab/cases/02-clean-thin-turn.json +122 -0
- package/tests/fixtures/active-memory-ab/cases/03-clean-starved-small.json +125 -0
- package/tests/fixtures/active-memory-ab/cases/04-clean-large-thin.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/05-clean-half-step.json +166 -0
- package/tests/fixtures/active-memory-ab/cases/06-clean-duplicate-reread.json +197 -0
- package/tests/fixtures/active-memory-ab/cases/07-clean-recent-recall.json +125 -0
- package/tests/fixtures/active-memory-ab/cases/08-global-relevant.json +111 -0
- package/tests/fixtures/active-memory-ab/cases/09-global-irrelevant-conflict.json +158 -0
- package/tests/fixtures/active-memory-ab/cases/10-no-human-turn.json +97 -0
- package/tests/fixtures/active-memory-ab/cases/11-clean-scale-100.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/12-clean-scale-1000.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/13-clean-scale-5000.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/14-clean-scale-20000.json +137 -0
- package/tests/fixtures/active-memory-ab/lead-ai-reviewed-reference.json +191 -0
- package/tests/fixtures/active-memory-ab/manifest.json +199 -0
- package/tests/fixtures/recall-bench/01-paraphrase.json +184 -0
- package/tests/fixtures/recall-bench/02-exact.json +153 -0
- package/tests/fixtures/recall-bench/03-negation.json +173 -0
- package/tests/fixtures/recall-bench/04-no-answer.json +162 -0
- package/tests/fixtures/recall-bench/05-conflict.json +194 -0
- package/tests/fixtures/recall-bench/06-boundary.json +273 -0
- package/tests/fixtures/recall-bench/07-secret.json +191 -0
- package/tests/fixtures/recall-bench/08-coding.json +164 -0
- package/tests/fixtures/recall-bench-gold-candidates/01-paraphrase.json +193 -0
- package/tests/fixtures/recall-bench-gold-candidates/02-exact.json +197 -0
- package/tests/fixtures/recall-bench-gold-candidates/03-negation.json +244 -0
- package/tests/fixtures/recall-bench-gold-candidates/04-no-answer.json +189 -0
- package/tests/fixtures/recall-bench-gold-candidates/05-conflict.json +245 -0
- package/tests/fixtures/recall-bench-gold-candidates/06-boundary.json +279 -0
- package/tests/fixtures/recall-bench-gold-candidates/07-secret.json +224 -0
- package/tests/fixtures/recall-bench-gold-candidates/08-coding.json +218 -0
- package/tests/fixtures/repetition-eval/01-zh-rp.json +93 -0
- package/tests/fixtures/repetition-eval/02-en.json +97 -0
- package/tests/fixtures/repetition-eval/03-code.json +91 -0
- package/tests/fixtures/repetition-eval/04-names.json +116 -0
- package/tests/fixtures/repetition-eval/05-user-quote.json +96 -0
- package/tests/fixtures/repetition-eval/06-agreement.json +86 -0
- package/tests/graph-view.test.mjs +187 -0
- package/tests/graph.test.mjs +77 -0
- package/tests/hook-order.test.mjs +72 -0
- package/tests/humanlike.test.mjs +232 -0
- package/tests/hybrid-recall.test.mjs +92 -0
- package/tests/inbound.test.mjs +393 -0
- package/tests/inject-guide.test.mjs +364 -0
- package/tests/inject.test.mjs +222 -0
- package/tests/injection-budget-settings.test.mjs +35 -0
- package/tests/injection-budget.test.mjs +64 -0
- package/tests/injection-chain.test.mjs +191 -0
- package/tests/loader-register.mjs +3 -0
- package/tests/loader.mjs +12 -0
- package/tests/local-embedding-entry.test.mjs +95 -0
- package/tests/local-embedding-manager.test.mjs +476 -0
- package/tests/local-embedding-protocol.test.mjs +95 -0
- package/tests/local-embedding-worker.test.mjs +346 -0
- package/tests/managed-memory.test.mjs +536 -0
- package/tests/managed-runtime.test.mjs +40 -0
- package/tests/media.test.mjs +132 -0
- package/tests/migration-payload.test.mjs +42 -0
- package/tests/mood.test.mjs +109 -0
- package/tests/multiprocess.test.mjs +29 -0
- package/tests/new-session-gate.test.mjs +361 -0
- package/tests/package-shipping.test.mjs +84 -0
- package/tests/preset-scope.test.mjs +212 -0
- package/tests/prompts.test.mjs +250 -0
- package/tests/react-snapshot.test.mjs +23 -0
- package/tests/recall-bench.test.mjs +306 -0
- package/tests/remote.test.mjs +140 -0
- package/tests/rename.test.mjs +30 -0
- package/tests/repetition-detector-snapshot.test.mjs +231 -0
- package/tests/repetition-eval-live.test.mjs +177 -0
- package/tests/repetition-eval.test.mjs +373 -0
- package/tests/salient-date.test.mjs +142 -0
- package/tests/settings-beat.test.mjs +221 -0
- package/tests/short-term.test.mjs +166 -0
- package/tests/sidebar-beat.test.mjs +208 -0
- package/tests/sidebar-refresh.test.mjs +123 -0
- package/tests/sidebar.test.mjs +70 -0
- package/tests/snippet.test.mjs +47 -0
- package/tests/source-kind.test.mjs +52 -0
- package/tests/state.test.mjs +150 -0
- package/tests/stats.test.mjs +112 -0
- package/tests/storage.test.mjs +125 -0
- package/tests/stubs/dsh-llm.js +5 -0
- package/tests/stubs/dsh-session.js +7 -0
- package/tests/stubs/dsh-tools.js +101 -0
- package/tests/tool-schema.test.mjs +73 -0
- package/tests/tools.test.mjs +184 -0
- package/tests/ui-defaults.test.mjs +57 -0
- package/tests/update-fields.test.mjs +138 -0
- package/tests/vector-migration.test.mjs +733 -0
- package/tests/vector-profile.test.mjs +425 -0
- package/tests/visibility-migration.test.mjs +152 -0
- package/tests/webui-auth.test.mjs +454 -0
- package/tests/webui.test.mjs +164 -0
- package/tests/windows-env.test.mjs +89 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# 主动记忆 A/B 评测与快照凭证(F 包,provisional)
|
|
2
|
+
|
|
3
|
+
> **状态:provisional,不是金标。** 确定性 harness 不调用模型;§9 另有一轮真实模型 live 运行(工具调用协议),它回答的是「协议下产出可不可用、省多少 token」,不是主功率提升,也不是 gold。数据集里 14 个 case 仍全部是**合成代理标注**(`annotationSource: "synthetic-proxy"`,`review: "pending"`,`gold: false`),没有人工逐条确认。
|
|
4
|
+
>
|
|
5
|
+
> 实测日期 2026-10-30(Asia/Shanghai)。测试数字与摘要都是**带日期的快照**,改动 fixture 或规则后必须重跑并更新,不能当永久保证。
|
|
6
|
+
|
|
7
|
+
- 工作包:§13.9 F 包(回放与验收)+ §13.6 C 包的**接口侧**(快照身份、凭证、游标规则)
|
|
8
|
+
- 脚本:`scripts/active-memory-ab.mjs`(确定性回放,§2)+ `scripts/active-memory-ab-live.mjs`(真实模型,§9)
|
|
9
|
+
- 测试:`tests/active-memory-ab.test.mjs`(38 个用例,38 pass;含结构行回执→凭证 5 条)
|
|
10
|
+
- 数据集:`tests/fixtures/active-memory-ab/`(14 个 case,15 个文件;含 4 个规模梯度 case,见 §5.2)
|
|
11
|
+
- 确定性 harness 模型/网络调用:**0 次**;真实模型调用只在 `--live` 下发生,报告不落盘、不留模型原文(§9)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. 这一页回答什么
|
|
16
|
+
|
|
17
|
+
| 问题 | 本文的回答 | 不能从这里得出的结论 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 快照是不是固定的? | 是。每个 case 有 `throughSeq` + 快照 `sha256` pin,改动未重新钉会被拒绝加载 | 不代表快照里的对话是真实用户会话(它是合成的) |
|
|
20
|
+
| 三种条件(旧行为 / guide / guide+weighted)在调度上有什么差别? | 有确定结论:见 §5 | 不代表 guide 对模型行为有/没有效果(未测) |
|
|
21
|
+
| 检查结果能不能被凭证校验、游标会不会乱走? | 能,且失败/半步/不支持工具都不前移游标 | 不代表后台 worker 的语义判断质量(oracle 适配器给的是上界) |
|
|
22
|
+
| 覆盖率是多少? | 只给**区间**:oracle 上界 / no-value 下界 | 不能写成「覆盖率提升 X%」这种点估计 |
|
|
23
|
+
| 全局干扰有没有把私有写污染? | 泄漏 0,冲突进 held | 不代表真实全局库内容与真实干扰强度 |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. 运行方式
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 全量确定性回放,JSON 打到 stdout
|
|
31
|
+
node scripts/active-memory-ab.mjs
|
|
32
|
+
|
|
33
|
+
# 写机器可读报告(CRLF)
|
|
34
|
+
node scripts/active-memory-ab.mjs --out artifacts/active-memory-ab.json
|
|
35
|
+
|
|
36
|
+
# 只跑前 3 个 case / 只跑某一行
|
|
37
|
+
node scripts/active-memory-ab.mjs --cases 3
|
|
38
|
+
node scripts/active-memory-ab.mjs --row guide-weighted:oracle
|
|
39
|
+
|
|
40
|
+
# 人工重新钉 manifest 前先看当前摘要(不会自动改写 pin)
|
|
41
|
+
node scripts/active-memory-ab.mjs --digests
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
测试:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node --import ./tests/loader-register.mjs --test tests/active-memory-ab.test.mjs
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**确定性脚本没有任何参数能开启真实模型调用**:`--real-model`、`--live`、`--model`、`--network`、`--gold` 一律直接报错退出。真实模型有独立的一支运行器(`scripts/active-memory-ab-live.mjs`),用法、两臂定义与实测结果见 §9;它同样不把 synthetic-proxy 数据集升级为 gold。
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. 数据集与固定快照
|
|
55
|
+
|
|
56
|
+
### 3.1 十四个 case
|
|
57
|
+
|
|
58
|
+
| case | 干扰维度 | 它在测什么 |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `clean-dense-turn` | off | 密集回合:分数达标就检查,不等空闲 |
|
|
61
|
+
| `clean-thin-turn` | off | 薄回合:没有空闲窗口,也不该唤醒 |
|
|
62
|
+
| `clean-starved-small` | off | 小会话饥饿:空闲到阈值无条件检查一次 |
|
|
63
|
+
| `clean-large-thin` | off | 大 transcript(2090 事件)薄回合:不吃饥饿豁免 |
|
|
64
|
+
| `clean-half-step` | off | throughSeq 停在 `tool/call` 上:半步 → deferred,不推进游标 |
|
|
65
|
+
| `clean-duplicate-reread` | off | 主模型已写过一条 + 重复轮询:重复/已存在怎么算 |
|
|
66
|
+
| `clean-recent-recall` | off | 刚注入过记忆:抑制,而不是"检查过没东西" |
|
|
67
|
+
| `global-relevant` | relevant | 相关全局条目可以进上下文,但不许复制进私有写 |
|
|
68
|
+
| `global-irrelevant-conflict` | irrelevant-conflict | 无关且冲突的全局条目:只进 held,不进可召回覆盖 |
|
|
69
|
+
| `no-human-turn` | off | 纯工具轮询:`no-human-turn` 与"没有新事实"是两回事 |
|
|
70
|
+
| `clean-scale-100` | off | 规模梯度最小档(98 事件):低于阈值,饥饿豁免生效 |
|
|
71
|
+
| `clean-scale-1000` | off | 规模梯度(998 事件):仍在阈值下,行为与上一档一致 |
|
|
72
|
+
| `clean-scale-5000` | off | 规模梯度(4998 事件):越过阈值,薄回合不再吃饥饿豁免 |
|
|
73
|
+
| `clean-scale-20000` | off | 规模梯度上限区(19998 事件):验证阈值以上不再有豁免 |
|
|
74
|
+
|
|
75
|
+
### 3.2 pin(`tests/fixtures/active-memory-ab/manifest.json`)
|
|
76
|
+
|
|
77
|
+
快照摘要 = `sha256(canonicalJson({ schemaVersion, fromSeq, throughSeq, events[fromSeq..throughSeq] }))`。`throughSeq` 之后的事件是"尚未到达",**不进摘要、不进逻辑检查范围,也不进 worker seed**;实际 seed 仍是从 0 到当前 `throughSeq` 的完整连续前缀。
|
|
78
|
+
|
|
79
|
+
| case | throughSeq | 事件数 | 快照 sha256(前 12 位) |
|
|
80
|
+
|---|---:|---:|---|
|
|
81
|
+
| clean-dense-turn | 22 | 23 | `01b63a470444` |
|
|
82
|
+
| clean-thin-turn | 9 | 10 | `73cfdd46eb46` |
|
|
83
|
+
| clean-starved-small | 9 | 10 | `49145876868c` |
|
|
84
|
+
| clean-large-thin | 2089 | 2090 | `e828cde9d7ee` |
|
|
85
|
+
| clean-half-step | 3 | 4(+8 尾事件) | `c6eb87ef6767` |
|
|
86
|
+
| clean-duplicate-reread | 13 | 14 | `d48163e03141` |
|
|
87
|
+
| clean-recent-recall | 9 | 10 | `2ea689efcda4` |
|
|
88
|
+
| global-relevant | 4 | 5 | `0f4d97adf484` |
|
|
89
|
+
| global-irrelevant-conflict | 9 | 10 | `0a1d5e3a7396` |
|
|
90
|
+
| no-human-turn | 5 | 6 | `851fa7965cbe` |
|
|
91
|
+
| clean-scale-100 | 97 | 98 | `ec6180fcd444` |
|
|
92
|
+
| clean-scale-1000 | 997 | 998 | `04510b40d08e` |
|
|
93
|
+
| clean-scale-5000 | 4997 | 4998 | `dc9095796c6c` |
|
|
94
|
+
| clean-scale-20000 | 19997 | 19998 | `400e916841df` |
|
|
95
|
+
|
|
96
|
+
数据集级 `casesDigest`(所有 pin 的 sha256):`8f44dfa8e4ab7818a1e750e9f2e26c8b71e97e00e3cb5967294a3f29ef9c0d75`(14 个 case 版本;加入规模梯度前的 10-case 版本是 `93e1f663…`)。
|
|
97
|
+
|
|
98
|
+
大 transcript 那个 case 用确定性填充(`ab-polling-padding-v1`):520 组完全相同的轮询结果,展开成 2080 个事件后参与摘要,因此既可复现又能跨过 2000 事件阈值;这些结果去重后只算 **1 个工作单元**(测试里有断言)。
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. 三组条件与四行
|
|
103
|
+
|
|
104
|
+
| 行 | 条件 | 检查时机 | 逻辑检查范围 / 实际 seed | 有无 guide |
|
|
105
|
+
|---|---|---|---|---|
|
|
106
|
+
| `legacy` | 旧行为 | 只有空闲 ≥ 3 分钟 | 整段前缀;实际 seed 为完整连续前缀 `[0..boundary]` | 无 |
|
|
107
|
+
| `guide` | 统一 guide | 同 legacy(guide 只改投放的规则消息) | 整段前缀;实际 seed 为完整连续前缀 `[0..boundary]` | 有(`rulesKey` 占位符) |
|
|
108
|
+
| `guide-weighted:oracle` | guide + 加权检查 + 快照 worker | `assessWake`(分数/饥饿/大 transcript)+ 抑制 | 逻辑资格范围是游标之后的范围;实际 seed 仍为完整连续前缀 `[0..boundary]` | 有 |
|
|
109
|
+
| `guide-weighted:no-value` | 同上,但 worker 总返回 `no_value` | 同上 | 同上;**没有 windowed seed** | 有 |
|
|
110
|
+
|
|
111
|
+
后两行是**同一调度、不同适配器**:oracle 给覆盖上界,no-value 给下界。真实模型落在两者之间,本 harness 不测。
|
|
112
|
+
|
|
113
|
+
触发计划全部由生产纯函数复算(`lib/active-memory.js` 的 `collectEvidence / scoreEvidence / assessWake / suppressionReason`),没有另写一套阈值;旧行为那 3 分钟空闲常量在脚本里是 `LEGACY_IDLE_MINUTES`,只用于对照。
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 5. 实测结果(2026-10-30,合成代理集,interference = off 的 12 个 case)
|
|
118
|
+
|
|
119
|
+
| 指标 | legacy | guide | guide-weighted:oracle | guide-weighted:no-value |
|
|
120
|
+
|---|---:|---:|---:|---:|
|
|
121
|
+
| 覆盖 | 14/20(main 1 / 后台 13) | 14/20(同左) | **11/20**(main 1 / 后台 10) | 1/20(只有 main 1) |
|
|
122
|
+
| 未覆盖 | 6 | 6 | 9 | 19 |
|
|
123
|
+
| 延迟(人类回合)mean/median/max | 0.643 / 1 / 1 | 0.643 / 1 / 1 | 1.182 / 1 / 3 | 0 / 0 / 0(n=1) |
|
|
124
|
+
| no_value 率 | 1/9 = 0.111 | 1/9 = 0.111 | 0/6 = 0 | 5/6 = 0.833 |
|
|
125
|
+
| 重复提议 / 已存在 | 1 / 0 | 1 / 0 | 1 / 0 | 1 / 0 |
|
|
126
|
+
| 后台检查次数 | 9 | 9 | 6 | 6 |
|
|
127
|
+
| 每千步骤检查次数 | 1.280 | 1.280 | 0.853 | 0.853 |
|
|
128
|
+
| 种子 token 估算(总/每次) | 46019 / 5113.2 | 46019 / 5113.2 | **4240 / 706.7** | 4240 / 706.7 |
|
|
129
|
+
| 父会话等待(未测) | 未测(null) | 未测(null) | 未测(null) | 未测(null) |
|
|
130
|
+
| 被拦原因分布 | `no-idle-gap` × 12 | 同左 | `low-score` × 10, `large-transcript-thin` × 3, `recent-recall` × 2, `no-human-turn` × 1 | 同左 |
|
|
131
|
+
| 唤醒触发 | `legacy-idle` × 9 | 同左 | `score` × 1, `starved` × 5 | 同左 |
|
|
132
|
+
| deferred / failed / 凭证不合法 | 0 / 0 / 0 | 0 / 0 / 0 | 1 / 0 / 0 | 1 / 0 / 0 |
|
|
133
|
+
|
|
134
|
+
标签总数 20,来自 12 个 interference = off 的 case(`global-relevant` / `global-irrelevant-conflict` 不进主表)。
|
|
135
|
+
|
|
136
|
+
相对 legacy 的差(同一批 case):
|
|
137
|
+
|
|
138
|
+
| 行 | 覆盖差 | 未覆盖差 | 检查次数差 | 种子 token 差 |
|
|
139
|
+
|---|---:|---:|---:|---:|
|
|
140
|
+
| guide | 0 | 0 | 0 | 0 |
|
|
141
|
+
| guide-weighted:oracle | −3 | +3 | −3 | −41779(约 −91%;检查次数/边界不同;每次 seed 仍是完整前缀) |
|
|
142
|
+
| guide-weighted:no-value | −13 | +13 | −3 | −41779(检查次数/边界不同;每次 seed 仍是完整前缀) |
|
|
143
|
+
|
|
144
|
+
**覆盖反转**:`guide-weighted:oracle` 现在是 11/20,**低于** legacy 的 14/20。原先那张表里加权比 legacy 多 1 条,那个结论不再成立。逐 case 拆开是:
|
|
145
|
+
|
|
146
|
+
| case | legacy | 加权 oracle | 差 | 原因 |
|
|
147
|
+
|---|---:|---:|---:|---|
|
|
148
|
+
| `clean-dense-turn` | 0/4 | 4/4 | +4 | 分数达标就检查,不等空闲 |
|
|
149
|
+
| `clean-large-thin` | 2/2 | 0/2 | −2 | `large-transcript-thin` 拦停 |
|
|
150
|
+
| `clean-scale-5000` | 2/2 | 0/2 | −2 | 同上(新增档) |
|
|
151
|
+
| `clean-scale-20000` | 2/2 | 0/2 | −2 | 同上(新增档) |
|
|
152
|
+
| `clean-recent-recall` | 1/1 | 0/1 | −1 | `recent-recall` 抑制 |
|
|
153
|
+
|
|
154
|
+
净 −3。原先「加权覆盖更好」只在 8-case 混比下成立,是 fixture 规模的产物,不是规则属性:那批 case 里只有 1 个超过 2000 事件阈值,而今天有 3 个。规则实际在做的事是拿覆盖换 seed 成本,换多少取决于 transcript 规模分布,不取决于规则本身。
|
|
155
|
+
|
|
156
|
+
### 5.1 全局干扰(独立维度,只取 guide-weighted:oracle 行)
|
|
157
|
+
|
|
158
|
+
| 干扰等级 | case 数 | 后台检查 | 锚点 token 估算 | 泄漏 | held | 覆盖/未覆盖 |
|
|
159
|
+
|---|---:|---:|---:|---:|---:|---|
|
|
160
|
+
| off | 12 | 6 | 0 | 0 | 0 | 11 / 9 |
|
|
161
|
+
| relevant | 1 | 1 | 82 | 0 | 0 | 1 / 0 |
|
|
162
|
+
| irrelevant-conflict | 1 | 1 | 41 | 0 | 1 | 1 / 0 |
|
|
163
|
+
|
|
164
|
+
- 主表**只**用 interference = off 的 case 计算(测试里有断言),所以"全局干扰"不会被算进主调度效果,也不会替主调度背锅。
|
|
165
|
+
- 泄漏检查是"全局 id 出现在 `createdIds`/`updatedIds`/`heldIds`",三行都是 0。
|
|
166
|
+
- 冲突 case 的 `outcome` 是 `mixed`:一条正常写入 + 一条进 `heldIds`,held 不算可召回覆盖。
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### 5.2 规模梯度:阈值切换点与它的代价
|
|
171
|
+
|
|
172
|
+
四个规模 case 与 `clean-large-thin` 同源:标签、证据、分数逐项相同,唯一变量是 `padding.count`(98 / 998 / 4998 / 19998 事件)。数字直接取自报告 `cases[]`,按 `caseId` + `rowId` 取。
|
|
173
|
+
|
|
174
|
+
| case | 事件数 | legacy 检查 | legacy 覆盖 | legacy 种子 token | oracle 检查 | oracle 覆盖 | oracle 种子 token |
|
|
175
|
+
|---|---:|---:|---:|---:|---:|---:|---:|
|
|
176
|
+
| `clean-scale-100` | 98 | 1 | 2/2 | 176 | 1 | 2/2 | 176 |
|
|
177
|
+
| `clean-scale-1000` | 998 | 1 | 2/2 | 1632 | 1 | 2/2 | 1632 |
|
|
178
|
+
| `clean-scale-5000` | 4998 | 1 | 2/2 | 8103 | 0 | 0/2 | 0 |
|
|
179
|
+
| `clean-scale-20000` | 19998 | 1 | 2/2 | 32368 | 0 | 0/2 | 0 |
|
|
180
|
+
|
|
181
|
+
- **切换点正好落在 2000。** 998 事件仍然检查(`starved`,1 次 completed);4998 事件完全不检查(0 次)。两档的 `evidence` 一致(humanTurns 2、toolUnits 1、score 0.249),legacy 也各检查 1 次,所以差异只能来自 `transcriptEvents > 2000`。2090 事件的 `clean-large-thin` 同样在加权行被拦成 0 次,与这条阈值一致。
|
|
182
|
+
- **守卫的代价可以量化。** 19,998 事件下一次 fork 的种子 token 估算 32,368(约 3.2 万),守卫把它降到 0;同一 case 的标签覆盖同时从 2/2 掉到 0/2。2090 事件档同理:3399 → 0,2/2 → 0/2。省下的是整个 fork,不是更便宜的 fork。
|
|
183
|
+
- **每次 seed 成本随事件数近似线性。** 四档折算每事件 176/98 = 1.80、1632/998 = 1.64、8103/4998 = 1.62、32368/19998 = 1.62 token。98 → 19998 多出的 19,900 个事件多花 32,192 token,边际 1.62 token/事件;98 档偏高是小档固定开销(约 17 token)。
|
|
184
|
+
- **边界。** 这证明的是**触发规则**在阈值上下的差异,不证明漏记问题被解决;种子仍是完整连续前缀 `[0..throughSeq]`,不是 windowed seed。守卫把 seed 变成 0,不是因为 seed 变短了。
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 6. 指标定义(照抄,避免下次改口径)
|
|
189
|
+
|
|
190
|
+
| 指标 | 定义 | 口径警告 |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| 覆盖 coverage | 有标签的 id 里,被"主模型主动写"或"后台写"覆盖的比例;按 `mainProactive / background / held / uncovered` 分开计 | oracle 适配器给上界,不是模型实测 |
|
|
193
|
+
| 延迟 latency | 标签的 `sourceSeq` 到首次写入边界之间的**人类回合数**与 **step/end(已完成步骤)数** | 不是毫秒;本 harness 不测真实 prompt 延迟 |
|
|
194
|
+
| no_value 率 | `status = completed` 且 `outcome = no_value` 的比例;分母是所有已执行的 checks(含 deferred) | 只有 completed/no_value 进分子;抑制不产生 check,`skipped`、`failed`、`deferred` 仍留在 checks 分母 |
|
|
195
|
+
| 重复 duplicates | 已写过的 id 再次出现在检查窗口里(`reproposed`)与重复 update(`alreadyPresent`) | 重复写不算新增覆盖 |
|
|
196
|
+
| held | `heldIds` 条数;与可召回覆盖分开 | held ≠ 覆盖,也 ≠ no_value |
|
|
197
|
+
| 调用 calls | 后台检查次数、每千 step/end 的检查次数、主模型记忆工具调用数 | 调用数不等于写入成功数 |
|
|
198
|
+
| 证据 evidence | 窗口内真人回合数(以及 `collectEvidence` 的字符/工作单元分项) | 轮询重复只算一次 |
|
|
199
|
+
| 成本 cost | `lib/budget.js estTokens` 对每次检查实际送进 worker 的完整连续前缀 `[0..throughSeq]` 事件文本求和;总量只累加实际触发的 checks | `estimateOnly: true`,不是实际 token 用量;总量差异可能只来自检查次数/边界,不是 windowed seed |
|
|
200
|
+
| 阻塞 blocking | 主会话等待没有持续时间观测,报告固定为 `mainSessionWaitForCheck: null` / unknown | 不是实测的阻塞耗时;不能写成 0 |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 7. 怎么读这些数字(重要)
|
|
205
|
+
|
|
206
|
+
1. **oracle 上界与 no-value 下界之间的差,是"模型有没有找到应记事实"的空间,不是本 harness 的结果。** 真实模型落在中间某处,本 harness 不给点估计。
|
|
207
|
+
2. **guide 行与 legacy 行完全相同**:在这批 fixture 上,guide 只改变投放的规则消息,不改变什么时候检查。guide 对模型遵从性的影响只能由真实模型调用测出;本文不作结论。
|
|
208
|
+
3. **加权这一行的总估算较低,不代表每次 seed 变短**:检查次数 −3、总种子 token −41779(约 −91%),因为触发的检查次数/边界不同;每次实际 seed 仍是完整连续前缀。父会话阻塞与等待时长均未测,报告为 unknown / `null`,因此不能推导阻塞改善或等待差异;覆盖净 −3 是两股力量对冲的结果:`clean-dense-turn` 的 +4 来自"密集回合也能检查",三个越阈值 case 的 −6 与 `recent-recall` 的 −1 来自守卫和抑制,不是来自更好的语义判断。
|
|
209
|
+
4. **no-value 下界说明"更省"不等于"更好"**:同调度、worker 总是空返回时覆盖从 6 掉到 1。两边必须一起看。
|
|
210
|
+
5. **大 transcript 那个 case 是这一轮的核心里程碑**:旧路径在薄回合上仍 fork(1 次,完整前缀 seed 3399 token 估算),加权路径按 `large-transcript-thin` 拦住(0 次)。这只证明**触发规则**生效,不证明漏记问题被解决;它不是 windowed-seed 的收益。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 8. 人审候选与 gold gating 是两件事
|
|
215
|
+
|
|
216
|
+
- 候选:case 存在、标签写了 `sourceSeq`、有人读过 → 仍只是 `review: "pending"`。
|
|
217
|
+
- 金标:需要人工逐条确认后把 `review` 改成 `human-reviewed`。**本 harness 不改它**,报告里 `goldGating.reviewedCases` 恒为设备实际值(今天是 0),`goldGating.goldAllowed === false`。
|
|
218
|
+
- 结构校验强制这条纪律:`annotationSource: "human"` 但 `review` 不是 `human-reviewed` → 直接 fixture 错误;任何 `gold: true` 的合成代理 case → 直接 fixture 错误。
|
|
219
|
+
- 报告的 `status` 恒为 `synthetic-proxy-not-gold`,`gold` 恒为 `false`。要改这两项只能改数据集的机器可读状态,而那是人审之后的事。
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 9. 真实模型 A/B:工具调用协议下的双臂实测(2026-10-30)
|
|
224
|
+
|
|
225
|
+
`scripts/active-memory-ab-live.mjs` 用工具调用协议跑真实模型:模型 `deepseek-flash`,OpenAI 兼容接口 `https://api.deepseek.com`。旧的 JSON 协议运行器已随 1.2.0 删除:它测的是无工具环境的降级路径,75 次请求全部 `candidates-invalid`,不产生任何可评分信号。
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# 只看计划,不联网
|
|
229
|
+
node --import ./tests/loader-register.mjs scripts/active-memory-ab-live.mjs --dry-run
|
|
230
|
+
# 真跑(key 只从环境变量读)
|
|
231
|
+
ACTIVE_MEMORY_AB_API_KEY=... node --import ./tests/loader-register.mjs scripts/active-memory-ab-live.mjs --live --arm both --out report.json
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
两臂只差一个变量:**baseline** 每个 case 都送模型;**weighted** 只送生产调度器判定要检查的 case(`assessWake` + 抑制),其余记 `scheduled-out`。提示词(system-guide + private-consolidation + 本地工具契约 + 任务书)、工具表、转录在两臂之间完全相同。半步(`tool/call` 无配对结果)由硬守卫挡住,两臂都不发请求,所以 baseline 是 13 次调用而不是 14 次。
|
|
235
|
+
|
|
236
|
+
同一配置连跑两遍(报告是仓库外的临时 JSON,不提交):
|
|
237
|
+
|
|
238
|
+
| 指标 | baseline | weighted |
|
|
239
|
+
|---|---:|---:|
|
|
240
|
+
| 实际调用 | 13 | 8(5 个 scheduled-out)|
|
|
241
|
+
| 参与统计的标签 | 22 | 15 |
|
|
242
|
+
| 覆盖(第 1 / 第 2 遍) | 11 / 9 | 7 / 9 |
|
|
243
|
+
| 覆盖率 | 0.50 / 0.41 | 0.47 / 0.60 |
|
|
244
|
+
| 写出的记忆条数 | 11 / 9 | 7 / 9 |
|
|
245
|
+
| **precision(写出即命中)** | **1.00 / 1.00** | **1.00 / 1.00** |
|
|
246
|
+
| prompt tokens | 36,392 | 23,107(−37%)|
|
|
247
|
+
| 填了 emotion 的条数 | 3 / 3 | 4 / 5 |
|
|
248
|
+
| 填了 dominance 的条数 | 0 / 0 | 0 / 0 |
|
|
249
|
+
|
|
250
|
+
**协议对了,产出就可用了。** 同一批 case,JSON 协议下 0 条可解析;工具调用下每一次调用都写出了合法记忆,且**写出即命中**(precision 1.00,两遍都是)——没有一条是凭空编的。此前 0.77–0.80 的 precision 是漏了整理提示词的产物,不是模型行为。
|
|
251
|
+
|
|
252
|
+
**守卫的账可以算清。** 把两臂对齐到同 8 个 case,覆盖是 10 对 8(标签基数 15),差异落在下面的方差内;被跳过的 5 个 case 共 7 条标签,baseline 在那里平均只覆盖 1 条。也就是说:**−37% prompt token 换约 1/22 条标签的延迟**,不是覆盖的永久损失——游标不前移,内容仍在 pending。
|
|
253
|
+
|
|
254
|
+
**情绪只在人物侧生效。** 工程类 fixture 上 emotion 只填在涉及人的条目(3–5 条),dominance 全为 0——这正是提示词要求的:工程事实固定中性,dominance 只跟人物侧 emotion 一起填。不要把这读成「模型没填」。
|
|
255
|
+
|
|
256
|
+
边界:
|
|
257
|
+
|
|
258
|
+
- 覆盖是**词面匹配**(overlap ≥ 0.3 或命中拉丁/数字强标记),不是语义判断,也不是人工金标;
|
|
259
|
+
- 标签仍是 `synthetic-proxy`,0 条人审;
|
|
260
|
+
- **n = 2**,两遍差异可达 ±2 条,任何小于这个幅度的改动都不可分辨;
|
|
261
|
+
- 与生产仍有唯一一处差异:生产把转录作为 fork 的种子会话传入,运行器把转录渲染成带 seq 的文本附在消息里。
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 10. C 包接口(可注入适配器)
|
|
266
|
+
|
|
267
|
+
F 包与 C 包并行开发,接口先按契约落地,C 的真实现只要满足这两条就能接进来,不需要改 harness:
|
|
268
|
+
|
|
269
|
+
### 10.1 适配器输入 / 输出
|
|
270
|
+
|
|
271
|
+
适配器是 `async (ctx) => credentialPayload`,通过 `runAb({ workers: { 'no-value': fn } })` 注入(`rowIds` 可只选一行)。
|
|
272
|
+
|
|
273
|
+
`ctx`:
|
|
274
|
+
|
|
275
|
+
```text
|
|
276
|
+
caseId, rowId, condition, path ('full-prefix-fork' | 'snapshot-worker')
|
|
277
|
+
fromSeq, throughSeq, snapshotSha256, scopeGeneration // 固定快照身份
|
|
278
|
+
events // 逻辑检查范围 [fromSeq, throughSeq]
|
|
279
|
+
seedEvents // 实际 worker seed:完整连续前缀 [0, throughSeq]
|
|
280
|
+
labels, labelsInRange, existingIds // 已有 id 不能再算新增
|
|
281
|
+
interferenceInput { level, globalIds, conflictLabelIds, anchorTokens }
|
|
282
|
+
halfStep, now, transcriptEvents, evidence
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
返回(**边界身份必须由 harness 填,适配器不许自报**):
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
status: completed | skipped | deferred | failed
|
|
289
|
+
outcome: created | updated | mixed | no_value
|
|
290
|
+
createdIds / updatedIds / heldIds / reasonCode / retryable
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 10.2 凭证与游标规则(`validateCredential` / `canAdvance`)
|
|
294
|
+
|
|
295
|
+
- `checked.{fromSeq, throughSeq, snapshotSha256, scopeGeneration}` 必须与请求逐项相同,否则凭证不合法;
|
|
296
|
+
- 同一 id 不能出现在两个桶里;非 `completed` 不得声称写入了任何 id;`no_value` 不得带 created/updated;
|
|
297
|
+
- **只有** `completed`(含 `no_value`)前移游标;`deferred / skipped / failed / 凭证不合法` 一律不动游标;半步(`tool/call` 没有配对 `tool/result`)由 harness 直接判 `deferred: half-step`,worker 不参与;
|
|
298
|
+
- 相邻检查的 `fromSeq` 必须等于上一段是否推进后的游标,报告 `structureChecks` 为空才算 `ok: true`。
|
|
299
|
+
|
|
300
|
+
### 10.3 与 C 实现的边界
|
|
301
|
+
|
|
302
|
+
- 本 harness **不** fork、**不**写库、**不**读 `state.json`、不创建任何用户目录(测试里用 sentinel 断言);§9 的 live 运行器遵守同一组边界,且不留模型原文、不留转录正文、不落 key;
|
|
303
|
+
- C 的真实 worker(专用整理职责、真实工具面、真实 CAS)接进来时,只要按 §10.1 返回,就能拿到同一批指标;
|
|
304
|
+
- 现在这两行适配器是**注解预言机**与**空返回**,它们量的是"管线的上/下界",不是 worker 质量;适配器收到的 `seedEvents` 也遵循生产的完整前缀语义。
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### 10.4 与 C 包实现的字段对齐(并行开发,只读核对)
|
|
309
|
+
|
|
310
|
+
C 包的 `lib/consolidation-receipt.js` 与 `lib/consolidation-snapshot.js` 用同一套枚举与同一段覆盖范围,可以直接接进来:
|
|
311
|
+
|
|
312
|
+
| 本文 adapter payload | C 包 receipt | 说明 |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| `status` | `RECEIPT_STATUSES` = `completed \| skipped \| deferred \| failed` | 同集合 |
|
|
315
|
+
| `outcome` | `RECEIPT_OUTCOMES` = `created \| updated \| mixed \| no_value` | 同集合 |
|
|
316
|
+
| `checked.{fromSeq, throughSeq}` | `checkedRange.{fromSeq, throughSeq}` | 字段名不同,值语义相同 |
|
|
317
|
+
| `createdIds / updatedIds / heldIds / reasonCode / retryable` | 同名 | 一致 |
|
|
318
|
+
| `checked.snapshotSha256` | 由 `snapshotHash` / `captureSessionSnapshot` 持有,回执里**不含** | 一致的分工:凭证身份不能由 worker 自报 |
|
|
319
|
+
|
|
320
|
+
两个形状差异记在这里,避免以后接真转录时猜:
|
|
321
|
+
|
|
322
|
+
1. **工具调用 id 的位置**:fixture 用顶层 `toolCallId`;宿主转录用 `event.data.callId`(`tool/call`)与 `event.data.message.callId`(`tool/result`),助手消息里的 `tool-call` 块用 `block.id`。本文的 `toolCallIdOf()` 与 `detectHalfStep()` 两种都认,所以半步判定在真转录上同样成立。
|
|
323
|
+
2. **半步判定**:两边都只看"有没有配对的 `tool/result`",都不读半份转录;C 包另外会补 `interruptedTurnClosers`,本文不做这件事——它只判 `deferred`,把"补齐"留给读盘的实现方。
|
|
324
|
+
|
|
325
|
+
接入方式:C 的 worker 跑完后用 `parseReceipt` 拿到 receipt,映射成本文 §10.1 的 payload,再 `runAb({ workers: { 'no-value': adapter } })` 注入即可;本文的凭证校验与游标规则会**再**检查一遍,不信任 worker 自报。
|
|
326
|
+
|
|
327
|
+
### 10.5 结构行:回执 → 凭证的回归
|
|
328
|
+
|
|
329
|
+
第五行 `guide-weighted:structural` 只换凭证来源,不换调度:worker 产出 C 包围栏格式的回执文本,`lib/consolidation-receipt.js` 的 `parseReceipt` 解析,`receiptToCredential` 映射成 §10.1 的 payload,之后照旧走 `validateCredential` / `canAdvance`。适配器自报的 id 仍不被信任。
|
|
330
|
+
|
|
331
|
+
- **默认不跑。** 默认报告仍是四行;结构行要用 `--row guide-weighted:structural` 显式指定,避免每次全量回放都多跑一遍回执编解码。
|
|
332
|
+
- **实测(2026-10-30)与 oracle 行逐 case 完全一致。** 14 个 case 的覆盖、检查次数、种子 token 全部相等(12 个 off case 合计 11/20 覆盖、6 次检查、4240 token;`deferred` 1、`failed` 0、凭证不合法 0)。也就是说,真 worker 的产出形状(回执 + 围栏)经 `parseReceipt` 后能被 §13.6 的凭证规则无损接受,不需要为它放宽规则。
|
|
333
|
+
|
|
334
|
+
三种失败映射:
|
|
335
|
+
|
|
336
|
+
| `parseReceipt` 结果 | 映射后 status | retryable | 落点 |
|
|
337
|
+
|---|---|---:|---|
|
|
338
|
+
| `receipt-missing`(没有回执) | `failed` | true | 游标不动 |
|
|
339
|
+
| `receipt-malformed`(围栏内 JSON 坏) | `failed` | true | 游标不动 |
|
|
340
|
+
| `receipt-invalid`(结构/枚举不符) | `failed` | true | 游标不动 |
|
|
341
|
+
|
|
342
|
+
三者都**不降级成 `no_value`**。理由是语义不同:`no_value` 表示"检查过、没有新事实",按 §10.2 会推进游标;空白或截断表示"没有结论",必须可重试,否则一次坏输出会被记成一次空检查,永久跳过那段范围。
|
|
343
|
+
|
|
344
|
+
一次完整 live 执行由两臂相加:baseline **13 次** + weighted **8 次** = **21 次 HTTP**。14 个 case 里,`clean-half-step` 两臂都被硬守卫挡住,weighted 另有 5 个 `scheduled-out`。任何真实模型复跑的成本都按这个数估。
|
|
345
|
+
|
|
346
|
+
已知语义分歧:C 包的 `decideCompletion` 认为带 `reasonCode` 的 `skipped` 也可以推进游标,本 harness 只认 `completed`。映射层因此把非 `completed` 的 id 桶一律清空——`skipped` 即使带了 `createdIds` 也不保留,`heldIds` 同样丢弃。这是有意的保守:宁可丢弃一次本可结算的 `skipped`,也不让未完成的检查被计成覆盖。接真 worker 时若要改这条,应当先改 §10.2 的游标规则,而不是在映射层偷偷放行。
|
|
347
|
+
|
|
348
|
+
## 11. 已知边界与未测项
|
|
349
|
+
|
|
350
|
+
- 主模型主动率(需要真实模型调用与用户明确授权);
|
|
351
|
+
- guide 正文对模型遵从性的影响;
|
|
352
|
+
- 后台 worker 的语义判断质量(oracle 适配器是上界;§9 的 precision 是词面匹配,不是语义判断);
|
|
353
|
+
- 真实 prompt 延迟与主任务阻塞耗时;
|
|
354
|
+
- 真实 token 消耗:确定性回放只有 estimate(按每次完整前缀 seed 累加);§9 的 prompt token 来自 provider usage,但只覆盖那一轮 fixture;
|
|
355
|
+
- 窗口化 seed 不是本 harness 的语义:生产 `private.js` 以完整连续前缀 `[0..throughSeq]` 作为 seed;逻辑资格范围仍单独由 `events` / `fromSeq..throughSeq` 表示;
|
|
356
|
+
- D 包的合并账本、跨重启恢复的真实案例未覆盖:这里的"重启后同一份快照"只体现为凭证身份校验,不是真机重放。
|
|
357
|
+
|
|
358
|
+
### 11.1 积压上界(`maxPendingEvents: 600`)
|
|
359
|
+
|
|
360
|
+
`assessWake` 新增 `pendingEvents` 维度:待整理区间达到 `maxPendingEvents`(默认 600 事件)时,在真人回合这道硬门槛之后、分数与大 transcript 守卫之前,无条件是检查一次,`reason` 为 `backlog`。
|
|
361
|
+
|
|
362
|
+
这条规则给延迟加上界,而不是放宽守卫。守卫要挡的是「为一句轻话重种整段转录」,不是「把从没整理过的大会话永远挡在门外」:没有上界的话,一个大会话只要每轮都是轻话,早期决定就永远等不到检查。被守卫跳过的区间游标不前移、内容仍在 pending,所以被跳过的是延迟而不是内容;`backlog` 只是把这段延迟封顶在 600 事件。
|
|
363
|
+
|
|
364
|
+
fixture 的 `snapshot.pendingEvents` 默认为 0,表示「已整理到区间起点、只有薄增量是新的」,因此 §5 的数字不受这条规则影响。
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 12. 本轮验证(2026-10-30)
|
|
369
|
+
|
|
370
|
+
```text
|
|
371
|
+
node --check scripts/active-memory-ab.mjs ok
|
|
372
|
+
node --check scripts/active-memory-ab-live.mjs ok
|
|
373
|
+
node --check tests/active-memory-ab.test.mjs ok
|
|
374
|
+
node --import ./tests/loader-register.mjs --test tests/active-memory-ab.test.mjs
|
|
375
|
+
ℹ tests 38 / pass 38 / fail 0 / skipped 0
|
|
376
|
+
node scripts/active-memory-ab.mjs ok:true, structureChecks 为空, 14 cases
|
|
377
|
+
node scripts/active-memory-ab.mjs --row guide-weighted:structural
|
|
378
|
+
ok:true, 与 oracle 行逐 case 一致
|
|
379
|
+
node --import ./tests/loader-register.mjs scripts/active-memory-ab-live.mjs --dry-run
|
|
380
|
+
ok:true, dryRun:true, 14 cases, 计划 13 + 8 = 21 次
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
回归测试另外构造临时双检查 fixture(不改固定数据集):第二次 weighted 逻辑检查从 seq 10 开始,但所有 arm 的 seed 都是 `[0..19]` 完整连续前缀;同时断言 token 估算来自该前缀,证据与标签资格仍限于逻辑范围。两条 fork 路径的父会话等待都断言为 `null`,且 delta 中不允许伪造等待差异。
|
|
384
|
+
|
|
385
|
+
报告本身(`report.ok`)为 `true` 的含义只是:**没有结构违规**——区间连续、凭证合法、全局条目零泄漏。它**不**表示"主动记忆效果已通过验收"。
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# 绑定生命周期 — dsh-memu-memory
|
|
2
|
+
|
|
3
|
+
> 本文对应 W9 只读诊断。实现:`lib/binding-diagnostics.js`;CLI:`scripts/diagnose-bindings.mjs`;测试:`tests/binding-diagnostics.test.mjs`。诊断只读取磁盘上的持久层,不创建、不修复、不删除、不 GC、不写回。
|
|
4
|
+
|
|
5
|
+
## 12. 绑定生命周期
|
|
6
|
+
|
|
7
|
+
### 12.1 三层状态:哪些在磁盘上,哪些只在进程里
|
|
8
|
+
|
|
9
|
+
一个会话的记忆状态分成三层。只有第一层能被另一个进程从磁盘读到,后两层由运行中的插件进程持有;诊断报告把后两层明确标成不可观察,而不是猜成 0。
|
|
10
|
+
|
|
11
|
+
| 层 | 内容 | 生命周期 | 诊断可见性 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| 1. 持久绑定 | `state.json` 的 `sessions[sessionId]`:`scopeKey`、`libraryId`、开关、`memoryContext`、`inbound.pending/seen/claimed/clock`、`sleepRetryAt`、注入标记等 | 跨重启、fork、插件重载保留;只有显式状态写入才改变 | 只读快照可读;报告的核心对象 |
|
|
14
|
+
| 2. 运行时监听器 | `lib/preset-scope.js:19` 的 `attachments`,按 **agent context 对象** 键控 | 随 context 存在;context 被替换时旧监听器应释放,下一次 attach 重新挂载 | 跨进程不可读,声明为 `runtime-listener` |
|
|
15
|
+
| 3. 临时映射/缓存 | `lib/inbound.js:12-14` 的 `mediaJobs`/`calls`/`sessions`、`lib/media.js:19` 的 `captures`、`lib/private.js:213` 的 `inFlight`、`lib/curator.js:11` 的 `jobs`、`lib/state.js:938` 的 `indexCache`、`lib/paths.js:52` 的 `readFailures` | 随 promise settle、turn/end、session/disposed、下一次成功读取或下一次加载清理 | 跨进程不可读,声明为 `temporary-map` 或 `cache` |
|
|
16
|
+
|
|
17
|
+
**三层不混算。** `summary.bindings`、`summary.inbound`、`summary.unfinished` 只统计持久绑定;`summary.runtime` 只在调用方提供了进程内快照时出现,分别是 `runtimeListeners` 和 `temporaryMaps` 的合计。runtime 计数不会加进 binding 数,也不会把“持久层零候选”解释成“运行时没有残留”。同一份 state 在提供/不提供 runtime 快照时,`bindings` 与 `candidates` 必须完全一致。
|
|
18
|
+
|
|
19
|
+
`RUNTIME_STATE` 里的 `file:line` 是诊断契约的一部分。测试会逐条打开源文件、检查对应行确实声明了该名字;源文件移动后必须同步更新 `RUNTIME_STATE`,否则测试直接失败,避免报告印出过期位置。
|
|
20
|
+
|
|
21
|
+
### 12.2 生命周期各阶段
|
|
22
|
+
|
|
23
|
+
- **首次绑定**:会话第一次进入记忆流程时创建 `sessions[sessionId]`。同一 `sessionId` 重连只复用这一条绑定,不按标题、不按 agent 实例、不按时间新建。文件级重复读取也必须幂等:同一 `state.json` 连续诊断两次,`bindings` 与 `candidates` 相同,字节和 mtime 不变。
|
|
24
|
+
- **开关打开但还没有库**:`useLibrary: true` 且 `libraryId: null` 本身不是故障。文件夹没有库时,注入路径用 `createLibrary: false` 延迟创建,这是文档化的形状。只有快照里存在“完成过注入”的证据(`injectedIds` 非空或 `lastInjectedTurn > 0`)时,才把 `library-enabled-but-unbound` 作为候选;否则只计入 `unbound-basis-unknown` 未知项,不虚增候选。
|
|
25
|
+
- **ctx 替换**:agent context 被替换只影响第 2 层监听器,不应改写 `sessions[sessionId]`。替换前后持久绑定的 `bindings`、队列、作用域和候选必须相同;变化只出现在 runtime 的 `presetAttachments` 计数上。
|
|
26
|
+
- **同标题不同 id**:标题只是标签,`sessionId` 才是身份。两个会话可以标题完全相同但一个是已绑定、一个是未绑定;`sameTitleGroups` 会把它们放在同一组,给出 `bound` 数量、各自的 `libraries`(未绑定位置为 `null`)、`mergeable: false`、`identity: "sessionId"`,并且从不输出标题原文。诊断不会因为标题相同就合并、复制或删除绑定。
|
|
27
|
+
- **预设消失又出现**:`agents` 是活 agent 视图,只能证明“在场”,不能证明“缺席”。预设从列表消失时 `host.status` 变为 `partial`(或 `not-loaded`),`host-session-missing` 被扣住;绑定、作用域和待注入队列原样保留。预设回来时仍是同一条绑定,不产生重复。
|
|
28
|
+
- **删除会话**:睡眠整理报告会话已删除时,绑定保留并打上 `sessionGone`。这是“保留以便恢复”的标记,不是删除指令。若宿主会话注册表同时权威地不列出该 id,会额外出现 `host-session-missing`;它是两个独立事实,和 `pending-inbound` 可以同时存在。作用域、`libraryId`、待注入消息都不因删除标记而丢失。
|
|
29
|
+
- **恢复会话**:同一 `sessionId` 恢复后,`sessionGone` 应为 `false`,绑定继续使用原来的库、作用域和队列;诊断只观察这个状态,不负责清除标记,也不写回。恢复后的候选只剩仍然存在的独立事实(例如队列里的待注入消息)。
|
|
30
|
+
- **宿主查询失败**:`host` 缺失、`not-loaded`、`partial`、空权威列表、活 agent 列表或未知来源都不能推出“会话不存在”。报告保留全部绑定、`libraryId`、`memoryContext.scopeKeys`、`inbound.pending`,只增加 `host-unavailable`/`host-not-loaded`/`host-partial` 等未知项,绝不下发 `host-session-missing` 删除信号。
|
|
31
|
+
- **库指针异常**:`libraryId` 指向不存在的库时报告 `library-missing`,但不改写指针;库被归档时报告 `library-archived`,但归档库仍在磁盘上,不等于可以解绑。
|
|
32
|
+
|
|
33
|
+
### 12.3 诊断字段
|
|
34
|
+
|
|
35
|
+
`diagnoseBindings()` 返回一个 JSON-safe 报告:
|
|
36
|
+
|
|
37
|
+
| 字段 | 含义 |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `schema` | `dsh-memu-memory/binding-diagnostics@1` |
|
|
40
|
+
| `generatedAt` | 报告生成时刻(由 `now` 决定,便于测试) |
|
|
41
|
+
| `readOnly` / `mode` | 固定 `true` / `report-only` |
|
|
42
|
+
| `refusals` | 明确拒绝的动作:`gc`、`delete-binding`、`delete-session`、`delete-library`、`write-state` 等 |
|
|
43
|
+
| `thresholds` | `{ idleInspectionDays: 30, idleIsDeletionTrigger: false }` |
|
|
44
|
+
| `state` | 快照文件 `file`、`status`(ok/missing/corrupt/unreadable)、`bytes`、`mtimeMs`、`sha256` |
|
|
45
|
+
| `analysis` | 文件不可读时为 `null`;可读时为下面的分析对象 |
|
|
46
|
+
| `unknowns` | 不可观察或不可判定的项,带 `code`、`detail`、`keeps` |
|
|
47
|
+
| `readOnlyProof` | 读取前后的 sha256、字节数、mtime 对比和 `unchanged` |
|
|
48
|
+
|
|
49
|
+
`analysis.summary` 的计数:`bindings`、`host`(status/source/complete/sessions/unboundHostSessions)、`switches`、`libraries`、`memoryContext`、`guide`、`inbound`、`sleep`、`unfinished`、`runtime`(无进程内快照时为 `null`)、`candidates`、`sameTitleGroups`。
|
|
50
|
+
|
|
51
|
+
每个 `analysis.bindings[]`(由 `bindingFacts` 产生)包含:`sessionId`、`switches`、`effective`、`folder`(仅 `scopeKeyHash`,不含路径)、`domain`、`library`、`memoryContext`(仅 `present` 和 `scopeKeys`)、`guide`、`inbound`、`sleep`、`recall`、`injection`、`activity`。`activity` 同时给出精确的 `idleMs` 和仅用于展示的 `idleDays`(四舍五入到 1 位小数)。默认不输出错误原文;`--redact` 时连 session id 也做伪名化,标题和文件夹路径从不输出。
|
|
52
|
+
|
|
53
|
+
每个候选形如 `{ sessionId, kind, inspectionOnly: true, action: "inspect", reason, evidence }`。候选种类包括 `session-gone`、`host-session-missing`、`library-missing`、`library-archived`、`library-enabled-but-unbound`、`pending-inbound`、`sleep-retry`、`recall-degraded`、`reinject-pending`、`idle-inspection`。**候选是“请人工看一眼”的提示,不是删除队列。**
|
|
54
|
+
|
|
55
|
+
`analysis.runtime`:调用方提供进程内快照时为 `{ status: "provided", source, layers }`;未提供时为 `{ status: "unknown", reason, layers }`,其中 `layers` 仍列出 `RUNTIME_STATE` 声明的层,但不含任何进程内计数。
|
|
56
|
+
|
|
57
|
+
### 12.4 30 天阈值:检查线,不是删除触发器
|
|
58
|
+
|
|
59
|
+
`INSPECTION_DAYS = 30`。只有 `activity.idleMs > 30 * 24 * 60 * 60 * 1000` 才产生 `idle-inspection` 候选;**恰好 30 天不报,超过 1 毫秒才报**。判定使用精确毫秒 `idleMs`,`idleDays` 只用于展示,不能反过来参与比较。
|
|
60
|
+
|
|
61
|
+
这条线只回答“要不要人工检查”。它不会:
|
|
62
|
+
|
|
63
|
+
- 删除会话、绑定、记忆或库;
|
|
64
|
+
- 自动解绑或清空待注入队列;
|
|
65
|
+
- 重置 `sleepRetryAt`、`lastTurnAt` 或其他时间戳;
|
|
66
|
+
- 触发 GC、flush、mark-injected 或任何状态写入。
|
|
67
|
+
|
|
68
|
+
报告的 `thresholds.idleIsDeletionTrigger` 固定为 `false`,`REFUSALS` 固定包含 `gc` 与各类删除动作。即使报告里出现 `idle-inspection`,也只是提示。
|
|
69
|
+
|
|
70
|
+
### 12.5 自动 GC 必须另行确认
|
|
71
|
+
|
|
72
|
+
W9 的交付范围是只读诊断,**不包含自动 GC**。将来若要做自动回收,必须作为独立工作包另行获得用户确认,并至少满足:
|
|
73
|
+
|
|
74
|
+
1. 宿主权威确认目标会话确实已删除,而不是仅凭标题、超时或本地快照推断;
|
|
75
|
+
2. 有可恢复的备份,且备份本身不被同一次清理覆盖;
|
|
76
|
+
3. 在事务中重读最新状态、重新校验绑定与队列,避免用过期快照删除新数据;
|
|
77
|
+
4. 留下可审计记录,说明删了什么、依据是什么、如何恢复;
|
|
78
|
+
5. 永不删除记忆库本身,永不丢失 `inbound.pending`,恢复会话时永不丢失作用域。
|
|
79
|
+
|
|
80
|
+
在这些条件满足前,诊断报告只做候选标记;任何“清理”“解绑”“删库”都需要用户显式操作。
|
|
81
|
+
|
|
82
|
+
### 12.6 CLI 与只读证据
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node scripts/diagnose-bindings.mjs --state <state.json> [--json] [--redact] [--now <ISO>]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
退出码:
|
|
89
|
+
|
|
90
|
+
| 码 | 含义 |
|
|
91
|
+
|---|---|
|
|
92
|
+
| 0 | 成功生成报告,没有检查候选 |
|
|
93
|
+
| 1 | 成功生成报告,有检查候选(仍只是提示,没有改动任何东西) |
|
|
94
|
+
| 2 | 用法错误(未知参数、非法 `--now`) |
|
|
95
|
+
| 3 | 没有报告:state.json 缺失、损坏或不可读;没有创建目录、没有修复文件 |
|
|
96
|
+
|
|
97
|
+
CLI 只写 stdout/stderr。它不创建父目录,不创建缺失的 `state.json`,不隔离损坏文件,不更新时间戳;文本报告会打印 `readOnlyProof`。测试覆盖 0/1/2/3 四种退出码、缺失路径不建目录、已有文件字节与 mtime 不变。
|
|
98
|
+
|
|
99
|
+
`readBindingSnapshot()` 直接 `fs.readFileSync`,不调用会修复文件的 `loadState()`;`diagnoseBindings()` 在分析后再哈希一次并比较 mtime,形成 `readOnlyProof`。缺失或损坏时 `analysis` 为 `null`,报告只说明状态并保持现场。
|
package/docs/contributing.md
CHANGED
|
@@ -20,7 +20,7 @@ node scripts/acceptance-local.mjs # 50/50;视觉证据只证明本次已审
|
|
|
20
20
|
npm run check # node --check + i18n 键对齐 + package.json 可解析
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
**结果口径**:以 `node --test` 输出的 `ℹ tests / pass / fail / skipped`
|
|
23
|
+
**结果口径**:以 `node --test` 输出的 `ℹ tests / pass / fail / skipped` 为准。用例数变了先问「是不是我加的」,失败先问「是不是并发写了同一个库」——这仓库真会多个会话同时改它。文档里出现的测试数字一律当作**带日期的快照**:2026-10-29 实测 392 tests / 391 pass / 0 fail / 1 skipped(真实引擎用例按设计跳过);更早的 321 / 327 / 333 等是历史转述,原报告已缺失、不可复核,不能当作当前保证。
|
|
24
24
|
|
|
25
25
|
### 4.3 每类改动的连带清单
|
|
26
26
|
|
|
@@ -57,7 +57,7 @@ npm run check # node --check + i18n 键对齐 + package.j
|
|
|
57
57
|
|
|
58
58
|
### 5.1 新增一个模型工具
|
|
59
59
|
|
|
60
|
-
1. `lib/tools.js
|
|
60
|
+
1. `lib/tools.js`:实际走 `toolBase(name, description, parameters, execute, options)`(`lib/tools.js:43`),它包一层 `defineTool({ name, description, parameters, output: { schema, render }, timeoutMs, isConcurrencySafe, execute(args, exec) })`。字段名是 **`parameters`**(不是 `inputSchema`)、输出契约在 **`output.schema`**、`timeoutMs` 是**正数**(默认 60000);`execute` 拿 `(args, exec)`,会话 id 用 `exec.agent.session.header.id`(§6.3)。
|
|
61
61
|
2. **描述是给模型看的契约**:写清默认值、拒绝条件、返回形状——模型读不到代码。
|
|
62
62
|
3. 主 agent **不注册**全局写入类工具(§1.2)。
|
|
63
63
|
4. `lib/index.js` 注册;`README.md` 工具表加一行。
|
|
@@ -75,7 +75,9 @@ npm run check # node --check + i18n 键对齐 + package.j
|
|
|
75
75
|
|
|
76
76
|
### 5.3 新增一个 RPC 方法
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
<!-- rpc-method-count: 83 -->
|
|
79
|
+
|
|
80
|
+
1. `lib/rpc.js` 的 `switch` 加 case(共 83 个方法,按功能分组,别乱插)。
|
|
79
81
|
2. 返回**形状稳定**:客户端 `call()` 直接解构,列表类给 `{items, total}` 而不是裸数组。
|
|
80
82
|
3. `revisionOnly` 分支只回 `{revision, stamp}`、**不要求 sessionId**(三端轮询,设置页与独立页没会话)、**不塞业务数据**(每 2.5 秒一次);顺带 `noteMemoryBeat()`,`memu_status` 靠它分「没写」与「没人轮询」。
|
|
81
83
|
4. 写成功后 `bumpMemoryRevision()`/`notifyMemoryWrite()`(§7);能让 `state.json` 或索引落盘的写,`state.js` 自动记写事件供 `memu_status` 展示。
|
|
@@ -123,7 +125,7 @@ npm run check # node --check + i18n 键对齐 + package.j
|
|
|
123
125
|
- [ ] `injectedIds` 记的是**真正进入文本**的条目?(§8.4)
|
|
124
126
|
- [ ] token 预算走 `lib/budget.js`,没有 `tokens * 3`?(§8.4)
|
|
125
127
|
- [ ] `pending_review` 被挡在注入与召回之外?(§3.9)
|
|
126
|
-
- [ ]
|
|
128
|
+
- [ ] 没有每步注入、记忆规则与动态记忆都没进 system prompt?
|
|
127
129
|
|
|
128
130
|
**数据安全**
|
|
129
131
|
- [ ] 读失败没被当成空数据(改名 `*.corrupt-<ts>` + 拒绝写入)?(§2.3 铁律一)
|