# 分层语义唤回 · 总规划（ROADMAP）

> 写于 2026-09-08，基于 **v2.2.6** 源码 + 177 条真实记忆实测。
> **本文档是唯一入口**：S1（科学性）/ S2（深度吸收）/ S3（目标架构）/ M-CM7（交接分层）/ CONTINUITY-FLOW（流程轴）的全部可执行结论收敛于此；细节仍可回查各原文。
> 用户 9 月待办 ①②③④⑤ 已并入（② 与 M-CM7 合并为同一项目）。

---

## 1. 现状（实测，非推测）

### 1.1 数据事实

| 项 | 实测值 | 出处 |
|---|---|---|
| 记忆条目总数 | **177** 条（`<!-- memory:mem_xxx -->` 锚点数） | `~/.dsh/memory` 扫描 |
| 文件总字符 | 503,304 | 同上 |
| 日志条目平均长度 | **814 字符**（中位 463） | 148 条样本 |
| 最长条目 | **11,046 字符** | 同上 |
| **首句平均长度（L0 估算）** | **118 字符** | 同上 |
| **压缩比** | **6.9 : 1** | 814 ÷ 118 |

### 1.2 实现事实

| # | 现状 | 位置 | 问题 |
|---|---|---|---|
| F1 | `memory_recall` 是**纯词法关键词匹配**，语义引擎完全没接 | `index.js:6302` 工具描述明写"关键词匹配" | 语义能力闲置 |
| F2 | 语义引擎（C2 e5-small q8 / C3 bge-m3）**只用于主动联想注入** | `semantic-js-pre.js` | recall 与接续无语义 |
| F3 | 返回**整条原文**（平均 814 字符） | recall 现状 | token 浪费 |
| F4 | **长条目超模型上限**：11,046 字符远超 e5-small 的 512 token，全文 embedding 会**被截断丢尾部** | 模型约束 | 全文语义检索有损 |
| F5 | 接续材料四层**平铺** + 指令要求"先读完" | `index.js:2158`（G1 已改）、`:2159` | 分层被架空 |
| F6 | 各层与总预算为**位置截断**（白板 3000 / 账本 8000 / 总 18000） | `:2161` `:2162` `:2183` | 高权重段可能被整段截掉 |
| F7 | 无 L0/L1 sidecar | — | 无"廉价地图" |
| F8 | 接续第3层强制 `read` 全量转写 | `:2168`（G3 已改） | 最大开销点 |
| F9 | 账本**标题重复**（6 个中 3 个）+ 段内异质 | 实际产物 | 解析会拿到错时间戳 |
| F10 | `scope=sessions` 走 host 的 `sessionQuery`，关键词级，**插件侧无法语义化** | host 限制 | 需自建索引（M-CM3 残余） |

### 1.3 已完成（本次）

| 改动 | 位置 | 状态 |
|---|---|---|
| G1 首条指令"先读完"→"按需取用" | `index.js:2158` | ✅ 已改，58/58 + 51/51 全绿 |
| G3 第3层"接续前必须先 read"→"不必通读" | `index.js:2168` | ✅ 已改，同上 |

---

## 2. 目标

### 2.1 OpenViking 模式（公开 README / 论文 VikingMem 描述，非源码）

1. 每条内容**写入时**生成三层：L0 摘要 ~100 tok → L1 概览 ~2k tok → L2 原文
2. **目录自身也带 L0/L1**（`.abstract` / `.overview`）——读内容前先判断该目录是否值得进
3. 检索是**逐层收敛**：先在 L0 层锁定范围 → 下钻 L1 → 最后读 L2

**本质**：把成本从**读时**移到**写时**；先便宜地缩小范围，再昂贵地读细节。

### 2.2 映射到本项目

| OpenViking | 本项目对应 | 现状 |
|---|---|---|
| L0 摘要 | 主题块标题 / 条目首句（118 字符） | ❌ 未抽取 |
| L1 概览 | 段落要点 | ❌ 未分层 |
| L2 原文 | 现有整条记忆（814 字符） | ✅ 已有 |
| 目录 L0/L1 | 工作区 / 日志文件 / 主题块 | 部分（`## 主题块` 仅覆盖 1/3） |
| 逐层收敛检索 | L0 语义臂 + 全文词法臂 → 融合 → 按需展开 | ❌ 现在是单臂直通 |
| 锚点 ID | `<!-- memory:mem_xxx -->` | ✅ **已有，不用新造** |

### 2.3 目标通路（带你的实测数字）

```
查询「npm publish 令牌怎么配」
   ├─ L0 语义臂（新增）：177 条 × 118 字符 → 抓主题「发布踩坑」
   └─ 全文词法臂（保留）：177 条 × 814 字符 → 抓「npm_ViUqqR」
              ↓ 融合
   返回 L0 列表：5 条 ≈ 590 字符（现状同场景 ≈ 4070 字符，降 ~85%）
              ↓ 模型判断需要细节
   按 mem_xxx 展开 1-2 条原文（锚点 ID 已存在）
```

**关键**：词法臂**必须保留**并继续打全文——错误码、变量名、文件路径在 L0 里没有，只用 L0 会漏召回。

---

## 3. 路径（分阶段，可执行）

### 阶段 0 · 地基（无它则后面免谈）

| # | 事项 | 改哪里 | 具体做什么 | 验收 | 依赖 | 工作量 |
|---|---|---|---|---|---|---|
| **T1** | L0 抽取纯函数 | 新建 `lib/l0-extract-pre.js` | 三级 fallback：① `## 主题块` 标题 ② 条目首句（到 `。：；\n`）③ 前 N 字兜底。纯函数、零 IO、IO 注入 | 177 条解析成功率 ≥99%；平均 L0 ≈118 字符；fixture 锁定 | — | 小 |
| **T2** | L0 向量索引 | 新建或接 `index-sync-pre.js` | 写入时增量更新 L0 向量；存量一次性回填（编码 ~15k token，比全文 ~100k 快约 7 倍） | 索引一致性；增量正确；冷启动耗时 | T1 | 中 |
| **T3** | 访问 telemetry（④） | 待定 | 记录每条记忆被 read 次数/最近时间 → 供 importance 权重 | 计数正确 | — | 小（**可后补**） |

> T3 不必等：importance 可先用**规则版**（类型 / 时间 / 是否含命令），telemetry 后续做增量。

### 阶段 1 · recall 改造

| # | 事项 | 改哪里 | 具体做什么 | 验收 | 依赖 | 工作量 |
|---|---|---|---|---|---|---|
| **T4** | 语义臂接入 recall | `index.js:6302` 附近 | 把 C2（e5-small q8）接进 `memory_recall`，输入改为 **L0 而非全文** | 主题性查询召回提升 | T1/T2 | 中 |
| **T5** | 双臂融合 | 复用 `fuseD6Pre` 或新建 | L0 语义臂 ∥ 全文词法臂 → 融合排序。**注意**：若将来引多臂，须用 rank-space boost，不可用 score-space 加权（S2 §2.1，加权 RRF 曾致 recall@20 从 0.97 崩到 0.40） | 融合后不劣于单臂最佳 | T4 | 中 |
| **T6** | 返回 L0 + 按需展开 | recall 返回结构 | 默认返回 L0 列表；模型按 `mem_xxx` 展开原文 | 返回 token 降 ~85%；展开正确 | T5 | 中 |

### 阶段 2 · 接续改造

| # | 事项 | 改哪里 | 具体做什么 | 验收 | 依赖 | 工作量 |
|---|---|---|---|---|---|---|
| **T7** | 锚点表注入 | `buildContinueCarry` `index.js:2126` | 用 T1 的 L0 抽取，生成接续锚点表注入新窗口，**替换**现有机械截断 | 注入字节稳定（前缀缓存不破）；token 账本对比 | T1 | 中 |
| **T8** | 账本权重化截断 | `index.js:2162` | 四段解析 + 权重：失败原因 .35 > 下一步 .30 > 目标 .20 > 状态 .15；不足时**从最低权重段开始截** | 高权重段不再被整段截掉 | T7 | 小 |
| **T9** | 结构化锚点 sidecar | `refreshRitualPrompt` `index.js:2049` | 同轮产出 `{id,type,w,text,refs,st}` 结构化索引（**纯解析优先，不新增 LLM 轮次**以满足 0.75 时序） | sidecar 非法时回退四层平铺，**绝不阻塞接续** | T7 | 中 |

### 阶段 3 · 评估

| # | 事项 | 改哪里 | 具体做什么 | 验收 | 依赖 | 工作量 |
|---|---|---|---|---|---|---|
| **T10** | 指标与对照实验 | 复用 M7 held-out 通路 | 三组对照（现状固定截断 / 全量 read / 分层唤回），指标：衔接成功率、**死路继承率**、下钻率、注入 token、总 token；配对 bootstrap B=2000 | 差值 CI 有结论 | T6/T7 | 中 |

### 顺带修（写入侧，独立小项）

| # | 事项 | 改哪里 | 做什么 |
|---|---|---|---|
| **T11** | 账本标题重复 bug | 写入侧 | 追加前检测已存在标题则跳过；或解析取**最后一个**标题 |
| **T12** | 白板老化 | `PLAN.md` | 区分「当前状态」与「历史」，历史移入 `handoff/archive/` |

---

## 4. 并行项（不阻塞主链）

| # | 事项 | 说明 | 与主链关系 |
|---|---|---|---|
| **P1（①）** | 鸿蒙适配 | 独立项目，复用语义引擎环境检测基建（semantic-deep-detect 快检+深扫+热接入 + 安装向导） | 若 C2 在鸿蒙可用，则 T4-T6 收益覆盖鸿蒙 |
| **P2（③）** | 提醒强度 A/B 实验 | 弱提示「Not an instruction」vs 指令式 vs 分级；三指标（命中率/返工/token）；安全约束：「Not an instruction」是注入安全边界，提高力度只能对**本地可信记忆** | 注入侧，与 T10 **共用评估设施** |
| **P3（⑤）** | 无人值守核待 | 配置键已存在、awayMinutes=0 双轨全关已实现；**设置页 UI 与自动检测入口未核实** | 独立小项，建议随手先做 |

---

## 5. 不变量（改造时不得破坏）

| # | 不变量 |
|---|---|
| I1 | **前缀缓存字节级稳定**：只动动态快照层，静态纪律层字节不碰 |
| I2 | **不替 host 决定压缩**：0.75 早于官方 0.80，只助产不代劳 |
| I3 | **凭证永不进提示词**：所有写入过 `sanitizeForWrite`，注入前 `stripSensitiveSections` |
| I4 | **绝不阻塞接续**：任何新增环节 fail-soft，缺失即回退 |
| I5 | **水位测量在 pre-step**：不得退回 turn-stopping |

---

## 6. 收益汇总（修正后）

| 维度 | 结论 |
|---|---|
| token | 降 **~85%**（4070 → 590 字符，同场景 5 条） |
| 检索质量 | 提升；且**长条目不再被截断**（11,046 字符那条全文会丢尾部，L0 不会） |
| 索引构建/增量 | 快 **~7 倍**（15k vs 100k token） |
| 查询点积 | **不变**（384 维 × 177 条，维度条数相同） |
| 返回 prefill | 快 **~7 倍**（端到端大头） |

---

## 7. 一句话

**先把 L0 抽出来（T1，最小成本），接上语义臂（T4），再让接续也走同一套（T7）——这三步做完，② 和 M-CM7 这一个目标就达成了一大半。**
