# 最终报告 · dsh-GOkifu-compact > 2026-10-28。目标来自主人当轮指令 + 与 Qwen3.8-Max 讨论后定稿的「棋谱」设想(见 `HANDOFF.md` 的用户原话节)。 > > **一句话**:一个已装进本机 DSH、每步注入失败清单、并在真实模型对照实验里把失败记忆保留率做到 8/8(官方 4.67/8)的压缩后端。 --- ## 一 · 交付物 | 类别 | 路径 | 说明 | |---|---|---| | 插件包 | `src/engine.mjs` | 继承官方 `BasicCompactionEngine`,只覆盖策略层 | | 核心库 | `src/graph/keeper/verifier/extract/range/ledger/summary-prompt/worldstate.mjs` | 纯函数优先,全部可脱离 DSH 单测 | | 测试 | `test/*.test.mjs` + `run.mjs` | **81 项全绿** | | 真实验证 | `test/real-modules.mjs`(4 项)`test/real-fold.mjs`(5 项)`test/real-model.mjs` `test/real-e2e.mjs` | 真实 `@deepseek-ai` 包 / 真实事务 / 真实模型 | | 性能 | `test/bench.mjs` | 2000 边压力基准 | | 安装/回滚 | `test/install.mjs` `test/rollback.mjs` `test/verify-install.mjs` | 已执行;13 项挂载校验全过 | | 证据 | `evidence/模拟结果.md` `真实模型测试.md` `真实端到端测试.md` `bench-2000.json` | 全部可复跑 | | 文档 | `README.md` `docs/方案书.md` `市场对标.md`(452 行)`DSH-技术测绘.md`(1303 行)`运行期修复报告.md` `重启后验证清单.md` | — | --- ## 二 · 四层验证(全部可复跑) ``` node test/run.mjs → 81 passed, 0 failed # 纯函数 + 假 DSH 接线 node test/verify-install.mjs → 13/13 通过 # 挂载校验(含运行期证据) node test/real-modules.mjs → 4/4 通过 # 真实 @deepseek-ai 包(从 asar 提取 29 个包) node test/real-fold.mjs → 5/5 通过 # 真实 compactRegion 事务 + 棋谱摘要 node test/real-model.mjs → 见下(真实模型对照) node test/real-e2e.mjs → 见下(真实 bash 失败 → 压缩 → 续跑) node test/bench.mjs 2000 → 见下(性能) ``` ### 2.1 真实模型对照实验(`evidence/真实模型测试.md`) 同一段会话(8 条失败边、2 条硬约束),同一个模型(`group/deepseek-v4.1-flash1017`),采样 3 次: | 方案 | 失败边保留(avg/min/max) | 约束 | 输出字符 | |---|---|---|---| | **本插件(棋谱 prompt + HARD FACTS)** | **8 / 8 / 8** | 2/2(3 次全中) | 1406–1592 | | 官方 8 段模板 | 4.67 / 3 / 6 | 2/2、0/2、0/2 | 1902–2928 | - 续跑保真:拿最好的棋谱摘要当历史,问「目标 / 失败过哪些做法 / 下一步」→ **提到 8/8 条失败做法** - 置换提示:给「⚠ 你回到了节点 n9」后,回答里重复已探明死路 **0 条** - 热上下文真实分词:1219 字符 → **486 prompt tokens** ### 2.2 真实端到端(`evidence/真实端到端测试.md`) 用**真实 bash** 跑 4 个 `node` 脚本产生真实报错(JSONL 解析的 3 次失败 + 1 次成功)→ 真实 compactRegion 事务 → 真实模型摘要 → 续跑: | 指标 | 值 | |---|---| | 真实失败边 | 3 条(都是 `SyntaxError` / `Unexpected non-whitespace character` 这类真报错) | | 失败边保留 | **3/3** | | 续跑回答提到失败做法 | **3/3** | | 产物长度 | 373 字符 → 925 字符(**变长**:3 条死路 + 完整结构,小样本无压缩意义) | ### 2.3 fold 触发验证(`evidence/fold触发验证.md`) 模拟 200K 窗口、45% 阈值,用真实模型当摘要器,跑完整自动路径: | 场景 | 期望 | 实际 | |---|---|---| | 60K < 90K 阈值 | 不 fold | 未 fold ✅ | | 150K > 90K | fold,surface 收缩 | fold ✅(25 → 6 个节点) | | 立刻再调(增量 0) | growth gate 拦住 | 被拦住 ✅ | | 增量 90K | 再次 fold | 再次 fold ✅ | **这是「自动 fold 真的会触发」的唯一直接证据** —— 之前只有手动给范围的事务级验证。 ### 2.4 性能(完整分解见 [性能与成本](性能与成本.md);原始数据 `evidence/bench-2000.json`) | 操作 | 耗时 | 备注 | |---|---|---| | 事件落账(assistant + tool/result 一对) | 0.007 ms | 每步两次 | | 渲染热上下文(预算 500 tokens) | **0.478 ms** | **323 tokens**(L5 降级,原版 28,800;v1.2.x 时 476、v1.3.x 时 310) | | 渲染硬事实块(摘要用) | 1.568 ms | 81,167 字符(原版 222,050) | | 渲染棋谱 md | 1.301 ms | 只在 idle / fold 后写(不每步写);建索引前 406ms | | 序列化 graph.json | 3.8 ms | 1.41 MB | --- ## 三 · 三个技术判断(都有数据支撑) 1. **官方实现会跨压缩失忆**:`selectCompactableRange` 从 surface 第一个非 system 节点开始压,而上一次 checkpoint 就在 surface 里,于是每轮都被再摘要一次。Compaction Cliff 实测安全规则五轮后只剩 10%。本插件把起点挪到「最后一个 checkpoint 之后」,`test/integration.test.mjs` 有「连续三次 fold 范围互不重叠」的硬断言。 2. **约束必须机械保留,不能靠模型遵守**:多规模实测(5/15/30 条失败边)发现约束保留率在 0/2~3/3 之间跳 —— 同一份 prompt,模型心情不同结果不同。改为 `engine.summarize()` 在模型输出后**机械追加** `CONSTRAINTS_VERBATIM` 段:规则抽什么,产物里就有什么,模型写不写都不影响。 3. **失败原因必须由规则注入**:真实模型对照证明 —— 同一段会话,官方 8 段模板平均只保留 4.67/8 条失败边(最差 3/8),而带 `[HARD FACTS]` 的棋谱 prompt 3 次全部 8/8。差异不是模型能力,是 prompt 是否把「不许丢」写成硬约束。 4. **热上下文必须是索引,不是全集**:1201 条失败边的现实里,「全部常驻」会渲染出 28,800 tokens。改为「预算内逐条列 + 超预算只列最近 20 条 + 汇总行指向 graph.json」,**323 tokens**(v1.4.0)解决,且汇总行保证可发现。 6. **标记与原因都要「自解释」(v1.4.0,模型消费者视角)**:实测模型 12/12 次回答都引用了**报错原文**, 而旧版 L3+ 把它整段砍掉(全库 133 条可见失败边只有 25 条带原因);标记则必须自带语义 (`blocked:permission` 而不是 `[blocked]`),且**出现即解释**(图例按用到的标记动态生成)。 代价实测 +6.8% tokens,换来 2.35× 可见原因(`evidence/热清单标记AB实验.md`)。 5. **原始记忆必须保留,棋谱只是索引**:干净子代理实验(15 条真实死路,`evidence/棋谱有效性实验.md`)证明棋谱能让一个没有记忆、没有源码的 agent **15/15 避开已知坑**;但对照组(只给原始文档、不给棋谱)补出了 **2 条棋谱漏掉的坑** —— 任何抽取都会漏。所以三层(索引 / 节点 / 原始)都要留,砍掉原始层就是永久丢信息。 --- ## 三·五 · v0.1.3:独立代码审查修掉的 3 个 P0 一个独立审查子代理(只读,真实日志回放 + 内存复现)报了 15 条,其中 3 个是会让插件「看起来在工作、实际在毁数据」的 P0: | # | 问题 | 证据 | 修法 | |---|---|---|---| | P0-1 | `this.flushTimers` 从未初始化 | `#scheduleFlush` 第一行 `this.flushTimers.has(key)` → TypeError;**debounce 落盘从未工作** | 构造函数初始化 Map | | P0-2 | 节点创建写死 `worldHash:"pending"`,fold 前统一回填成同一哈希 | 4 个 step 后节点 5→2、3 条边 `from` 悬空;live graph 30 个 agent 只有 1 个真哈希 | engine 注入 `state.worldHashOf()`,节点用创建时刻真实哈希;`touchPath` 真的喂给世界状态 | | P0-3 | `mergeNodeInto` 只改 `edge.to` 不改 `edge.from` | live graph 155 条边里 **115 条 `from` 指向已删节点** | 补 `edge.from === fromId` 分支 + 新增单测 | **这三个的共同点**:不抛异常、不报错,只是悄悄把图结构写坏。`validate()` 能查出来,但运行期从来没有人调用它 —— 这条(P1-11)还没修,已记进 HANDOFF。 **当前测试**:81 项单测全绿(修 P0 后新增 3 条:flushTimers 存在性、合并非叶子节点、真实哈希非 pending)。 ## 四 · 已装好(可回滚) - junction → `profiles/desktop/node_modules/dsh-gokifu-compact` - `profile/package.json` 加 link 依赖(**没**加 `dsh.profile.bundles`,避免顶层双挂载) - `preset-bundles/personal-assistant/cordis.patch.yml` 的 compaction group 内:`compaction-basic` → `dsh-gokifu-compact`(`command-compact` / `tool-result-pruner` 原样保留,仍在 `isolate:{compaction:true}`) - 运行期证据:`/chess-context/sessions/` 下已有 2 个会话的 `graph.json` 回滚:`node test/rollback.mjs`(恢复 `*.bak-before-chess-context-*`)。 --- ## 五 · 已验证 / 未验证(不虚报) **已验证** - 上述四层测试全部通过(81 + 13 + 4 + 5 项) - 真实模型对照:本插件 8/8 vs 官方 4.67/8(3 次采样) - 真实端到端:真实 bash 失败 → 真实事务 → 真实模型续跑 3/3 - 运行期:插件已在当前 DSH 进程里工作(每步注入 ``,graph.json 落盘) **未验证** - **v0.1.2 的修复尚未在运行中的 DSH 里生效** —— 当前进程加载的是 v0.1.0。重启后按 `docs/重启后验证清单.md` 四条核对。 - 真实会话里的 fold 尚未触发(会话上下文没到 45% 阈值);fold 的运行期行为只有 `real-fold.mjs` 的事务级证据。 - `summaryMode: rules` 在真实长会话里的摘要质量(只做了短样本)。 --- ## 六 · 下一步 | 优先级 | 动作 | 判据 | |---|---|---| | P0 | 重启 DSH → 按 `docs/重启后验证清单.md` 四条核对 | 四条全过,结果写进 `evidence/重启验证.md` | | P1 | 在真实长会话里触发一次 fold,记录前后 token 曲线 | 真实计费口径的节省数字 | | P2 | 把 `test/real-e2e.mjs` 扩成 20 轮的真实任务(当前 4 轮样本太小) | 压缩比与续跑保真都有统计意义 | | P3 | `worldstate` 换成 `git status --porcelain` + mtime 混合指纹 | 外部进程改文件时置换仍准 | --- ## v1.x(2026-10-28 下午 · 模型消费者视角) ### 六个版本,全部子代理实测驱动 | 版本 | 变更 | |---|---| | v1.2.6 | 三层树补缺陷 1/3/4:`errorSnippet` 400、写所有 `g.nodes`、失败边全表 | | v1.2.7 | 补缺陷 2/5:评语兜底(162 节点)、`verify:kind`(101 条) | | v1.2.8 | 补缺陷 6:写顺序(json 最后 = 提交点)+ index `updated` | | v1.2.9 | 修死链(230 个)+ 计数(`g.nodes`)+ 评语标注「非 LLM 原话」 | | v1.3.0 | text 型 failure 标记 `[text:?]`(47 条)—— 数据层回洗会被运行中的会话覆盖 | | v1.3.1 | 热清单省预算:短标签 + L4 20 条(310 tokens / L4) | ### 核心教训 1. **结构对了不等于内容对了** —— 三层树的结构是对的,但 `errorSnippet` 截断太狠、节点漏写、全表没列、评语没兜底、误报没标注。 2. **不要改运行中的数据** —— 真实会话的 `#flush` 会覆盖 `graph.json`,回洗要在会话停止后做。 3. **SGF 是树,我们是 DAG** —— 最大入度 119,目录嵌套会让同一节点重复 119 份。 ### 实测数据 | 指标 | v1.2.5 | v1.3.1 | |---|---|---| | 热上下文(2000 边) | 476 tokens / L4 | **310 tokens / L4** | | `nodes/` 文件数 | 200(漏 24) | **242**(写所有 `g.nodes`) | | 有评语的节点 | 7 | **162** | | 带 `verify:kind` 的失败边 | 0 | **101** | | `../raw/` 死链 | 230 | **0** | | `[text:?]` 标记 | 0 | **47** |