M4 驱动层 · L11

会话工程:跨天不丢历史、损坏能自愈

45 min · uuid5 钉 transcript + session_heal.py(182行) + 事件重放 · M4 毕业课

🎒 预备知识:完成上一课「L10」(零基础入口:第 0 课)

本课唯一结论:会话持久化的三个坑——绑定过期、传输劈叉、历史损坏—— Easel 各给了一个确定性补丁。这三个坑你自建 agent 时全会遇到。

一、基础:会话持久化要解决什么

先明确"会话"的物理形态:一次对话的全部历史(你说的、模型答的、工具结果)存在 OpenClaw 的 transcript 文件里(jsonl:每行一个 JSON 对象的文本格式,可无限追加——这也是 A3 铁律②的物理基础)。"会话工程"的问题是: 怎么保证第 N 轮的请求,能准确接上第 N-1 轮的文件——跨天、跨设备、跨进程重启。 这本质是个键值问题:用户手里的会话标识(session key,如 web-0928-1830) 怎么稳定映射到磁盘上的 transcript 文件。三个坑都是这个映射在某些条件下会漂移或断裂。

二、坑① 绑定过期 → uuid5 确定性 ID

症状:两天后回来继续聊,agent 完全失忆——新起了一个空会话。
根因(:1803 注释原文大意):OpenClaw 靠 --session-key 解析 transcript, 但 key→文件的绑定(threadBindings)空闲超过约 24h 会过期,过期后同一个 key 被解析成新 transcript。且 HTTP 路径的端点不读 x-openclaw-session-id 头—— 客户端钉了 ID 也没用。
补丁:_openclaw_session_id(sk) = uuid5(命名空间, sk)。 uuid5 是确定性的 UUID——同一个输入永远算出同一个值(区别于 uuid4 的随机)。 把它随每个请求显式传给 gateway,transcript 路径从此只由数学决定,不依赖任何会过期的绑定表。 用纯函数替代有状态映射——分布式系统的经典招(同款:JWT 自包含、内容寻址)。 文件名即身份,身份不再需要"记住"。

三、坑② 传输劈叉 → 钉子文件

症状:同一个会话,有时回答得出来历史,有时完全失忆,随机出现。
根因:HTTP 路径和 CLI 路径写不同的会话文件(:2117-2119 注释: 只带 session-key 的话网关会自己另起 transcript)。如果第一轮走了 HTTP、第二轮回退到 CLI (A13 的回退链!),两轮就写进了两个文件——对话历史劈成两半。
补丁:outputs/_sessions/<sk>.transport 钉子文件——第一轮选定路径时写入, 之后每轮先读钉子再行动:钉子说 http 就必须走 http(哪怕 CLI 更快)。 这是 A13 回退纪律的完整版:回退只发生在新会话,绝不在会话中途。 一致性 > 可用性,在会话这个场景是铁律。

四、坑③ 历史损坏 → session_heal.py 自愈

症状:会话文件还在,但一续聊就 400 "Invalid signature",会话永久打不开。
根因链(docstring 写得很清楚):OpenClaw 存 Claude thinking 块时丢失 signature → Bedrock 类网关回放历史时校验签名失败 → 400。也就是说:存的时候丢了一样东西, 读的时候严格校验,上游 bug 在下游爆雷。
补丁:每轮 spawn 前跑 heal——删掉无签名 thinking 块和空消息。 四个工程品质值得学:幂等(跑一遍跑十遍结果一样,可安全重复执行)、 原子写(写临时文件再 rename,不产生半修复状态)、可选备份(修前留底)、 带 selftest(验证"删的是该删的"——好 thinking 块必须毫发无损)。
哲学:"上游 bug 无法改,就在下游门口修"——理想主义是修上游,现实主义是门口装过滤器, 并留 issue 链路让上游终有一天修好。

五、三坑合观:一张决策表

坑失效条件补丁可迁移模式
绑定过期空闲 >24huuid5 确定性 ID纯函数替代有状态映射
传输劈叉会话中途换路径钉子文件首次决策持久化
历史损坏上游存坏 + 下游严校启动前 heal幂等修复 + 门卫模式

六、常见踩坑

坑 1:信任平台的会话绑定。"平台肯定帮我管好"——24h 过期这种条件不读源码/issue 根本不知道。坑 2:修复脚本无备份直改。heal 删错了内容,会话从"打不开"变"内容缺失"。 坑 3:回退只测新鲜会话。A13 的回退链在"会话中途触发"时才是真考验。

七、检索练习

八、出口检验

设计题:为你的 agent 设计断线恢复方案,必须覆盖——用户 20 分钟后回来怎么接着看? 换台机器怎么续聊?历史文件损坏怎么办?讲给我听。通过即 M4 毕业,进入 M5 执行层。

一手源:scripts/session_heal.py 全文(182 行,含 selftest——看它怎么验证"删的是该删的")。