第 10 章:会话持久化、重放与恢复
这一章有一个最重要的结论:
updates.jsonl是 durable source of truth;chat_history.jsonl是可以由更新重建的派生缓存。
如果反过来理解,compaction、rewind、Leader replay 和崩溃恢复都会显得充满“例外”。
10.1 Session 目录
会话通常位于:
{grok_home}/sessions/{url_encoded_cwd}/{session_id}/子代理显式位于父目录:
{parent_session}/subagents/{child_session_id}/主要文件包括:
| 文件 | 用途 |
|---|---|
updates.jsonl | 持久 ACP/XAI 事件事实源 |
chat_history.jsonl | 发送模型用 conversation 的派生缓存 |
summary.json | session 摘要/列表元数据 |
plan.json、plan_mode.json | 计划与模式状态 |
signals.json | 控制信号 |
goal/state.json | 持久目标状态 |
announcement_state.json | 通知/公告状态 |
rewind_points.jsonl | prompt 与文件检查点 |
workflows/<id>/... | workflow manifest、journal、scratch |
| compaction artifacts | 压缩 checkpoint 与资源 |
确切文件会随 feature 和会话行为增加。
10.2 为什么需要两个日志视图
Updates:发生过什么
包含客户端和 reducer 理解 session 所需的事件,包括模型增量、工具状态、生命周期、计划/目标变化等。
Chat history:下一次模型要看什么
这是规范 conversation 的快速缓存,已经应用:
- tool call/result 结构;
- synthetic message;
- compaction replacement;
- pruning/repair;
- model request 需要的形态。
它可以被重建,所以不需要承担所有审计语义。
10.3 Event Sourcing 的实际形态
Grok Build 并非把所有进程状态都严格 event-source 化,但 session 恢复核心采用 reducer 思路:
好处:
- 派生缓存损坏可以重建;
- compaction 可用 checkpoint 改变 reducer 基线;
- rewind 可用逻辑截断标记重建过去视图;
- 新客户端可重放 UI 状态;
- 协议更新保留比最终聊天文本更丰富的信息。
10.4 Persistence Actor
session/persistence.rs 中的 actor 消费 PersistenceMsg,负责:
- 写 ACP/XAI updates;
- 写/替换 chat history;
- session summary/title;
- model、plan、goal、workflow 状态;
- rewind/compaction checkpoint;
- remote sync;
- flush/shutdown ack。
把落盘串行化有三个收益:
- 同一日志 append 顺序稳定;
- 可以合并连续纯文本 chunk;
- 调用方可选择 fire-and-forget、accepted ack 或 durable ack。
10.5 文本 chunk 合并
模型可能每几个字符产生一条 update。若每条都单独 fsync:
- I/O 开销巨大;
- replay 文件膨胀;
- 客户端重放需要处理海量碎片。
Persistence 对连续、可合并的普通 ACP text chunk 做 coalescing,但不能跨过 tool/lifecycle/identity 边界。合并改变存储粒度,不改变可观察文本顺序。
10.6 JSONL 的崩溃语义
JSONL 很适合 append-only 日志,但进程可能在写一行中途崩溃。storage 的 JSONL 实现:
- append 受锁保护;
- 读取时识别不完整尾行;
- 可忽略或修复 torn trailing line;
- 全量 rewrite 使用 sibling temp + rename;
- durable 模式在 macOS 使用更强的
F_FULLFSYNC。
有效日志中间的损坏与最后一行被撕裂不同:前者不能简单跳过,否则 event sequence 可能产生错误状态。
10.7 Accepted、Committed 与 Indeterminate
异步持久化不是“send 成功 = 已落盘”:
| 状态 | 含义 |
|---|---|
| queued/accepted | actor 收到消息 |
| committed | storage 明确成功 |
| failed before commit | 可知未写入 |
| reply dropped/unknown | 可能写入,也可能未写入 |
最后一种是 indeterminate。调用方不能盲目重试非幂等 append,否则可能重复事件;也不能假设失败并覆盖内存。ChatState tests 专门覆盖这类收敛语义。
10.8 Replay Cursor
更新带 event id/cursor。客户端 load 时:
- 若请求 cursor 存在;
- 且其后的记录都有可界定 event id;
- 则只发送 tail delta;
- 否则发送 full replay。
AvailableCommandsUpdate 等临时/可重新计算内容会从 replay 中丢弃,避免恢复过时命令状态。
10.9 Full Replay 与 Delta Replay
client cursor = e42
updates: e40 e41 e42 e43 e44
└─ delta replay: e43 e44
若 e42 不存在、日志已重写或后续无可靠 id
└─ full replay2
3
4
5
6
7
Full replay 需要明确标记,让 Client 清理/替换已有投影,而不是把整段历史再次 append。
10.10 Replay Buffer
Session 加载期间,reducer 可能产生大量小 chunk。Replay buffer 可合并兼容更新,再发送给 gateway。Leader 又在更外层暂存加载期间的 live update。
两者作用不同:
- Session replay buffer:优化和规范历史输出;
- Leader live buffer:保证 replay 与实时流的全局顺序。
10.11 Compaction Checkpoint
压缩会把原 conversation 全量替换成 compacted history。updates 不能假装之前事件没发生,但派生 chat history 必须从新基线继续。
CompactionCheckpoint 告诉 reducer:
- 旧对话前缀在模型视图中被替换;
- 从这个 compacted snapshot 重新建立 chat state;
- 后续 update 继续应用。
这也是为什么 chat_history.jsonl 被称为缓存而非事实源。
10.12 Rewind Marker
Rewind 不一定物理删除 updates.jsonl 中所有旧字节。逻辑 marker 可以指示 reducer 截断到目标点,再应用之后的新分支。
这种方式保留恢复/审计线索,并避免对 append-only 大日志做危险的原地编辑。文件检查点则由独立 rewind data 处理。
10.13 Session Load 的完整顺序
10.14 Gateway 何时开启
通知可以在没有客户端时继续持久化;gateway 不必一直打开。显式 load/attach 后再连接 gateway,避免后台 session 因没有 UI 而停止记录,也避免向不存在的客户端无限发送。
10.15 Fork 与 Resume
Fork 创建新的 UUIDv7 session,并记录 parent session。它可以:
- 复制某个点的 conversation;
- 复制必要工具/上下文状态;
- 选择新 cwd/worktree;
- 覆盖 model;
- 建立独立持久化目录。
Resume 继续原 session;Fork 产生新身份。即使初始 history 相同,后续 event id、文件检查点和 goal/workflow 所有权也必须分离。
10.16 Remote Sync 的边界
Persistence 还能驱动 remote sync/share,但本地日志仍需具备独立恢复能力。网络同步不能成为本地每个 token 写入的硬同步依赖,否则离线或远端抖动会阻塞 Agent loop。
通常应把:
- 本地 durable commit;
- 远端最终同步;
- UI 实时可见;
视为三个不同确认层级。
10.17 源码阅读卡
| 目的 | 文件 |
|---|---|
| 文件名与 session 目录 | storage/mod.rs |
| JSONL 与 durability | storage/jsonl/mod.rs |
| Persistence actor | persistence.rs |
| Chat persistence | chat_persistence.rs |
| Replay event/reducer | replay_events.rs |
| Replay helper | helpers/replay.rs |
| File rewind | workspace/session/file_state.rs |
10.18 本章结论
恢复能力来自对“事实”和“视图”的分离:
updates 记录发生过什么;reducer 决定当前意味着什么;chat history 缓存模型视图;checkpoint 改变重建基线;cursor 与 Leader barrier 让历史安全切换到实时。
下一章专门解释 compaction 与 pruning:它们如何有损地缩短模型视图,同时不破坏 durable history。