# 方案书 · dsh-GOkifu-compact > 定稿于 2026-10-28。上游需求见 `HANDOFF.md` 的「用户原话」。 > 市场对标见 `docs/市场对标.md`,DSH 接口测绘见 `docs/DSH-技术测绘.md`。 --- ## 一 · 一句话方案 **把上下文压缩从「历史 → 一段 prose 摘要」改成「历史 → 棋谱 DAG(节点=状态,边=尝试+结果+验证信号) + 每轮热上下文(根目标原话 + 当前路径 + 已探明死路清单)」**。 压缩是增量 fold,每块只压一次;失败边是常驻资产,永不因为压缩而消失。 --- ## 二 · 为什么现成方案都不够(实测/读码依据) | 方案 | 机制 | 致命缺陷(相对本需求) | |---|---|---| | `dsh-compaction-basic`(DSH 官方) | 压力过阈值 → 用一次 LLM 调用把「最旧平衡范围」换成一条 8 段模板摘要 | ① 每轮压缩范围从 surface 头开始,**把上一次 checkpoint 再摘要一遍** → 保留率 ρ^k 几何衰减(论文实测 5 轮后安全规则存活率 10%);② 摘要模板里没有「失败尝试」这一栏,否定结果必然丢;③ 只在 80% 窗口才动,前期上下文线性涨,每轮都贵 | | `dsh-compaction-tool-result-pruner` | 压缩触发时按字符预算修剪超大工具输出 | 只在压缩时剪;不解决「失败原因丢失」;无结构 | | billion-context(acp-kernel) | 模型主动调 `compress/decompress/search_context`,增量分层块(tier1/2/3),growth-gated 触发,保护区 | 已解决 ①②③ 中的增量与可逆,但:摘要块是 **prose 块**,没有「节点=状态 / 边=尝试」的结构,**没有置换检测**,也不能保证「所有失败边的一句话原因」常驻;且它靠代理层改写请求,DSH 有自己的 compaction seam,不需要代理 | | opencode-dcp | 剪除过期工具输出 | 只剪不加,不可逆 | | LLMLingua / ACON / AgentFold | token 级压缩 / 训练 fold 算子 | 需训练或额外模型;token 级压缩会破坏报错原文、路径、diff | **结论**:billion-context 的「增量 + 分层 + 可逆 + growth gate」是必须吸收的工程骨架; 官方 backend 的「surface 事务(锁 / 稳定性 / 工具对平衡)」是必须复用的安全底座; **用户提出的棋谱 DAG + 失败记忆 + 置换检测是市场上没有的增量价值**,也正是「超越」的落点。 --- ## 三 · 采纳方案 ### 3.1 双层结构 ``` ┌───────────────────────── 热上下文(进入模型请求)─────────────────────────┐ │ system prompt(不变) │ │ 最近一次增量 fold 的产物(节点+边+死路清单) │ │ 失败边一句话清单(每轮重建,≤ hotLedgerMaxTokens) │ │ ── 逐字保留的近期窗口(retainTokens)─────────────────────────────────── │ └───────────────────────────────────────────────────────────────────────────┘ ┌───────────────────────── 冷存储(磁盘,可检索可回读)─────────────────────┐ │ /sessions//graph.json 完整 DAG(节点+边+索引) │ │ /sessions//graph.md 人读视图(棋谱) │ │ DSH 自己的 session 日志(原始记忆,不复制;节点记 seq 坐标,用时回读) │ └───────────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 数据模型(写死在 schema 里) ```js // 节点 = 状态 UserNode = { id, kind: 'user', seq, turn, text, // ★ 原话逐字,不可压缩、不可被后续节点覆盖 constraints: [string], // 从原话抽出的约束("必须 / 不要 / 只能") } AgentNode = { id, kind: 'agent', seq, turn, goal, // 当前目标(一句话) worldHash, // 世界状态哈希 constraintHash, // 已知约束集哈希 via, // 入边 id(当前路径) } // 边 = 动作 + 结果(★ 道路是边,不是节点) Edge = { id, from, to | null, intent, // 预想道路,一句话(意图层,不写实现细节) action, // 实际执行(工具名 + 关键参数,≤120 字) result: 'success' | 'failure' | 'unverified' | 'untested', verify: { kind, summary },// 外部验证信号:exit code / 测试通过失败数 / 报错原文 errorSnippet, // 失败时的报错原文前 400 字 evidence: { start, end },// 原始记忆坐标(surface seq 区间),用于回读 ts, } ``` **硬规则(对应用户原话)** 1. `UserNode.text` 逐字保存;压缩时**原样注入**,模型不许改写。 2. `Edge.result === 'failure'` 的边,`intent + 一句话 reason` 必须在**每一次** fold 的产物里逐条出现,不合并、不省略。 3. 道路是边:两条不同入边可以指向同一个 AgentNode(置换)。 4. 节点到节点之间的过程不逐条存,只存 seq 坐标 —— 原始记忆在 DSH 日志里,回读即可。 ### 3.3 状态哈希与置换检测(技术命门) ```js worldHash = sha256( sort(触达文件 → `${relPath}:${size}:${mtimeMs}`) // 只 stat 本会话写过的文件 + '|' + cwd ) constraintHash = sha256(sort(所有 UserNode.constraints 的规范化文本)) stateKey = worldHash + '/' + constraintHash ``` - `worldHash` 只用「本会话触碰过的文件」做 stat,成本 O(改动文件数),**不扫全树、不跑 git**; 可选 `gitSha: true` 时额外取 `git rev-parse HEAD`(压缩时一次,不每步)。 - `stateKey` 命中历史 AgentNode ⇒ 记 `transposition: {nodeId, distance: 已走过的边数}`。 这实现用户说的「换了一条路回到之前卡住的地方 = 前进米数无长进」。 - 置换命中后:热上下文出现一行 `⚠ 回到节点 N(这里已失败 M 次:E12/E15)`,并且**不新增节点**,只在图上加一条指向旧节点的新边。 ### 3.4 每步落账(零模型成本) 挂 `session/event`,纯规则抽取: | 事件 | 动作 | |---|---| | `user/message`(非 checkpoint) | 新建 UserNode(逐字);抽 constraints(匹配「必须/不要/只能/一定要/禁止/记得」) | | `assistant/message` 含 tool-call | 为每个 call 开一条 `result: 'untested'` 的边(intent 取 assistant 文本首句) | | `tool/result` | 结算边:判 result + verify + errorSnippet;更新 to 节点(必要时新建 AgentNode 并算 stateKey) | **判定规则(`src/verifier.mjs`,全部可单测)** | 信号 | result | |---|---| | 结果文本含 `[exit code: N]` 且 N≠0 | failure(verify.kind='exit') | | 结果含 `Error:` / `Traceback` / `失败` / `FAILED` / `✗` 且非 exit 0 | failure | | 结果含 `[exit code: 0]` 且无错误标记 | success(verify.kind='exit') | | 结果含 `passing`/`tests passed`/`all tests passed` | success(verify.kind='test',带数字) | | Write/Edit 成功(无错误标记) | success(verify.kind='diff',带行数) | | 其它(纯读取、搜索、查询) | **unverified**(不是 success —— 防污染图) | ### 3.5 触发策略(性能与性价比的来源) 三层,按成本从低到高: 1. **每步落账**(0 token):只更新 graph.json,不发请求。 2. **增量 fold(growth gate)**:`agent/pre-step` 时判断 ``` 新增 token = measure.totalTokens - lastFoldTotalTokens 触发 = totalTokens >= min(W*thresholdRatio, W-O-headroom) // 默认 0.45 && 新增 token >= growthFloorTokens // 默认 40K ``` 满足才压缩,范围 = **上一个 checkpoint 之后**到「保留尾部边界」。 ⇒ 每块内容一生只被摘要一次,保留率停在固定值,不随轮数衰减。 3. **溢出自救**:`agent/request-error` 且 `CONTEXT_WINDOW_EXCEEDED` → 绕过阈值强制 fold 一次并重试。 ### 3.6 摘要调用(hybrid 模式:规则事实 + 一次模型润色) prompt 结构(关键:**硬事实由规则给,模型只负责表达**): ``` [SYSTEM FACTS — mechanically extracted, authoritative, must be preserved verbatim] ROOT USER REQUEST: <逐字> CONSTRAINTS: <逐条逐字> FAILED EDGES (one line each, never merge, never drop): E3 intent="改成流式" action="edit server.js" → failure: TypeError at line 42 | seq 120-131 CURRENT PATH: N12 → N18 → N21 UNVERIFIED: E7 (ran, no test output) [CONVERSATION TO CONDENSE] <回放的历史> [OUTPUT FORMAT] ## 目标 ## 棋谱(节点:状态 → 目标) ## 已探明死路(逐条保留) ## 已验证事实 ## 未验证 ## 当前节点与下一步 ## 原始坐标(seq 区间) ``` - `summaryMode: 'rules'` → 完全不调模型,用规则模板直接生成(最便宜,适合理性任务) - `summaryMode: 'hybrid'`(默认)→ 一次模型调用,`maxTokens` 默认 2048,purpose='compaction' - `summaryMode: 'llm'` → 完全交给模型(只在明确需要时) ### 3.7 分层 fold(tier-2) checkpoint 数量 > `tier2After`(默认 8)时,把**最老的两个 checkpoint 合并成一个**: 纯规则拼接(不调模型),保留所有死路行与目标行 ⇒ 成本 0,保留率只降一次且有限。 ### 3.8 与 DSH 的接缝 - 实现 `CompactionEngine`,注册 `ctx.compaction`;`cordis.patch.yml` 里 `disabled: true` 掉 `@deepseek-ai/dsh-compaction-basic`(单实例要求)。 - **继承 `BasicCompactionEngine`**:surface 事务(`compaction/start` → `summary` → `replace` → `end`、 锁、稳定性校验、工具对平衡、`` 框定、checkpoint source)全部复用官方实现; 只覆盖三处策略:`compactIfNeeded()`(growth gate + 增量范围)、`summarize()`(棋谱 prompt)、 以及新增的 graph keeper listeners。 - `/compact`(`dsh-command-compact`)不改,继续可用。 - 失败清单注入:优先用 agent 的注入通道(待测绘确认);不可用时退化为「随 checkpoint 携带 + 每次 fold 前把未进 checkpoint 的新失败边并入」。 --- ## 四 · 已否决方案(含理由与复活条件) | 方案 | 否决理由 | 什么条件下重新可行 | |---|---|---| | 直接用 `dsh-compaction-basic` + 改 prompt | 摘要模板可改,但「每轮重摘要」与「丢弃否定结果」是范围选择逻辑,不在 prompt 里 | 官方实现增量 fold 后 | | 自己重写 `compactRegion` 事务 | 锁、稳定性、工具对平衡、checkpoint 标记是 DSH 会话不变量,重写等于重交一遍 bug | 官方 seam 把事务开放为可组合函数时 | | 走代理层(像 billion-context 改写请求) | DSH 有原生 compaction seam,代理层会与 magpie 网关重复,且 KV cache 反而更差 | DSH 没有 compaction seam 时 | | MCTS / beam search 真的做多分支执行 | 无廉价验证器时无意义(ReSCALE:预算越大越差);成本爆炸 | 有确定的外部验证器且任务可重复执行时 | | 每步对所有预想道路各跑一遍 | 烧钱;用户要的是「失败后换路」,不是「并行穷举」 | 验证器免费且可并行时 | | 摘要写成 md 索引供模型查 | md 适合人读,机器做不了等价判定;改 JSONL + md 视图(本方案采纳) | 不适用(已分层解决) | --- ## 五 · 风险与缓解 | 风险 | 缓解 | |---|---| | 继承官方类,官方升级改签名 | 锁版本 + `test/engine.test.mjs` 有契约测试;升级前跑 `node test/verify-install.mjs` | | 规则判定误判(把失败当成功) | 默认 `unverified` 而非 success;判定表全部单测;误判时热上下文仍有 errorSnippet | | 预热/首次压缩没有 graph | 首次 fold 用「从表面起点抽取」的退化路径,正常可用 | | 每步 stat 文件影响性能 | 只 stat 本会话写过的文件;上限 200 个;超过则退化为动作链哈希 | | 压缩后 token 反而更大 | 复用官方「摘要必须小于被遮蔽内容」校验(framedSummaryTokenCount < shadowedRouteTokenCount) |