Skip to content

Commit 8ff11b6

Browse files
committed
docs: finalize session recall design decisions
1 parent 7f32ea4 commit 8ff11b6

1 file changed

Lines changed: 138 additions & 10 deletions

File tree

‎docs/superpowers/specs/2026-05-14-hermes-style-session-recall-design.md‎

Lines changed: 138 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,25 @@ Codex session jsonl
2222
## 已确认决策
2323

2424
- 使用全局 SQLite 数据库,不按项目拆多个数据库。
25+
- SQLite 是本机 durable recall archive,不只是镜像缓存或临时索引。
26+
- SQLite 要保存原始 session 消息和 raw json,避免 `~/.codex/sessions` 本地日志丢失后 recall 失效。
27+
- 原始 Codex jsonl 文件被删除时,SQLite 中已归档 session 不自动删除。
2528
- `csep recall` 默认只查同仓库范围。
2629
- 跨仓库 recall 必须显式使用 `--global`。
2730
- 同一个 git repository 的多个 worktree 算作同一个 repo;worktree 路径只作为更细粒度 metadata 和排序信号。
31+
- 第一版提供 `[session_recall]` 总开关,默认启用。
32+
- Stop hook 第一版自动归档当前 session;归档通过独立后台子进程执行,不是 scheduler、队列或定时任务。
33+
- 第一版不做 ingest 失败重试队列,失败只记录日志、status 和 ingest error,后续靠手动 backfill 补。
34+
- 第一版不做 purge / 显式删除能力;已归档数据不会自动删除,删除能力留到后续版本。
35+
- user、assistant、tool call、tool output 都要入库;recall 默认搜索 tool output,但 tool 命中降权,输出时强截断。
36+
- FTS 只索引提取出的可读文本;raw json 保存到库里,但不进 FTS,也不在默认 recall 输出里展示。
37+
- 第一版支持 Hermes 风格 FTS5 高级语法,默认保留 FTS5 多词 AND 语义;广搜由用户或模型显式写 `OR`。
38+
- 默认按 session 聚合结果,`--limit` 表示最多返回几个 session;同一 session 默认只返回一个最佳命中窗口。
39+
- `--recent` 支持最近 session 浏览,默认同仓库,`--recent --global` 才看全局。
40+
- 能识别当前 session 时,recall 默认排除当前 session;手动命令识别不了时不强行猜。
2841
- 第一版返回原始消息窗口,不做 LLM focused summary。
2942
- 第一版保持当前触发方式,不接 `UserPromptSubmit` 自动 recall。
30-
- 第一版支持增量 ingest 和手动历史 backfill。
43+
- 第一版支持 Stop hook 单 session archive 和手动历史 backfill;不做无参数 `csep session-ingest` 增量扫描。
3144
- recall 输出必须有总长度预算和单条消息预算。
3245
- 现有 `recall/index.json` 行为保留为 fallback,SQLite 不可用或无结果时不打断旧能力。
3346

@@ -39,7 +52,7 @@ CSEP 新增一个全局 session recall store:
3952
~/.codex-self-evolution/session_recall/state.db
4053
```
4154

42-
这个 store 负责可查询的 recall 索引。Codex session jsonl 仍然是原始事实来源;CSEP 只把这些文件导入到自己的 SQLite 表和 FTS 索引里。
55+
这个 store 负责可查询的 recall 索引,也负责本机长期归档。Codex session jsonl 是初始事实来源;一旦导入成功,SQLite 中的消息内容不依赖原始 jsonl 文件继续存在。
4356

4457
第一版最小表集合:
4558

@@ -49,6 +62,12 @@ CSEP 新增一个全局 session recall store:
4962
- `ingest_checkpoints`
5063
- `ingest_errors`
5164

65+
需要保留最小 schema version:
66+
67+
- `schema_version`
68+
69+
第一版只实现轻量版本升级,不引入 Alembic 或复杂迁移框架。
70+
5271
`csep recall` 优先查询这个 store。默认情况下,它根据当前 `--cwd` 或进程 cwd 解析 repo fingerprint,只返回同仓库命中,并围绕命中消息返回有预算控制的上下文窗口。`--global` 会移除 repo 过滤,允许跨仓库检索。
5372

5473
repo identity 和 worktree identity 分开处理:
@@ -60,23 +79,68 @@ repo identity 和 worktree identity 分开处理:
6079

6180
repo fingerprint 的实现要优先选择稳定信号,例如 git common dir、origin URL 或归一化后的 repository root。不能只用当前 worktree 路径直接做唯一身份,否则同一仓库的多个 worktree 会被错误拆开。
6281

82+
## 配置开关
83+
84+
第一版新增最小配置:
85+
86+
```toml
87+
[session_recall]
88+
enabled = true
89+
stop_hook_archive = true
90+
```
91+
92+
默认开启,避免用户忘记归档导致日志丢失。
93+
94+
- `enabled = false`:SQLite session recall 整体关闭,`csep recall` fallback 到现有 `recall/index.json`。
95+
- `stop_hook_archive = false`:手动 backfill / recall 仍可用,但 Stop hook 不自动归档当前 session。
96+
6397
## Ingest 与 Backfill
6498

6599
新增 CLI:
66100

67101
```bash
68-
csep session-ingest
102+
csep session-archive --from-hook-payload <payload.json>
103+
csep session-archive --transcript-path <path> --cwd <cwd> --session-id <id>
69104
csep session-ingest --backfill
105+
csep session-ingest --backfill --since-days 30
106+
csep session-ingest --backfill --limit-files 500
70107
```
71108

72-
`session-ingest` 从 Codex session jsonl 做增量导入。它使用 checkpoint,只处理上次导入后新增或变更过的文件。
109+
`session-archive` 归档一个明确的 session / transcript,主要给 Stop hook 使用,也支持手动补录单个 transcript。
110+
111+
`session-ingest --backfill` 扫描历史目录,批量导入旧 session。第一版不提供无参数 `csep session-ingest` 增量扫描,避免把第一版做成半个同步器或 scheduler。
112+
113+
Stop hook 会单独 spawn 一个独立后台子进程,执行 `csep session-archive --from-hook-payload <payload.json>`,归档当前 hook payload 指向的 transcript。这个子进程不与 stop-review 共用;任意一边失败都不影响另一边。前台 Stop hook 仍然快速返回。
114+
115+
这里的“后台”只表示 detached process,不是定时任务、队列或重试系统。
73116

74-
`--backfill` 扫描已有的 `~/.codex/sessions` 历史,并导入同一个全局数据库。第一版 backfill 是手动命令,不自动跑。它必须支持中断后重跑。
117+
第一版不做 ingest 失败重试队列。Stop hook archive 失败时只记录日志、`ingest_errors` 和 status 可见状态;后续通过手动 `session-ingest --backfill` 补。
118+
119+
`--backfill` 默认扫描已有的 `~/.codex/sessions` 全量历史,并导入同一个全局数据库。第一版 backfill 是手动命令,不自动跑。它必须支持中断后重跑,也支持 `--since-days` 和 `--limit-files` 控制范围。
75120

76121
ingest 必须幂等。重复处理同一个文件或同一条消息,不应该生成重复行。
77122

123+
同一 session 多次 ingest 时,不删除后重建,而是做幂等增量 upsert。唯一键使用 `(session_id, message_uid)`;`message_index` 只作为排序、窗口提取和 fallback 信息。
124+
125+
`message_uid` 解析顺序:
126+
127+
1. 优先使用 Codex event 中稳定 id 字段。
128+
2. 其次使用 item id、turn id、tool call id 等稳定字段组合。
129+
3. 没有稳定字段时,用 `sha256(session_id + raw_line)`。
130+
78131
解析失败不能阻塞整批任务。坏文件或暂不支持的 event shape 应记录 ingest error,然后继续处理其他文件。
79132

133+
如果原始 Codex jsonl 后续被删除,SQLite 中已归档数据不自动删除。ingest 或 backfill 发现源文件缺失时,只更新类似 `source_missing` / `last_seen_at` 的 metadata,不删除消息。
134+
135+
`session-archive` 成功标准:
136+
137+
- 成功写入或更新 session row。
138+
- 至少写入 1 条可读 message row。
139+
- 写入的可读 message 能通过 FTS 搜到。
140+
- repo metadata 尽量完整;无法解析 git repo 时可用 `cwd` fallback。
141+
- tool metadata 解析失败但 tool output 文本已入库时,只记 warning,不算整体失败。
142+
- 0 条可读 message 算 archive failed。
143+
80144
session metadata 至少包括:
81145

82146
- `session_id`
@@ -87,58 +151,98 @@ session metadata 至少包括:
87151
- `repo_fingerprint`
88152
- `worktree_root`
89153
- `git_branch`
154+
- `source_missing`
90155
- `started_at`
91156
- `updated_at`
92157
- `message_count`
93158

94159
message metadata 至少包括:
95160

96161
- `session_id`
162+
- `message_uid`
97163
- `message_index`
98164
- `role`
99165
- `content`
166+
- `raw_json`
167+
- `metadata_json`
100168
- `timestamp`
101169
- `tool_name`
102170
- `raw_event_type`
103171

172+
入 `messages.content` 并进入 FTS 的只应是可读文本:
173+
174+
- user 文本
175+
- assistant 文本
176+
- tool call 名称和参数摘要
177+
- tool output / tool result
178+
- 必要的 system / developer / context 片段
179+
180+
不直接进入 FTS 的内容:
181+
182+
- 纯 telemetry
183+
- token accounting
184+
- 空 content
185+
- 未提取文本的大段结构对象
186+
- `raw_json`
187+
104188
## Recall 查询
105189

106190
默认命令:
107191

108192
```bash
109193
csep recall "query"
194+
csep recall --recent
110195
```
111196

112197
默认查询流程:
113198

114199
1. 从 `--cwd` 或 `PWD` 解析当前 repo。
115-
2. 搜索 `messages_fts`。
200+
2. 使用 FTS5 搜索 `messages_fts`。
116201
3. 默认过滤到相同 `repo_fingerprint`。
117-
4. 按 FTS score、时间、轻量 role 权重排序,并给相同 worktree / cwd prefix / branch 额外加权。
118-
5. 按 session 和 message 聚合命中。
119-
6. 提取每个命中周围的原始消息窗口。
202+
4. 按 FTS score、role 权重、时间排序,并给相同 worktree / cwd prefix / branch 额外加权。
203+
5. 默认按 session 聚合命中。
204+
6. 每个 session 只选择一个最佳命中窗口。
120205
7. 默认渲染 Markdown,请求 JSON 时输出机器可读结果。
121206

207+
FTS 查询语法尽量接近 Hermes:
208+
209+
- 支持普通关键词。
210+
- 支持 `"exact phrase"`。
211+
- 支持 `OR` / `NOT`。
212+
- 支持 prefix,例如 `deploy*`。
213+
- 对破坏 FTS5 语法的字符做 sanitize。
214+
- 查询语法异常不能 crash;应返回空结果或 fallback,并在 debug metadata 中标记。
215+
216+
多词普通 query 默认保留 FTS5 原生 AND 语义。需要 broad recall 时,由用户或模型显式写 `OR`。
217+
122218
跨仓库命令:
123219

124220
```bash
125221
csep recall "query" --global
222+
csep recall --recent --global
126223
```
127224

128225
`--global` 可以跨仓库搜索,但输出里仍必须带 repo / cwd / source metadata,方便模型判断相关性。
129226

227+
`--recent` 返回最近 session metadata 和 preview,不回读完整消息窗口。默认同仓库过滤;配合 `--global` 才展示全局最近 session。
228+
130229
第一版支持这些 CLI flags:
131230

132231
```bash
133232
--global
233+
--recent
134234
--limit 3
135235
--before 3
136236
--after 5
137237
--budget-chars 12000
138238
--message-chars 1200
239+
--tool-message-chars 600
139240
--format markdown|json
241+
--current-session-id <id>
140242
```
141243

244+
能识别当前 session 时,默认排除当前 session。普通手动命令没有 current session id 时,不做复杂运行时推断。
245+
142246
## 输出形态
143247

144248
Markdown 输出要短、清晰,适合模型直接阅读:
@@ -191,8 +295,11 @@ JSON 输出要包含便于调试的结构化 metadata:
191295
- `after = 5`
192296
- `limit = 3`
193297
- `message_chars = 1200`
298+
- `tool_message_chars = 600`
194299
- `budget_chars = 12000`
195300

301+
`limit` 表示最多返回几个 session。每个 session 默认只返回一个最佳命中窗口;如果同一 session 内还有其他命中,JSON 记录 `hit_count` 和其他 hit 的 snippet / index,Markdown 只做短提示。
302+
196303
内容超过预算时,按以下优先级裁剪:
197304

198305
1. 保留命中消息。
@@ -223,6 +330,8 @@ Markdown 或 JSON 渲染前,必须复用现有脱敏规则或等价检查,
223330

224331
第一版只要求输出时脱敏,不要求修改数据库中已存储的 row。
225332

333+
SQLite 中保存原始消息和 `raw_json` 是有意设计:它是本机归档库,不是只读镜像缓存。第一版不提供 purge / 显式删除能力;后续若引入删除能力,必须是显式操作;不会因为源 jsonl 消失而自动删除。
334+
226335
## 兼容性
227336

228337
第一版不删除、不迁移现有 `recall/index.json` records。
@@ -245,6 +354,9 @@ fallback 行为:
245354
- graph retrieval。
246355
- 整段 session transcript 输出。
247356
- scheduler 自动 backfill。
357+
- ingest 失败重试队列。
358+
- purge / 显式删除能力。
359+
- 无参数 `csep session-ingest` 增量扫描。
248360
- 远程同步或共享 recall 存储。
249361

250362
## 风险与兜底
@@ -278,19 +390,29 @@ recall 输出受总预算、单消息预算、命中数量和窗口大小共同
278390
使用小型 fake Codex jsonl fixture,覆盖:
279391

280392
- user、assistant、tool message
393+
- tool call 参数摘要和 tool output
394+
- raw json 保存但不进 FTS
281395
- 缺字段事件
282396
- 缺 cwd / repo metadata
283397
- 重复 ingest 幂等
398+
- 同一 session 多次 Stop hook ingest 不重复
399+
- 原始 jsonl 删除后不自动删除 SQLite 消息
284400
- parser error 写入 receipt 且不中断整批
401+
- `session-archive` 0 条可读 message 时失败
402+
- `session-archive` tool metadata 解析失败但文本入库时只 warning
285403

286404
### SQLite / FTS 测试
287405

288406
使用临时 state dir,验证:
289407

290408
- 导入内容能通过 FTS 搜到
409+
- FTS5 phrase / OR / NOT / prefix 查询可用
410+
- FTS5 语法异常被 sanitize 或软失败
291411
- same-repo 默认过滤生效
292412
- `--global` 能返回跨 repo 结果
293413
- 当前 repo 无命中时,不加 `--global` 不返回其他 repo
414+
- tool output 默认可搜,但排序低于 user / assistant 命中
415+
- `--recent` 默认同仓库,`--recent --global` 跨仓库
294416

295417
### 消息窗口测试
296418

@@ -299,6 +421,7 @@ recall 输出受总预算、单消息预算、命中数量和窗口大小共同
299421
- `before` / `after` 行为
300422
- session 开头和结尾边界
301423
- 同一 session 多个命中
424+
- 同一 session 多个分散命中只返回最佳窗口
302425
- 重叠窗口去重
303426
- tool output 截断
304427

@@ -316,10 +439,15 @@ recall 输出受总预算、单消息预算、命中数量和窗口大小共同
316439

317440
覆盖:
318441

319-
- `csep session-ingest`
442+
- `csep session-archive --from-hook-payload <payload.json>`
443+
- `csep session-archive --transcript-path <path> --cwd <cwd> --session-id <id>`
320444
- `csep session-ingest --backfill`
445+
- `csep session-ingest --backfill --since-days 30`
446+
- `csep recall --recent`
321447
- `csep recall "query"`
322448
- `csep recall "query" --global`
449+
- `session_recall.enabled = false` 时 fallback 到现有 recall search
450+
- `session_recall.stop_hook_archive = false` 时 Stop hook 不归档
323451
- SQLite 不可用时 fallback 到现有 recall search
324452

325453
主要验证命令:

0 commit comments

Comments
 (0)