@a9i5k4/dsh-auto-memory 2.5.3 → 3.0.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/README.md +171 -1
- package/README.zh-CN.md +171 -1
- package/docs/CONTRIBUTORS.html +471 -0
- package/docs/HANDOFF-CRITERIA.md +92 -0
- package/docs/INTEGRATION-ANALYSIS.md +350 -348
- package/docs/USER-GUIDE.en.md +56 -1
- package/docs/USER-GUIDE.zh-CN.md +57 -2
- package/docs/internal/ACCEPT-35-LIVE.md +143 -0
- package/docs/internal/ACCEPTANCE-20260914.md +90 -0
- package/docs/internal/ARCH-REVIEW-BRIEF.md +411 -0
- package/docs/internal/ARCH-REVIEW-REQUEST.md +201 -0
- package/docs/internal/ARCH-REVIEW-ROUND2.md +169 -0
- package/docs/internal/ARCH-REVIEW-ROUND3.md +206 -0
- package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +314 -0
- package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +192 -0
- package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +72 -0
- package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +131 -0
- package/docs/internal/DECISIONS-20260914-SESSION.md +269 -0
- package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +219 -0
- package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +132 -0
- package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +13 -0
- package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +74 -0
- package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +352 -0
- package/docs/internal/GPT-REVIEW-PROMPT.md +216 -0
- package/docs/internal/GROUP-WEBHOOK-SETUP.md +33 -0
- package/docs/internal/KICKOFF-P0.md +254 -0
- package/docs/internal/MASTER-PLAN-3.0.md +411 -0
- package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +85 -0
- package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +222 -0
- package/docs/internal/PENDING-FIXES-20260916.md +289 -0
- package/docs/internal/RAG-KARPATHY-PROGRAM.md +229 -0
- package/docs/internal/REPORT-P0-NIGHTLY.md +212 -0
- package/docs/internal/REPORT-P5-ACCEPTANCE.md +31 -0
- package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +153 -0
- package/docs/internal/REVIEW-WB-GRAPH-SELF.md +81 -0
- package/docs/internal/ROADMAP-20260917-WEEK.md +305 -0
- package/docs/internal/ROADMAP.md +106 -0
- package/docs/internal/RUN-P0-NIGHTLY.md +227 -0
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +175 -0
- package/docs/internal/S10-GAPS-PLAIN-20260917.md +125 -0
- package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +360 -0
- package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +90 -0
- package/docs/internal/THREE-LAYER-CONTRACT.md +210 -0
- package/docs/internal/TODO-BACKLOG.md +263 -142
- package/docs/internal/TODO-GRAPH.html +715 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +493 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +703 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +715 -0
- package/docs/internal/WB-FORMAT-CONVENTION.md +112 -0
- package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +71 -0
- package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +56 -0
- package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +787 -0
- package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +112 -0
- package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +230 -0
- package/docs/prompts/M8-3-enable-verify.md +49 -49
- package/lib/acceptance.js +71 -0
- package/lib/activation-host.js +90 -9
- package/lib/activation-inbox.js +25 -7
- package/lib/board-mode.js +30 -0
- package/lib/client.js +878 -75
- package/lib/context-bridge.js +2 -2
- package/lib/context-host.js +70 -6
- package/lib/engine-identity.js +149 -0
- package/lib/engine-switch.js +247 -0
- package/lib/episodic-store.js +11 -10
- package/lib/evidence-store.js +2 -2
- package/lib/fact-store.js +1 -1
- package/lib/fs-retry.js +46 -0
- package/lib/index.js +1987 -153
- package/lib/intent-clean-safe.js +40 -0
- package/lib/intent-clean.js +12 -16
- package/lib/l0-extract.js +263 -149
- package/lib/l0-index-sync.js +195 -0
- package/lib/l0-index.js +349 -239
- package/lib/ledger-criteria.js +142 -0
- package/lib/m7-index-sync-host.js +65 -4
- package/lib/m7-wire.js +3 -3
- package/lib/memory-anchor.js +56 -1
- package/lib/memory-envelope.js +252 -0
- package/lib/memory-hub.js +14 -4
- package/lib/memory-mutation.js +246 -0
- package/lib/memory-writer.js +204 -24
- package/lib/procedure-observation.js +48 -0
- package/lib/procedure-store.js +34 -17
- package/lib/python-setup.js +1 -1
- package/lib/rerank-host.js +160 -0
- package/lib/rules-layer.js +261 -0
- package/lib/semantic-js.js +15 -0
- package/lib/shadow-retrieval.js +3 -3
- package/lib/state-commit.js +245 -0
- package/lib/subagent-gc.js +4 -8
- package/lib/tier-layer-inject.js +650 -0
- package/lib/tier0-catalog.js +693 -0
- package/lib/water-window.js +263 -186
- package/lib/wb-contract.js +495 -0
- package/lib/wb-sidecar.js +839 -0
- package/lib/ws-overview-rank.js +2 -2
- package/package.json +1 -1
- package/python/m7_embedding_v1.py +5 -5
- package/python/worker_semantic_v1.py +17 -6
- package/python/worker_v1.py +38 -4
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 跨会话 / 跨 Agent 记忆检索 · 调研报告(2026-09-14)
|
|
2
|
+
|
|
3
|
+
> 与 `CROSS-SESSION-SEARCH-PATH-DECISION.md`(决策页)配套:那页定"走哪条路",本页回答"**具体怎么做、优缺点是什么**"。
|
|
4
|
+
> 证据分级:**[实测]** = 本机跑出来的数字;**[实证]** = 有公开 benchmark/事故报告支撑;**[经验]** = 工程界共识/无硬数据;**[估算]** = 由实测外推。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、一句话结论
|
|
9
|
+
|
|
10
|
+
**词法为主干(`node:sqlite` FTS5,Node 内置、零依赖)+ 轮次级分块 + 字节 offset 增量 + 逐文件跳过计数**,语义向量只作**可选增益**(装了走 RRF 融合,不装也必须独立可用)。
|
|
11
|
+
理由:LongMemEval-S(500 题,all-MiniLM-L6-v2)上 **BM25-only R@5 = 86.2% → BM25+向量 = 95.2% → 纯向量 96.6%** —— 加向量是单项收益最大的一步(+9pp),但混合与纯向量只差 1.4pp,**混合才是性价比点**([来源](https://raw.githubusercontent.com/rohitg00/agentmemory/a8e7d19a814a24a21818afc715f3301b3eaeee80/benchmark/LONGMEMEVAL.md))。**[实证]**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 二、本机实测:语料盘点与格式规则 [实测]
|
|
16
|
+
|
|
17
|
+
### 2.1 体量(决定了"能不能全量索引")
|
|
18
|
+
|
|
19
|
+
| 来源 | 文件数 | 体量 | 备注 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| DSH 会话 `~/.dsh/sessions/**/session*.jsonl.zstd` | 191 | **321 MB**(压缩态) | 最大单文件 25 MB 压缩 |
|
|
22
|
+
| WorkBuddy `~/.workbuddy/projects/**/*.jsonl` | 89 | 155 MB | 最大单文件 **53 MB** |
|
|
23
|
+
| ZCode `~/.zcode/cli/rollout/*.jsonl` | 3 | 80 MB | **model-io 日志:同一段历史重复 N 次** |
|
|
24
|
+
| Codex `~/.codex/sessions/**/rollout-*.jsonl` | 17 | 46 MB | 最大单文件 25 MB |
|
|
25
|
+
| Claude Code `~/.claude/projects/**/*.jsonl` | 4 | 0.1 MB | 本机几乎没在用 |
|
|
26
|
+
| Kimi `~/.kimi-code/sessions` | 0 | — | 目录为空 |
|
|
27
|
+
| 外部记忆 markdown(CLAUDE/AGENTS/MEMORY.md) | 55 | 0.5 MB | 成本最低、收益最直接 |
|
|
28
|
+
|
|
29
|
+
### 2.2 DSH 会话的压缩比与正文占比 [实测]
|
|
30
|
+
|
|
31
|
+
抽样 4 个文件(含 3.4 MB 级):**压缩比 2.1x、正文占解压文本 52%**。
|
|
32
|
+
→ 外推:321 MB 压缩 ≈ **0.67 GB 解压文本 ≈ 0.35 GB 可索引正文**。
|
|
33
|
+
→ 速度:解码 5.5 MB 压缩(11 MB 文本、1.7 万事件)耗时 **0.7 s** → **全量扫一遍 DSH 会话约 1 分钟 CPU** [估算]。**结论:一次性全扫完全可接受,问题不在解码速度,而在索引体量。**
|
|
34
|
+
|
|
35
|
+
### 2.3 各源正文抽取规则(已逐源实测字段路径)
|
|
36
|
+
|
|
37
|
+
| 源 | 取哪些 | 字段路径 | 必须排除 |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| DSH | 消息事件文本 | 现有解码器 + 事件 type 过滤 | 系统提示/工具结果大块 |
|
|
40
|
+
| Claude Code | `user` / `assistant` | `message.content`(字符串或 blocks) | `file-history-snapshot`、`mode` |
|
|
41
|
+
| Codex | `response_item` | `payload.content[].text` | **`session_meta.payload.base_instructions.text`(系统提示,每文件 ~17 KB)**、`world_state` |
|
|
42
|
+
| WorkBuddy | `message` | `content[].text` | `reasoning`(思维链,默认不索引)、`file-history-snapshot` |
|
|
43
|
+
| ZCode | `model_io` | `request.body.input[].content` / `request.messages[].content` + `response` | **重复消费**:每次请求重发全史 |
|
|
44
|
+
|
|
45
|
+
**两个坑(不避开就会把索引撑爆或污染结果)**:
|
|
46
|
+
1. **Codex 的 `base_instructions` 是系统提示**(每个会话一份、上万字符),索引它等于把"我的插件说明"混进用户记忆。
|
|
47
|
+
2. **ZCode 是请求日志**:同一段对话在每次 API 调用里重复出现,**必须按 `turnId`/`requestId` 去重或只取最后一条**,否则 80 MB 里绝大多数是重复。[实测]
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 三、业界实证:怎么做才不翻车
|
|
52
|
+
|
|
53
|
+
| 议题 | 结论 | 证据 |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| 架构 | 词法打底 + 可选向量 + RRF 融合;RRF 是零调参行业默认 | **[实证]** LongMemEval 数据;[ADR 016](https://github.com/psmfd/agent-experise-api/blob/main/adrs/016-hybrid-rrf-search.md) |
|
|
56
|
+
| 分块粒度 | **轮次(round = user+assistant)优于会话级**;进一步压成"单条 fact"会因信息损失**整体变差**(只在多会话推理上更好) | **[实证]** LongMemEval 论文 §5.2 [arXiv 2410.10813](https://arxiv.org/abs/2410.10813) |
|
|
57
|
+
| 增益技巧 | 多键索引(抽取 user facts 扩展 key)recall@k **+9.4%**、QA +5.4%;**时间感知索引 + 查询扩展 时序 recall +6.8~11.3%** | **[实证]** 同上 |
|
|
58
|
+
| 增量 | 指纹 `(path, size, mtime)`;更稳是内容哈希做 chunk 级;**append-only JSONL 记字节 offset 续读**,不重解析 | **[经验]** |
|
|
59
|
+
| 容错 | **跳过 + 计数 + quarantine 表**,逐文件/逐行隔离;绝不 fail-closed | **[实证]** LightRAG 曾因"索引 FAILED 卡死所有查询",修复方式是显式 rebuild([PR#3177](https://github.com/HKUDS/LightRAG/pull/3177))—— 与 DSH 上游同款病灶 |
|
|
60
|
+
| 模型 | 中英混合:bge-small(24M/384d) 或 multilingual-e5-small(118M/384d);**bge-m3 2.27GB 与 130MB 预算不匹配**;量化可压 1/4 | **[经验]** |
|
|
61
|
+
| 隐私 | 入索引前做**路径级排除名单**(`.env`/credentials)+ 展示层高熵串脱敏;**全密文与可检索互斥**,不要设计成全密文本地检索 | **[经验]** |
|
|
62
|
+
| 换模型 | 必须版本化模型标识并全量重算,否则向量空间混用 | **[实证]** [Neo4j 迁移教训](https://neo4j.com/labs/agent-memory/how-to/migrate-embedding-model/) |
|
|
63
|
+
|
|
64
|
+
本机已有的可复用件:`decodeZstdFrames(Head)`(已按 PR#29 加固)、`_jsSemanticRank`(零依赖 JS 语义臂)、`_semanticRankBest`(python dense 择优)、`l0-extract-pre.js`(L0 摘要抽取)、`discover()`(外部源扫描,已抓 md `content`)。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 四、三种实现方案的优缺点
|
|
69
|
+
|
|
70
|
+
### 方案 1:FTS5 词法索引(`node:sqlite`,Node 内置)— **推荐做 P0**
|
|
71
|
+
- **怎么做**:`node:sqlite`(Node ≥22.5 内置,本机 v24.18.0 已实测可用)建 `chunks(source, sids, ts, role, text)` + FTS5 虚表;写入按轮次切块;查询 `MATCH` + 元数据过滤(时间/工具/工作区)。
|
|
72
|
+
- **优点**:零依赖、不下载模型;毫秒级查询;索引体量小(估 FTS5 约为正文 30–60% → **100–200 MB** 级 [估算]);实现与测试成本最低;完全离线。
|
|
73
|
+
- **缺点**:**隐含偏好类查询是硬伤**(BM25 该类 60% vs 混合 83.3%);同义/换词检索弱。
|
|
74
|
+
- **适用**:所有用户默认路径(含没装引擎的)。
|
|
75
|
+
|
|
76
|
+
### 方案 2:向量索引(复用/扩展现有引擎)
|
|
77
|
+
- **怎么做**:轮次切块 → 现有 provider 抽象(bge-m3 / hash)或新增小模型 → 向量存 sqlite BLOB(int8)或分开的 `.bin`;查询 top-K 后与词法 RRF 融合。
|
|
78
|
+
- **优点**:召回最高(纯向量 96.6%),改述查询也能命中。
|
|
79
|
+
- **缺点**:**成本最高的部分** —— 本机语料量级 [估算] 需 embed ~25–40 万块;384 维 float32 约 **400–600 MB**(int8 约 100–150 MB);CPU 全量重算是分钟~小时级;模型/版本漂移要全量重算;现有 python 向量落盘是 JSON(`vectors-*.json`),**这个量级不能继续用 JSON 存**。
|
|
80
|
+
- **适用**:装了语义引擎的进阶用户;作为增益项。
|
|
81
|
+
|
|
82
|
+
### 方案 3:轻量"倒排 + JS 语义臂"(零依赖兜底)
|
|
83
|
+
- **怎么做**:纯 JS 倒排表(词→chunk ids)+ 现有 `_jsSemanticRank` 重排;索引落单个紧凑 jsonl/二进制。
|
|
84
|
+
- **优点**:兼容老 Node;无 sqlite 依赖;实现小。
|
|
85
|
+
- **缺点**:自建倒排的查询质量/性能都不如 FTS5;内存占用随语料涨;容易变成"自研半成品数据库"。
|
|
86
|
+
- **适用**:**仅当检测不到 `node:sqlite` 时的降级路径**(DSH Desktop 的 Electron Node 22 需实测),不要作为主路径。
|
|
87
|
+
|
|
88
|
+
### 横向对比
|
|
89
|
+
|
|
90
|
+
| 维度 | 方案 1 FTS5 | 方案 2 向量 | 方案 3 JS 倒排 |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| 依赖/下载 | 0 | 130 MB+(可选) | 0 |
|
|
93
|
+
| 首次建索引 | 分钟级 | 分钟~小时级 | 分钟级 |
|
|
94
|
+
| 索引体量 [估算] | 100–200 MB | +100–150 MB(int8) | 50–150 MB |
|
|
95
|
+
| 召回(LongMemEval 口径) | 86.2% | 96.6% | ~86% 或更低 |
|
|
96
|
+
| 实现风险 | 低 | 中(模型/版本/存储) | 中(自研存储) |
|
|
97
|
+
| 老 Node 兼容 | 需降级 | 需引擎 | 最好 |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 五、推荐组合与分阶段
|
|
102
|
+
|
|
103
|
+
**P0(零依赖基线,必须独立可用)**:DSH 会话 + 外部记忆 markdown 入 FTS5 + 轮次分块 + offset 增量 + 跳过计数 + 来源/时间元数据 + 时间感知查询扩展(词法下性价比最高项,+6.8~11.3% 时序召回)。**目标:不装任何模型也能检索。**
|
|
104
|
+
**P1(外部 Agent 会话)**:Claude Code / Codex / WorkBuddy / ZCode 适配器(按 §2.3 规则;ZCode 先按 turnId 去重),**逐源可开关**、单源失败不影响其他源。
|
|
105
|
+
**P2(语义增益)**:小模型(bge-small / e5-small 量级)向量 + RRF 融合;沿用"模型标识版本化 + 不匹配即重算"。
|
|
106
|
+
**P3(体验)**:`scope='sessions'` 改为插件原生(有 DSH sessionQuery 就补充、没有/失败就用自建索引);面板里显示"索引了多少条 / 跳过了几个文件"。
|
|
107
|
+
|
|
108
|
+
**硬性预算(默认值建议)**:时间窗 90 天 + 单源上限 2 万块 + 总量上限 10 万块 + 单文件解码上限 16 MB;超限只索引"最近优先"并**在面板如实显示跳过/截断数量**。理由:本机语料 [估算] 会产生 25–40 万块,无上限必然失控。
|
|
109
|
+
|
|
110
|
+
**隐私默认**:入索引前路径级排除(`.env`、credentials、密钥目录)+ 高熵串脱敏;索引只落本机 `~/.dsh/memory/`;提供"一键清除索引"与工作区排除名单。
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 六、风险清单(按优先级)
|
|
115
|
+
|
|
116
|
+
1. **fail-closed 传染**(最高危):任何"单文件坏 → 整库不可用"的设计直接否决 —— 这是 DSH 上游正在发生的病灶,必须反着做。
|
|
117
|
+
2. **规模失控**:600 MB 压缩语料全量索引必然爆预算 → 必须时间窗 + 条数上限 + 如实计数。
|
|
118
|
+
3. **敏感内容入库不可逆**:脱敏要在入索引前;排除名单要能改并支持重建索引。
|
|
119
|
+
4. **热路径阻塞**:索引只在空闲/后台做,单批限量;查询只扫 top-K。
|
|
120
|
+
5. **重复内容污染**(ZCode 请求日志、Codex 系统提示):先做源级去重与字段白名单,否则"检索到 10 条其实是一条"。
|
|
121
|
+
6. **模型/版本漂移**:向量必须带模型标识,换模型全量重算。
|
|
122
|
+
7. **格式漂移**:各家会话格式会变 → 适配器版本化 + 解析失败即跳过并计数(不要抛给用户)。
|
|
123
|
+
8. **跨工具隐私**:索引别人的记忆目录(Claude Code / Codex)等于把别的工具的对话搬进来 → 默认只索引"用户勾选过的源"。
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 七、与上游那份缺陷的关系
|
|
128
|
+
|
|
129
|
+
- 本方案**不依赖** DSH 的 `sessionQuery`:自建索引是主路径,它只作可选补充。→ 上游那个 fail-closed 缺陷**不再是阻塞**。
|
|
130
|
+
- 建议仍单独上报(可并入 #5732 系列):①v0 日志里 `subagent/descriptor` v2 被编解码器硬拒(DSH 自己写的数据);②索引器 fail-closed 且无容错开关。社区同族:#4811、#4910、#5694。
|
|
131
|
+
- 决策状态:**等大排期一起拍板,暂不动工**(用户 2026-09-14 决定)。
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# 规划层裁决记录 · 2026-09-14 夜(思维拟合会话)
|
|
2
|
+
|
|
3
|
+
> **性质**:本文是规划层留档,不是执行文档。记录 2026-09-14 晚与用户逐轮问答得出的**全部裁决与前提修正**。
|
|
4
|
+
> **为什么单独留档**:用户明确「规划层的内容要比执行层重要非常非常多」;这些结论由多轮拟合得出,**散落在对话里会随压缩丢失**(本会话已压缩一次)。
|
|
5
|
+
> **上游**:`ARCH-REVIEW-ROUND2.md`(投喂操作件)、`reviews/PLAN-gpt6astra-round2-20260914.md`(GPT 交付)、`reviews/CLAIM-VERIFICATION-20260914.md`(12 条核实)。
|
|
6
|
+
> **下游**:待产出的《合并总纲》。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 0. 本次会话做了什么
|
|
11
|
+
|
|
12
|
+
以「一问一答」方式,把 GPT 方案里的 7 个待裁决项(R1–R7)逐个结合**本机实测状态**向用户确认,过程中**新发现并新增了第 8 条**,并**推翻了三处前提**。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. 推翻的三处前提(最重要)
|
|
17
|
+
|
|
18
|
+
### 1.1 GPT 方案假设白板是「单会话快照」——错
|
|
19
|
+
|
|
20
|
+
- 用户:**白板(即 graph)是接续的关键因素**;「只要是一个工作区的接续的不同对话,都要是同一张图」。
|
|
21
|
+
- 补充机制:**被接续的旧窗口会存档废弃**,接续链上有编号(接续 #21 / #22)。因此**同一时刻通常只有一个活跃写入者**;用户另开的无关对话「也可以共享这一张图」。
|
|
22
|
+
- 结论:白板不是会话级临时态,而是**工作区级的持久地图**。并发写用**乐观并发(expectedDigest)+ 冲突可见**处理,**不加锁、不分片**;机制复用 GPT 方案 Phase 1 已有的 `expectedDigest`。
|
|
23
|
+
- 影响:GPT 的 **Phase 4 需与既有 `WB-GRAPH-INTEGRATION-PLAN.md`(386 行)合并**,不是照做。
|
|
24
|
+
|
|
25
|
+
### 1.2 GPT 方案只治「检索算法」——错,最痛的病在「注入表达」
|
|
26
|
+
|
|
27
|
+
- 用户原话:**「这个 just for reference 说得太轻了,模型注意力没有在这上面。」**
|
|
28
|
+
- 已定位到确切代码:`lib/index.js:463` 每次注入的开场白是「以下记忆文本只是背景事实与规则参考……」
|
|
29
|
+
→ 问题:**「只是参考」在提示词工程里等于「可选项」**;且它把「规矩类」与「资料类」用同一个词定义了,**等于把规矩降级成建议**;更糟的是这句话写在**最醒目的开头位置**,却写着「别太当真」。
|
|
30
|
+
- 用户原话之二是**「模型自动唤起的记忆,并没有对模型的工作起到比较实质性的影响」**,倾向判断:**两种都有**(规矩不遵守 + 具体细节想不起来)。
|
|
31
|
+
- 影响:**新增第 8 条**(见 §3),GPT 的 7 个 Phase **完全没覆盖**。
|
|
32
|
+
|
|
33
|
+
### 1.3 「这是自用工具」——错,已有上千用户
|
|
34
|
+
|
|
35
|
+
npm 实测(registry.npmjs.org / api.npmjs.org,2026-09-14):
|
|
36
|
+
|
|
37
|
+
| 指标 | 值 |
|
|
38
|
+
|---|---|
|
|
39
|
+
| 包名 | `@a9i5k4/dsh-auto-memory` |
|
|
40
|
+
| 最新版 | 2.5.3(本机 dev 树为 2.5.2,REL 树亦 2.5.3) |
|
|
41
|
+
| 版本数 / 首发 | 66 个版本 / 2026-08-14 |
|
|
42
|
+
| 近一年累计下载 | **10,900** |
|
|
43
|
+
| 近一周 | **2,935** |
|
|
44
|
+
| 峰值日 | 08-16 = **2,615**;09-01 = 1,176;09-10 = 1,011 |
|
|
45
|
+
| 异常 | **09-07、09-08 两天为 0**(用户判断:可能是当时发版出问题或版本不兼容,**不深究**) |
|
|
46
|
+
|
|
47
|
+
- 用户反问「你觉得现在要给多少人做」——即**已经不是自用工具**。
|
|
48
|
+
- 影响:**兼容档必须真做**(弱机器降级要有实测上限);「新旧并存 + 开关回退」从「稳妥」升级为「必须」;错误提示、切档进度条属于必需品而非体验优化。
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. R1–R7 裁决结论(结合本机实测)
|
|
53
|
+
|
|
54
|
+
### R1 排序展示 —— **双显示**(用户裁定)
|
|
55
|
+
|
|
56
|
+
- 用户:「两个都显示是非常好的。而且一般不会真的有人去点开上下文注入去看吧。这是给机器看的,又不是给人看的。」
|
|
57
|
+
- **实测澄清**:`memory_recall_pre` 返回的数字是**稠密余弦相似度**(`lib/index.js:4429`,过滤线 `minScore: 0.5`,按它降序);排序侧的 RRF 分与原始分**已在数据结构里**(`recall-fusion-pre.js:35` 明确「原始分数逐条保留供审计」)。
|
|
58
|
+
- 因此「双显示」接近**零成本**(数据已在手上,只是 UI 未显示)。
|
|
59
|
+
- **关键区分**:UI 上的分数是**给人调试**用的;模型看到的是**注入文本**。两条展示链路不应混。
|
|
60
|
+
- UI 上显示顺序:**相似度在前,融合排序分在后**。
|
|
61
|
+
- 与 S5.4 的关系:因为**没有拿融合分冒充相似度**(两个都摆出来),冲突不存在。
|
|
62
|
+
|
|
63
|
+
### R2 margin / 决策阈值 —— **维持现有架构**(工程判断)
|
|
64
|
+
|
|
65
|
+
- 代码里已经做对了:`recall-fusion-pre.js:10-12` 明确「**融合分数只用于排序;是否注入的决策必须使用绝对分数(稠密余弦)与校准阈值比较**」。
|
|
66
|
+
- 实测记录(`lib/index.js:7254`):「bge-m3 校准的 0.03 对 e5 的压缩分布过严(live 实测 margin 0-0.0284 全被拦)」→ **两档的 margin 尺度不同,不能互相套用**。
|
|
67
|
+
- 裁决:新策略**消费带版本的融合间隔必须重新校准**(采纳 GPT 的 R2 推荐);旧 `margin` 定义与阈值**冻结**,新特征先跑 shadow。
|
|
68
|
+
|
|
69
|
+
### R3 注入预算边界 —— **分开算**(用户裁定)
|
|
70
|
+
|
|
71
|
+
- 用户:「以目前的情况来说,分开算应该是非常好的。反正可以让用户在设置里随便改。」
|
|
72
|
+
- **实测修正**:本机 `injectBudgetChars` 实际为 **4800**,而 GPT 方案的成本模型假设是 **2000**——**方案建立在一个用户并未使用的数字上**。
|
|
73
|
+
- 裁决:**记忆条目一个额度,其他动态内容(白板 / 账本 / 日历 / 外部记忆)另一个总额度**。
|
|
74
|
+
- **但新增一条覆盖规则**:**规则类不参与任何预算裁剪**(见 §3)。
|
|
75
|
+
- 附带:设置页「大修、提升 UI 质感与可操作度」记为**独立排期项**,不塞进 Phase 0–6。
|
|
76
|
+
|
|
77
|
+
### R4 共用契约 —— 按 GPT 推荐(无异议)
|
|
78
|
+
|
|
79
|
+
两档共用流程、字段与判据,允许适配器、数量与预算不同;避免把「同路径」解释为「相同的模型调用」。
|
|
80
|
+
|
|
81
|
+
### R5 白板 lint —— **要做,且归属变更**(用户裁定)
|
|
82
|
+
|
|
83
|
+
- 用户:「这个白板后面我要 combine 进 dsh graph 这个开源项目……这个用户是肯定要看的,而且这个肯定是需要改的。甚至有些不明白的时候,AI 可以主动去向用户提问。」
|
|
84
|
+
- 裁决:lint **要做**,且**不只是报告**——有问题要能改;AI 不明白时可**主动向用户提问**。
|
|
85
|
+
- **归属**:并入 WB-GRAPH 重构,**不单独作为 Phase 4 的一部分**。
|
|
86
|
+
|
|
87
|
+
### R6 白板人机分区 —— **需要用户手写区**(用户裁定,推翻了「全 AI 维护」的初答)
|
|
88
|
+
|
|
89
|
+
- 用户先说「全是 AI 写的,由 AI 来维护」,随后**自我修正**:「确实需要有用户手写区来保护用户,或许有时候确实需要手动去改。然后 AI 应该也会有和用户共同编辑。」
|
|
90
|
+
- **实测现状(关键)**:`WB-FORMAT-CONVENTION.md` §5 **早已定义人机分区**(每张卡片分 `<!-- model -->` / 用户区,永不互相覆盖),但**实际 PLAN.md 里一个锚点、一个分区标记都没有**——规范已批准、**代码从未实现**。
|
|
91
|
+
- **这解释了今天早些时候那个事故**:没有锚点、没有分区 ⇒ **整篇覆盖是唯一可行的写入方式** ⇒ 系统自动快照一写,白板全貌就没了。
|
|
92
|
+
- 裁决:分区要真做,并入 WB-GRAPH 重构 P1/P2。
|
|
93
|
+
|
|
94
|
+
### R7 数值与计量 —— 按 GPT 推荐(无异议)
|
|
95
|
+
|
|
96
|
+
定义源记录已批准数值,行为测试独立验证;字符估计只标「估计」,真实 tokenizer 计量另报。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 3. 新增第 8 条(GPT 方案完全没有)
|
|
101
|
+
|
|
102
|
+
### 8.1 病症
|
|
103
|
+
|
|
104
|
+
用户原话(本次会话最有价值的信号):
|
|
105
|
+
|
|
106
|
+
> **「just for reference 说得太轻了,模型注意力没有在这上面。」**
|
|
107
|
+
> **「模型自动唤起的记忆,并没有对模型的工作起到比较实质性的影响。」**
|
|
108
|
+
|
|
109
|
+
### 8.2 两条独立病因
|
|
110
|
+
|
|
111
|
+
| | 病因 | 证据 |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| **A. 措辞** | 注入开场白把「规矩」与「资料」统一降格为「参考」;且写在最醒目位置却写着「别太当真」 | `lib/index.js:463` |
|
|
114
|
+
| **B. 节奏** | `snapshotMinGapRounds = 5` ⇒ **规矩在第 2–5 轮不在场**;模型不是不听话,是**没收到** | `lib/index.js:326`、`7455` |
|
|
115
|
+
|
|
116
|
+
### 8.3 裁决:规则/参考分层
|
|
117
|
+
|
|
118
|
+
| 层级 | 存放 | 注入节奏 | 预算 | 谁写 |
|
|
119
|
+
|---|---|---|---|---|
|
|
120
|
+
| **用户级规则** | 用户级记忆(现有 `~/.dsh/memory/MEMORY.md`) | **每个工作区、每轮都注入**(相当于用户画像) | **不参与预算裁剪** | AI 写入,用户可改 |
|
|
121
|
+
| **工作区级规则** | 工作区自己的规则文件 | **每轮都注入** | **不参与预算裁剪** | AI 写入,用户可改 |
|
|
122
|
+
| **参考类** | 现有 `MEMORY.md` / 日志 / 反思 | 三层漏斗(目录常驻 → 摘要 → 原文) | 受 R3 的额度约束 | 现有机制不变 |
|
|
123
|
+
|
|
124
|
+
- 用户裁定:**两层都要**(工作区级 + 用户级)。换工作区 = 换工作区级规则;用户级跨工作区恒定。
|
|
125
|
+
- 用户补充的**膨胀对策**:「让规则写得稍微克制一点,以每个工作区为锚点」。
|
|
126
|
+
- **成本可行性**:规则几乎不变 ⇒ 位于注入前缀 ⇒ **命中 DeepSeek 前缀缓存(约原价 1/10)** ⇒ **每轮注入的边际成本极小**。用户判断「注入这点东西用不了多少缓存预算」**成立**。
|
|
127
|
+
- **分类时机(用户裁定,重要)**:**不做成独立 LLM 调用**。`memory_log` 本来就在**本轮内由模型直接写**(「这个是直接写的,就在本轮当中确定什么是规则、什么是参考」),顺手打标 = **零额外成本**。将来 procedural memory 同理。
|
|
128
|
+
- **反驳与保留(我的建议,待用户确认)**:AI 分类的风险是**漏判**——真规则被判成「偏好」就会掉进参考类,**而这正是要修的病**。建议**前缀双保险**:出现 `【用户硬性规则】`/「严禁」/「必须」/「绝不」等字样时,**无论 AI 怎么判一律强制进规则类**。规则集小,多塞代价低;漏一条的代价是反复纠正。
|
|
129
|
+
|
|
130
|
+
### 8.4 附带的 UI 缺口(用户提出)
|
|
131
|
+
|
|
132
|
+
- 用户:「这个约束性的东西**必须得让用户看到**,并且让用户能进行精修。比如在记忆窗格的可视化里面,能够让用户看到目前模型有什么约束。」
|
|
133
|
+
- **实测**:记忆窗格现有 **12 个标签页**(概览 / 日志 / 精炼 / 记忆中枢 / 存储 / 笔记 / 白板 / 反思 / 连接 / 日历 / 工作区……),**没有任何一页显示「当前模型受什么约束」**。
|
|
134
|
+
- 裁决:新增「约束」视图,归入**UI 提质排期**。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 4. 引擎隔离与切换(用户裁定)
|
|
139
|
+
|
|
140
|
+
- 用户:「本来它们就是分隔的,只要在切换的时候强制前排(全量重建)就可以了。直接给用户显示一个进度条。」
|
|
141
|
+
- **实测两档用的是两个不同模型**:C2 = `Xenova/multilingual-e5-small` q8(端侧);C3 = `Xenova/bge-m3` int8(Python sidecar,555MB)。**向量空间不通用**。
|
|
142
|
+
- 代码已有意识(`PROVIDER_ID_INT8 = 'bge-m3-onnx-int8-pre-v1'`),**但没做成硬约束**。
|
|
143
|
+
- **新增能失败断言 T2-9(引擎隔离)**:用 e5 建的索引,切到 bge-m3 后**必须整体失效并重建**;若出现两套向量参与同一次排序,**判失败**。成本为零(仅加身份校验)。
|
|
144
|
+
- **用户强调的耦合约束**:切档进度条**必须并进现有「引导 / 向导」体系**(Python BGE 下载、venv 建环境已有可视化指导),**不得另起一套**。
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 5. 精排(H2)——方案被实测数据推翻,需重写
|
|
149
|
+
|
|
150
|
+
### 5.1 方案假设 vs 实测
|
|
151
|
+
|
|
152
|
+
| | GPT 方案假设 | 本机实测(`artifacts/m7-rerank-pre/results.json`) |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| 额外等待 | **≤750 ms** | **bge-reranker-v2-m3:P95 37.4 秒;qwen3-reranker-0.6b:P95 8.8 秒** |
|
|
155
|
+
| 模型资产 | U5「缺,阻塞」 | **已下载**(bge-reranker-v2-m3 2.1GB / qwen3-reranker-0.6b 1.1GB,各带 tokenizer) |
|
|
156
|
+
| 精度收益 | 未量化 | **recall@1:0.739 → 0.898(bge)/ 0.800(qwen)** |
|
|
157
|
+
|
|
158
|
+
### 5.2 硬件实测(我最初查错了,此处更正)
|
|
159
|
+
|
|
160
|
+
- **有可用显卡**:`NVIDIA GeForce RTX 4070 Ti SUPER`,**16GB 显存**,驱动 616.64。
|
|
161
|
+
- (我最初只列了前 3 个显示适配器就误判「无 GPU」,**此处更正并留痕**。另有 AMD Radeon 集显 + 3 个虚拟适配器。)
|
|
162
|
+
- 当前 Python 环境:`torch 2.13.0+cpu`(**CPU 版**)、`transformers 5.15.1`、`onnxruntime 1.23.2`、16 核 CPU、31GB 内存。
|
|
163
|
+
|
|
164
|
+
### 5.3 用户裁定:改为「多级选项」
|
|
165
|
+
|
|
166
|
+
用户原话:「这恐怕是一个**可以多级选项**的问题。发烧友、追求高质量的人或者研究人员,可以打开显卡加速进行精排……普通用户的话,用粗排就可以……(或者只在主动翻记忆的时候跑。亦或者拉长自动注入的时间。反正目前自动注入的时间窗口间隔是 1 分钟,也是可以改的。)」
|
|
167
|
+
|
|
168
|
+
| 档 | 谁用 | 跑在哪 | 触发时机 |
|
|
169
|
+
|---|---|---|---|
|
|
170
|
+
| **关** | 默认 | — | 不精排,纯粗筛 |
|
|
171
|
+
| **快档** | 普通用户 | int8 量化 / ONNX / CPU | 主动翻记忆时 |
|
|
172
|
+
| **发烧档** | 作者 / 研究者 | 完整模型 + RTX 4070 Ti SUPER | 主动翻记忆 + 可选自动注入 |
|
|
173
|
+
|
|
174
|
+
**并且新增方案里没有的机制**:精排改为 **1 分钟异步窗口** —— 本轮先用现有排序,精排在后台跑完,**结果留给下一次注入**。「准确率」与「不卡等待」不必二选一。
|
|
175
|
+
|
|
176
|
+
### 5.4 两个开发中遇到的实测坑(保留)
|
|
177
|
+
|
|
178
|
+
- **精排模型量化后更小更快**:用户提到 `multilingual` 100 多 MB、`bge` 500 多 MB(= `models-xenova-bge-m3-int8`,实测 555MB),比完整版快很多。
|
|
179
|
+
- **已有 int8 vs fp32 对照脚本**:`python/bench/l2_bench_int8_vs_fp32.py`(尚未读到结果,待补)。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 6. 用户级判断:成本模型的正确算法
|
|
184
|
+
|
|
185
|
+
用户反问:「你觉得这个成本模型的成本要怎么算?反正付钱的又不是我给这几千人一起付,是他们自己付自己的钱。」
|
|
186
|
+
|
|
187
|
+
**结论:我原先的推理有隐含错误**——把「上千用户 × 每轮注入」当成**一张账单**来吓自己。真实情况:
|
|
188
|
+
|
|
189
|
+
| 项目 | 谁付钱 | 对作者的成本 |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| 记忆注入 token | 用户自己的 API key | **0** |
|
|
192
|
+
| 精排算力 | 用户自己的机器(本地推理) | **0** |
|
|
193
|
+
| 嵌入建索引 | 用户自己的机器(一次性 + 增量) | **0** |
|
|
194
|
+
|
|
195
|
+
- 「上千用户」对**成本模型的影响为 0**;真正的影响是**代码质量**(bug 被放大)与**兼容档必须真做**。
|
|
196
|
+
- 推论:**该省的地方是「别浪费」(不重复注入同样内容),不该省的地方是准确性**;而前缀缓存让「规则每轮注入」的边际成本极小。
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 7. 白板 / graph 的战略裁决(用户裁定:(乙))
|
|
201
|
+
|
|
202
|
+
用户裁定:**「(乙) 两者合并:把 GPT 的『写入门 + 引擎隔离 + 状态过滤』这套通用原则,注入到你 WB-GRAPH 方案的 P1/P2 里。」**
|
|
203
|
+
并补充:**「我认为这个 dsh graph 正是我想要的 Karpathy 模块。」**
|
|
204
|
+
|
|
205
|
+
查证结果(仓内既有资产):
|
|
206
|
+
|
|
207
|
+
| 文档 | 规模 | 状态 |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| `WB-GRAPH-INTEGRATION-PLAN.md` | 386 行 | 2026-09-13 完成,含判据 Schema、sidecar 设计、两工具方案 |
|
|
210
|
+
| `WB-GRAPH-DECISIONS-20260914.md` | 54 行 | 一页纸拍板点(A1–A8 / B1–B7),**大部分仍标 🎯 未拍板** |
|
|
211
|
+
|
|
212
|
+
**GPT 的 Phase 4 与既有 WB-GRAPH 方案的差异**:
|
|
213
|
+
|
|
214
|
+
| | GPT Phase 4 | WB-GRAPH 方案 |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| 目标 | 白板进语料 + 写入门 + lint | 白板 → **看板化**(combine dsh-graph) |
|
|
217
|
+
| 判据 | 卡片集合比对 | 完整判据表(H1–Hn) |
|
|
218
|
+
| 存储 | 直接改 Markdown | **Markdown = 人读真相源,sidecar JSON = 机读派生件** |
|
|
219
|
+
| 检索 | 进 L0 语料 | **新增两工具**(expand / trace),工具数 14 → 16 |
|
|
220
|
+
| 人机 | 用户区保护(一整块) | **每卡片分区**(模型区 / 用户备注区) |
|
|
221
|
+
|
|
222
|
+
**合并后的分工(我的建议,待确认)**:
|
|
223
|
+
- GPT Phase 4 中**真正新增**的只有 **lint** 一项 → **挂到 WB-GRAPH 的 P1 上**;
|
|
224
|
+
- 「写入门的前后比对」与 WB-GRAPH 的 **B1 同源** → 合并实现;
|
|
225
|
+
- 「用户区保护」与 **B4 选项 (c)** 同源 → 按 WB-GRAPH 的每卡片分区做;
|
|
226
|
+
- GPT 的 **引擎隔离(T2-9)与状态过滤(C8)** 属 Phase 0/2,**不随白板走**。
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 8. 评测题集(U3)
|
|
231
|
+
|
|
232
|
+
- 用户:题集是**很早期初创算法时做的**,后面需要**结合已有记忆(现在记忆量已丰富)继续优化算法,或再整一套丰富的测试题和标准答案**。
|
|
233
|
+
- 待查:`python/bench/results/m7-2-results.json`(799KB)与 `m7-2-l2-results.json`(468KB)的内容形态。
|
|
234
|
+
- **建议方向(待确认)**:新题集应当**从真实记忆语料出发**(用户已有大量真实记忆),而不是凭空造题——这样才测得出「真实使用中的召回质量」。
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 9. 开工节奏与风险(用户裁定)
|
|
239
|
+
|
|
240
|
+
- 用户裁定:**要新旧并存 + 开关切换,随时能退回**。
|
|
241
|
+
- 用户裁定第一步:**先出《合并总纲》**。
|
|
242
|
+
- 用户追问:现在可以开工了吗?还是先找 GPT 再咨询 / 总纲出来再让 GPT 审一遍?
|
|
243
|
+
|
|
244
|
+
**我的建议(写作本文时给出)**:
|
|
245
|
+
|
|
246
|
+
> **总纲 → 我方先做「矛盾扫描」→ GPT 审 → 开工**
|
|
247
|
+
|
|
248
|
+
理由:GPT 审之前,两份方案(它的 7 Phase 与 WB-GRAPH)之间的**表面对齐**应由我方先做;否则它会花大量篇幅指出「你这跟我的 Phase 4 不一样」——那是**已知信息**。让它审「调和是否正确」,比让它审「自己的方案」有效得多:**它能发现自己没考虑的整合问题,发现不了自己没想到的东西**——后者今天这场对话已经做了。
|
|
249
|
+
|
|
250
|
+
**开工前必须先补的三件**:
|
|
251
|
+
1. 《合并总纲》(含 GPT 7 Phase + 第 8 条 + WB-GRAPH 的合并与排序);
|
|
252
|
+
2. 矛盾扫描结果(两份方案冲突清单 + 调和方案);
|
|
253
|
+
3. **不做**:在总纲出来前动 `lib/index.js` 的注入链。
|
|
254
|
+
|
|
255
|
+
**唯一的例外(可以立刻做、且建议做)**:
|
|
256
|
+
- **C8 缺陷修复**——`superseded` / `retracted` 条目**现在真的会进入注入文本**(本会话已用 `tools/_redproof/red-proof-phase0-t01.mjs` 跑出 4/4 报红)。这是一个**正在发生的真实危害**,且修复范围极小、可独立回滚。
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 10. 本次会话产出的文件
|
|
261
|
+
|
|
262
|
+
| 文件 | 内容 |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `docs/internal/reviews/REVIEW-gpt6astra-20260914.md` | 第一轮评审原文(补录,此前并未真正落盘) |
|
|
265
|
+
| `docs/internal/reviews/CLAIM-VERIFICATION-20260914.md` | 12 条主张逐条核实(12/12 成立) |
|
|
266
|
+
| `docs/internal/ARCH-REVIEW-ROUND2.md` | 第二轮投喂操作件 |
|
|
267
|
+
| `docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md` | GPT 第二轮交付:RAG+Karpathy 实施方案 v1(37071 字) |
|
|
268
|
+
| `tools/_redproof/red-proof-phase0-t01.mjs` | T0-1 断言的「能红」证明(4/4 报红) |
|
|
269
|
+
| **本文** | 规划层裁决记录 |
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# P1 设计稿 · 统一状态提交与快照(miv 单源)+ 并发原子边界
|
|
2
|
+
|
|
3
|
+
> 状态:**待审**(先审后写代码 —— 用户裁定顺序:设计稿是必审点)
|
|
4
|
+
> 依据:`MASTER-PLAN-3.0.md` Phase 1(§216-227)、`TODO-GRAPH.html` 卡 `V2-P1`、`ROUND3-REVIEW-INTEGRATION-20260914.md` §3.2
|
|
5
|
+
> 作者:执行方 · 日期:2026-09-15 · 全部 file:line 为**本机实测**取值,非转述
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. 一句话目标
|
|
10
|
+
|
|
11
|
+
把「谁在写、写的什么版本、写完算不算数」收敛成**一个提交边界**:所有窗口经同一工作区 owner 提交,
|
|
12
|
+
提交时在边界**内**校验 `expectedDigest` 与 `miv`;快照的 `miv` 成为**唯一事实源**(内容身份 + 状态清单摘要,
|
|
13
|
+
哈希身份、不递增、不比较)。并发不加锁,靠「队列串行 + 边界内比较」保证恰好一项成功、另一项收到**可见冲突**。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. 现码取证(每条都可在本机复现)
|
|
18
|
+
|
|
19
|
+
### 1.1 已有的好东西(**不要重造**)
|
|
20
|
+
|
|
21
|
+
| 机制 | 位置 | 实测语义 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| 短提交队列 | `lib/memory-writer-pre.js:235 _queue` | 按 `path.resolve(filePath)` 分键串行;空闲即回收 Map 条目(`:242`),无界增长已处理 |
|
|
24
|
+
| 原子写 | `lib/memory-writer-pre.js:199 atomicReplace` | 同目录 tmp + fsync + rename。**签名里没有 `expectedDigest`** —— 它**不是** CAS,只做原子写 |
|
|
25
|
+
| 边界内 digest 校验 | `lib/memory-writer-pre.js:344 replace` / `:332` / `:357` / `:369` / `:380` | 五个提交方法**都在 `_queue` 内部**先比 `expectedDigest`,不匹配返回 `{ok:false, reason:…}`;**这是当前唯一正确的并发保护** |
|
|
26
|
+
| 无 BOM 闸 | `lib/memory-writer-pre.js:287` | 提交前拒 BOM(用户硬性规则的代码级保障) |
|
|
27
|
+
| 写入门 | `lib/memory-mutation-pre.js:76 validateMutationBoundaryPre` | 只收规范化投影(`beforeIds`/`afterIds`/`archivedIds`);`M1 丢卡保护`无条件生效(`:99-101`) |
|
|
28
|
+
| 门调用点 | `lib/index.js:1984` | **全仓唯一实调**(`memory-mutation-pre.js:231` 是自身内部调用) |
|
|
29
|
+
|
|
30
|
+
### 1.2 缺口(P1 要修的)
|
|
31
|
+
|
|
32
|
+
**(a) `miv` 有生成、无单源,且**没有被提交边界校验****
|
|
33
|
+
|
|
34
|
+
- 生成:`lib/shadow-retrieval-pre.js:144` —— `idx_pre_ + first32hex(sha256(canonical corpus tuples))`;经 `lib/m4-corpus-pre.js:128` → `context-host-pre.js:216/393` 装配进快照。
|
|
35
|
+
- 消费:`lib/activation-host-pre.js:102 setMiv` / `:252 currentMiv` 只做**缓存**(`mivCache = {wsRef, miv}`),`setMiv` 是**显式写入口**(`:111`)——即"谁调谁说了算",**没有单一权威计算点**。
|
|
36
|
+
- **关键缺口**:`memory-writer-pre.js` 的提交方法**只比 `expectedDigest`,完全不看 `miv`**。⇒ 图内容变了但 digest 恰好相等(或调用方没传 digest)时,快照仍可能带着**旧 miv** 发出去。
|
|
37
|
+
|
|
38
|
+
**(b) ~~复用命中只查 `contextVersion`,不查 `miv` ⇒ 契约 I6 失守~~ —— ★施工期核实:本条已过时,撤销**
|
|
39
|
+
|
|
40
|
+
> **勘误(实测取证,非推断)**:以下三行是**当时**(09-14 评审 C9 口径)的判断;动手前逐行复核,现状已是:
|
|
41
|
+
> - 投影**已携带** miv —— `lib/activation-host-pre.js:165`:`miv: String(req.memoryIndexVersion || '') || undefined`(同处 `:164/166/167` 带 `contextVersion`/`observationId`/`requestKey`);
|
|
42
|
+
> - 版本门**已含 miv 两道 fail-closed** —— `lib/tier-layer-inject-pre.js:200-204`:`if (!curMiv || !projMiv) return no('version-unknown')` / `if (curMiv !== projMiv) return no('miv-changed')`;
|
|
43
|
+
> - 调用方**已传当前 miv** —— `lib/index.js:4269`:`miv: this.tierCurrentMivPre()`;
|
|
44
|
+
> - **且有专项测试** —— `tests/smoke/smoke-test-t0-2-version-gate-pre.mjs:75`(`miv-changed`)、`:96/:103`(`version-unknown` 两侧)。
|
|
45
|
+
>
|
|
46
|
+
> **结论**:I6 在 P1 开工前**已由 P0 的 T0-2 守住**。§3 表中「I6 失守 → 守住」一栏作废;原 §2.4(版本门补 miv)**从改动清单撤销** —— 重复实现只会引入回归风险。
|
|
47
|
+
> **教训**:外部评审结论带时间戳,动手前必须用**当前**代码复核,不得把评审描述当现状直接排进施工项。
|
|
48
|
+
|
|
49
|
+
*(以下为当时的分析原文,仅作留痕,勿据此施工)*
|
|
50
|
+
|
|
51
|
+
- 契约原文 `docs/internal/THREE-LAYER-CONTRACT.md:184`:**I6** 三层来自同一份快照(同一 `miv`),混版视为错误。
|
|
52
|
+
- 实测 `lib/tier-layer-inject-pre.js:169 selectReusableTierHitsPre`:① 时间门(`:186`)② 身份门 session/workspace(`:188-194`)③ **版本门只有 `contextVersion`**(`:195-198`)。**`gh.miv` 虽被读入快照字段(`:177`)却不参与判定** ⇒ 语料换了、miv 变了,只要 contextVersion 没变就**照样复用旧命中**,三层可能混版。
|
|
53
|
+
- 这与我方 09-14 外部评审 **C9** 结论一致(`lib/index.js:3894/3896/3901` 复用命中只查时间与会话)。
|
|
54
|
+
|
|
55
|
+
**(c) 提交接口没有来源身份**
|
|
56
|
+
|
|
57
|
+
- 现签名(`memory-writer-pre.js:344`)只收 `expectedDigest` + `replacement` + `idFactory`。
|
|
58
|
+
- 缺 `workspaceKey / boardId / txId / actor` ⇒ 冲突被拒时**说不出"谁和谁撞了"**,`mutationRefusalTextPre`(`:212`)也拿不到 actor 信息。
|
|
59
|
+
|
|
60
|
+
**(d) 共享 vs 隔离虽已分开,但缺显式声明**
|
|
61
|
+
|
|
62
|
+
- 图(共享):按工作区分键(`_queue` 的 key 是文件路径;工作区文件路径天然隔离)✓
|
|
63
|
+
- 激活/冷却/已交付标记(隔离):`lib/activation-host-pre.js` 的 `registry.forRuntime(sessionId, workspaceKey, …)`(`:132/248`)已按会话隔离 ✓
|
|
64
|
+
- **缺**:这个"哪些共享、哪些隔离"的边界目前只存在于代码直觉里,没有写成常量表 ⇒ 后续 P6B 容易越界。
|
|
65
|
+
|
|
66
|
+
**(e) 三种状态目前无类型隔离**
|
|
67
|
+
|
|
68
|
+
- 记忆有效状态:`current / superseded / retracted`(契约 I5,`index.js` 注入侧过滤)
|
|
69
|
+
- 任务进度:`done / passed / archived`(看板语义)
|
|
70
|
+
- 归档位置:`archived`(同时也是任务态 —— **同名不同义**)
|
|
71
|
+
- 风险:`archived` 一词两义,跨线传递时会误判。P1 只做**命名隔离**(见 §2.3),不做语义合并。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 2. 改动清单(按依赖序,每步可独立回归)
|
|
76
|
+
|
|
77
|
+
### 2.1 【新增】`lib/state-commit-pre.js` —— 统一提交契约(纯函数,零依赖)
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
export const STATE_COMMIT_VERSION_PRE = 'state_commit_pre_v1'
|
|
81
|
+
|
|
82
|
+
// 提交单据(P1 唯一的写入契约)
|
|
83
|
+
// { workspaceKey, boardId, txId, expectedDigest, expectedStateVersion,
|
|
84
|
+
// actor: { sessionId, contSeq, kind }, // kind: 'user' | 'plugin' | 'model' | 'system'
|
|
85
|
+
// writes: [{ path, content, expectedDigest }],
|
|
86
|
+
// stateChanges: [{ target, from, to }] }
|
|
87
|
+
|
|
88
|
+
export function buildStateCommitPre(input) // 归一化 + 必填校验,缺字段 fail-closed
|
|
89
|
+
export function commitConflictPre(commit, observed) // → { ok:false, reason, detail:{ expected, observed, actor, boardId } }
|
|
90
|
+
export function commitReceiptPre(commit, result) // → { txId, boardId, miv, digest, at, actor }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**`boardId` 硬约束(卡内明确要求)**:在工作区内**稳定**、**不含当前会话号**
|
|
94
|
+
⇒ 由 `sha256(workspaceKey + '|' + scope)` 派生,**不得**掺 `sessionId`。理由:接续后新窗口换 sessionId,
|
|
95
|
+
若 boardId 跟着变,图就会被当成两块,共享语义直接崩。
|
|
96
|
+
|
|
97
|
+
### 2.2 【新增】`miv` 单源:`memoryIndexVersionPre(projection)`
|
|
98
|
+
|
|
99
|
+
> **★2026-09-15 施工期核实:§6 第 2 步「收敛 `activation-host-pre.js:111 setMiv`」已撤销。**
|
|
100
|
+
> 实测 `setMiv`(`lib/activation-host-pre.js:111` + 孪生 `lib/activation-host.js:111`)**全仓零调用者**(`lib/`、`tests/`、`lib/client.js` 全量检索仅命中其自身定义行)⇒ 它是**死代码**,
|
|
101
|
+
> **没有"活跃写者"需要收敛**。真实 miv 更新路径是 `:100-104` 的 `corpusRegistry.get(catalog)` 分支(`mivCache = { wsRef: ws, miv: res.snapshot.memoryIndexVersion }`),
|
|
102
|
+
> 而该 `memoryIndexVersion` 来自契约 §8 的 `shadow-retrieval-pre.js:145 memoryIndexVersion(sources)` —— **它才是建索引侧真源**。
|
|
103
|
+
> 结论:P1 **不删死代码、不动 `mivCache`**(删死代码属独立清理项,不混进 P1;动了反而扩大回归面)。
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// lib/state-commit-pre.js
|
|
107
|
+
export function memoryIndexVersionPre(projection) {
|
|
108
|
+
// 输入:{ records:[{id, status, l0?}], boardCards?:[{id, status}], scope }
|
|
109
|
+
// 输出:'idx_pre_' + first32hex(sha256(canonical))
|
|
110
|
+
// canonical = 按 id 升序的 [id, status, contentDigest] 三元组(换行拼接,无尾随空白)
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**三条硬规则**(卡内 T1-5 扩展口径):
|
|
115
|
+
|
|
116
|
+
1. **内容身份 + 状态清单摘要**:`status` 进摘要 ⇒ 仅状态变化(`current→superseded`)**miv 必变**。
|
|
117
|
+
2. **不递增、不比较**:miv 是哈希身份,**不是版本序**。任何代码不得写 `if (miv > lastMiv)`。
|
|
118
|
+
3. **白板卡片状态也进摘要**:`boardCards` 参与 canonical ⇒ 扩到白板。
|
|
119
|
+
|
|
120
|
+
**必须废弃的口径**:WB-GRAPH 的 `index.json` 里 `rebuilt_at` **不得**当版本序。它是时间戳,重建时间变但内容没变时它会变 ⇒ 用它当版本序会造成**假失效 + 真混版**。P1 交付物里包含一条断言专门钉死它。
|
|
121
|
+
|
|
122
|
+
### 2.3 【改动】`memory-writer-pre.js`:提交方法收 `expectedStateVersion` + actor
|
|
123
|
+
|
|
124
|
+
- `replace` / `append` / `migrate` 等五个方法(`:332/344/357/369/380`)在 `_queue` **内部**、`expectedDigest` 校验**之后**,追加:
|
|
125
|
+
```
|
|
126
|
+
if (opts.expectedStateVersion != null && state.stateVersion !== opts.expectedStateVersion)
|
|
127
|
+
return commitConflictPre(...) // 可见冲突,不写
|
|
128
|
+
```
|
|
129
|
+
- **不改 `atomicReplace` 签名**(它不是 CAS,别给它加语义)。
|
|
130
|
+
- **不引入长期编辑锁、不按会话分片**(卡内明确否决)。队列 key 仍是文件路径。
|
|
131
|
+
|
|
132
|
+
### 2.4 【撤销】~~`tier-layer-inject-pre.js:195` 版本门补 `miv`~~
|
|
133
|
+
|
|
134
|
+
**★2026-09-15 施工期核实后撤销**:该检查**已存在**(`:200-204`,含 `version-unknown` 与 `miv-changed` 两条 fail-closed),
|
|
135
|
+
投影与调用方亦已就位(`activation-host-pre.js:165` / `index.js:4269`),并有专项测试
|
|
136
|
+
`tests/smoke/smoke-test-t0-2-version-gate-pre.mjs`。**I6 已守住,此处不改任何代码。**
|
|
137
|
+
|
|
138
|
+
*(以下原始改动设想作废,仅留痕)*
|
|
139
|
+
- ~~现:只比 `contextVersion`。改:`contextVersion` 且 `miv` 两侧可得且相等;任一侧缺 `miv` ⇒ 不复用。~~
|
|
140
|
+
- ~~新增拒绝原因 `miv-mismatch` / `miv-unknown`~~ ⇒ 既有实现用的是 **`version-unknown`** 与 **`miv-changed`**(`tier-layer-inject-pre.js:200-204`),命名不同因而已有测试可依。
|
|
141
|
+
|
|
142
|
+
### 2.5 【新增】状态命名隔离常量
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
export const MEMORY_STATUS_PRE = Object.freeze(['current', 'superseded', 'retracted'])
|
|
146
|
+
export const TASK_STATE_PRE = Object.freeze(['open', 'done', 'passed'])
|
|
147
|
+
export const ARCHIVE_STATE_PRE = Object.freeze(['active', 'archived'])
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
三者**不得互转**(卡内"三种状态不能混")。`archived` 一词两义的问题以 `ARCHIVE_STATE_PRE` 独立命名解决,
|
|
151
|
+
**映射由白板线适配器负责**(KICKOFF §3.4 边界约定:3.0 不自行解释图格式)。
|
|
152
|
+
|
|
153
|
+
### 2.6 【改动】冲突可见性
|
|
154
|
+
|
|
155
|
+
- `mutationRefusalTextPre`(`memory-mutation-pre.js:212`)扩字段:拒绝文本必须带**当前版本 + 冲突目标**(卡内 T1-7C 要求),形如
|
|
156
|
+
`[提交被拒] tx=<txId> 目标=<path> 期望 miv=<a> 实测 miv=<b> 冲突方=<actor.sessionId>@<contSeq>`
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 3. 契约影响
|
|
161
|
+
|
|
162
|
+
| 契约 | 现状 | P1 后 | 是否接口改动 |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| **I5** 非 current 两处过滤 | P0 已达标(注入侧 import 检索侧 `isCurrentPre`) | 不变 | 否 |
|
|
165
|
+
| **I6** 三层同快照(同一 miv) | **已守住**(P0 的 T0-2 已修,见 §1.2b 勘误) | **不变**(P1 不动此路径) | 否 |
|
|
166
|
+
| **I7** 降级必须显式标注 | 达标 | 新增 `miv-unknown` 降级行 | 否(复用既有降级通道 `index.js:4246`) |
|
|
167
|
+
| T1-5 仅状态变化 miv 必变 | 未覆盖白板 | 覆盖(§2.2 规则 3) | 否 |
|
|
168
|
+
| `validateMutationBoundaryPre` | 只收投影 | **不变**(3.0 只接收规范化投影,不解释图格式) | 否 |
|
|
169
|
+
|
|
170
|
+
**兼容档**:~~`memoryMutationMode='readonly'`(既有开关)~~ **★2026-09-15 施工期核实:该键在代码中不存在**
|
|
171
|
+
(`lib/`、`tests/`、`lib/client.js`、本机 `~/.dsh/dsh-auto-memory-pre.json` 全量检索均无命中;仅出现在 4 份文档里:
|
|
172
|
+
`PLAN-gpt6astra-round2-20260914.md:458/696`、`MASTER-PLAN-3.0.md:227`、本稿)。
|
|
173
|
+
⇒ **P1 的实际回滚面 = 新增字段全部可选**:不传 `expectedStateVersion`(也不传 `expectedDigest`)时,
|
|
174
|
+
`_checkCommitBoundary` 两个闸都不触发,行为与 P1 前**逐字节一致** —— 这是已实测的(见 T1-8)。
|
|
175
|
+
`readonly` 若要落地,属**独立新功能**(需实现 + 配置项 + GUI),**不在 P1 范围**,另行排期。
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 4. 验收断言(**能失败**,逐条对应卡片 crit)
|
|
180
|
+
|
|
181
|
+
| ID | 断言 | 对应 crit |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| **T1-1** | 两窗口读同一 digest 后**同时**提交替换 → 恰好一项 `ok:true`,另一项 `ok:false` 且 `reason` 可见 | 卡内 T1-4 |
|
|
184
|
+
| **T1-2** | 成功版本内容**未被覆盖**(终态 == 成功者写入内容) | 卡内 T1-4 |
|
|
185
|
+
| **T1-3** | A、B 会话共享图节点;**A 的激活包与交付记录不出现在 B** | 卡内 T1-7B |
|
|
186
|
+
| **T1-4** | 旧接续窗口迟到写入**不能覆盖新图**;拒绝信息带**当前版本 + 冲突目标** | 卡内 T1-7C |
|
|
187
|
+
| **T1-5a** | 业务内容改变 ⇒ miv 变 | 卡内 T1-5 |
|
|
188
|
+
| **T1-5b** | **仅**状态变化(`current→superseded`)⇒ miv **必变** | 卡内 T1-5 |
|
|
189
|
+
| **T1-5c** | 仅切换会话、仅更新 `rebuilt_at` ⇒ miv **不变** | 卡内 T1-5 |
|
|
190
|
+
| **T1-5d** | 白板卡片状态变化 ⇒ miv **必变** | Phase 1 扩展 |
|
|
191
|
+
| **T1-6** | `boardId` 不含 sessionId:同一工作区两个不同 sessionId ⇒ `boardId` 相等 | 卡内接口要求 |
|
|
192
|
+
| **T1-7** | 复用命中 `miv` 不等 ⇒ **不复用**(三层不混版);任一侧缺 miv ⇒ 不复用 | 卡内 T1-5 / I6 |
|
|
193
|
+
| **T1-8** | `expectedStateVersion` 缺省 ⇒ 行为与 P1 前**逐字节一致**(回归守卫) | 兼容档 |
|
|
194
|
+
| **T1-9** | ~~`memoryMutationMode='readonly'` ⇒ 所有提交被拒~~ **★核实:该键不存在,本条改写为兼容档真值表**:① 不传 `expectedStateVersion` ⇒ 提交照常成功(零行为变化)② 传入且匹配 ⇒ 成功 ③ 传入且不符 ⇒ 拒绝且可见 | 兼容档 |
|
|
195
|
+
|
|
196
|
+
**注入式沙箱纪律**(本项目已踩 3 次):新方法被抽进 `new Function` 沙箱时,
|
|
197
|
+
其引用的**所有**模块级符号必须显式列入 helpers,否则 `ReferenceError` 会被外层 `catch` 吞成"静默空内容"。
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 5. 明确**不做**的事(防止范围蔓延)
|
|
202
|
+
|
|
203
|
+
- ✗ 不加长期编辑锁、不按会话分片(卡内否决)
|
|
204
|
+
- ✗ 不改 `atomicReplace` 语义(它不是 CAS)
|
|
205
|
+
- ✗ 不引入 `miv` 序比较(哈希身份,不递增)
|
|
206
|
+
- ✗ 不在 3.0 内解析白板格式(KICKOFF §3.4:白板线拥有 `parseWhiteboardPre`)
|
|
207
|
+
- ✗ 多宿主同目录 —— **留给 U2 单独认证**;若要多进程直写同一文件,必须另加跨进程原子提交或单写者路由
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 6. 施工顺序(每步跑全量回归)
|
|
212
|
+
|
|
213
|
+
1. `state-commit-pre.js` + 单测(纯函数,无接入风险)
|
|
214
|
+
2. ~~`miv` 单源接线(`setMiv` 收敛到单一计算点)+ T1-5a/b/c/d~~ **已撤销**(见 §2.2 勘误:`setMiv` 为死代码,无对象可收敛)+ T1-5a/b/c/d ✅ **已完成**(`smoke-test-state-commit-pre.mjs`)
|
|
215
|
+
3. 队列内 `expectedStateVersion` 校验 + T1-1/T1-2/T1-4/T1-8/T1-9
|
|
216
|
+
4. ~~`tier-layer-inject-pre.js` 版本门补 miv + T1-7(修 I6)~~ **已撤销**(I6 由 P0 的 T0-2 守住,见 §2.4)
|
|
217
|
+
5. 状态命名隔离 + T1-3
|
|
218
|
+
6. 冲突可见性(拒绝文本)+ T1-4
|
|
219
|
+
7. 全量回归 + 更新 `TODO-GRAPH.html` V2-P1 卡
|