换窗无痛 v2:定稿(三类九条改完)
## v2 是什么
v1 是把小直那半(骨架)和未迟那半(管道口)攒到一起;v2 是照着三类九条挑完的定稿。两边都封了版,这版只做三件事:把打架的打平、把落不了地的换成能落地的、把漏的补上。
## 一、打架的,条条定死
1. 「两页由库在 open 时重渲染」与验收①「库全空只靠两页也得接上人」打架 → **两页在 raw 侧留快照**(快照也带 last_confirmed),由 open 渲染,人手不碰。库空了,raw 还在。
2. 七「同指纹只挂来源表记数」与十「不存次数」打架 → 改:**只挂来源行,次数一律 COUNT 出来**;重放走 INSERT OR IGNORE。
3. 七「规矩、事实即时写」与四「单写者」/十「整批 append+fsync」打架 → 定:**即时=即时 append raw,单条自成一批**;入库仍走唯一写入口、延迟派生。
4. 暗坑1(bge 版本/维度/前缀指纹)与八「向量先别上」——同一个东西一处当常驻依赖、一处当可不装 → 定:**v1 明写不上向量**,暗坑1 整条进 v2。
5. 暗坑6(心跳按天滚动、跨天回退最近非空文件)与设计二判据打架:那份文件末行是昨天的正常收尾(收尾标记=真、seq 也连),跨夜首开会判成「没断」→ 定:**回退到非当天文件,本身就算断过**。
6. 暗坑8「库跟人走」与 boot_id 判据打架:换机器就是 boot_id 变,正常搬家被读成「被掐」→ 定:**搬家显式记一行 migrate,重放器当清窗认**。
## 二、落不了地的,换掉
1. **清窗这档没有写入者。** 心跳靠每轮收尾 append,可清窗那一刻进程没死、pid 不变、seq 不断,手伸不进去。定法:**宿主清窗前留一行**(靠钩子);**没有钩子的档,明写「判不出,只报没断」**,不许装知道。
2. **「两页 open 时重渲染」晚一轮。** open 是开场自己调的,那时新窗已经把旧两页读进上下文了。定法:**挂轮末钩子**——每轮收尾写回执行、顺手刷新两页快照。
3. **pid 取谁。** tick 每轮都是新进程,取自己 pid 就全判成「被掐」→ 定:**心跳行只记 hub 主进程 pid,tick 从环境拿,禁自己 getpid。**
4. **bigram 落不下。** FTS5 内置没有 bigram 分词器 → 定:**jieba 预分词,单存一列走 FTS。**
5. **敏感串清单没落地。** 定:**独立文件带版本、命中只标不删、误杀靠格式白名单(「9.8 元」不是 IP)**;清单本身也是家底,不进对外备份。
## 三、漏的,补上
1. 字段表补回 **boot_id + pid**(判「哪种断」用)。
2. **断电半行**:raw 尾行可能是断 JSON。重放器三处(心跳/条目/批次)都要「非法尾行丢尾、不整库崩」。
3. **两页的写者**没人定。定:**页的写也收进单写者,临时文件+原子 rename**(獬兵和会话同时叫醒也不会互相盖)。
4. **2200 字硬帽**:库渲染自带裁剪,别超帽被截、锚点静默丢。
5. **作废/更正入口**挂回来:**append void 行 + raw id,历史不删。**
6. **并行窗口**:清窗/唤醒/通道可能同时在跑。写死:**同一人同一时刻只有一个写者,其余只读。**
7. **「管家」得定义**:投候选那个进程,跑本机,只投本地 pending,**不碰库**。
8. **降级路径**:open 失败/库损坏/指纹对不上 —— **两页 + 一行明说「库不可用」,不许静默**(开场空白比低召回更坏)。
9. **指纹也得打 raw 侧**(跟 consumed 一样):库是可重建派生,指纹只存库里,rebuild 之后要么无从校验、要么永远拒服务。
## 四、钩子这条线,我在这台机器上核过了
小直说 hermes 有 `post_llm_call` 和 `on_session_start`——**核过,真有**(另有 pre_tool_call/post_tool_call/pre_llm_call/on_session_end)。插件可以放在自己家的 plugins 目录里,走钩子和 ctx,**不动核心一行代码**(框架规矩写得很死:插件不许碰 core,需要新能力就加通用钩子)。
所以「开场自动读回执 + 轮末写回执」这根线有解:
- **轮末(post_llm_call)**:append 一行心跳 + 刷新两页快照 + 库不可用时写一行降级说明;
- **开场(on_session_start)**:读最近非空回执 → 判断没断/哪种断 → 把「刚换过窗,账在这儿」这行放进我眼前。
钩子没触发的那一档(被打断、超轮),正好就是「最多丢最后一行」。
## 五、数字定死
心跳 **5 分钟一拍**,阈值 **2×=10 分钟**(只做兜底);回执读**最近非空的那一份**;raw **按天滚档**、老档压缩;两页 **2200 字**是硬帽,渲染必须裁剪。
## 六、v1 到底做什么
**v1 = raw(心跳 + 条目)+ SQLite 派生库 + 三个口(open/tick/recall)+ 两处钩子 + 三档验收。**
**向量不上**(向量重排、model/维度/前缀指纹校验,全进 v2)。四百条这个量级,FTS(jieba 预分词)+ 暴力扫就够——别又养一个 hindsight。
## 七、验收(三档)
① 库全空,只靠两页快照也得把人接上(**四样都新鲜**,不是四样齐);
② 连清两窗 + 半路 kill -9,新窗第一句仍旧是「刚换过窗,账在这儿」;
③ rebuild 之后**不许**喊「刚换过窗」。
过不了就是没感知到。
## 八、分工
- 骨架(表结构 + 三条查询):小直
- 管道口三样(提炼规矩/写入时机/写入口):未迟
- 钩子(两处)+ 合稿:枕河
- 定稿:这一篇
- 动土:等阳阳点头