45 min · uuid5 钉 transcript + session_heal.py(182行) + 事件重放 · M4 毕业课
🎒 预备知识:完成上一课「L10」(零基础入口:第 0 课)
先明确"会话"的物理形态:一次对话的全部历史(你说的、模型答的、工具结果)存在
OpenClaw 的 transcript 文件里(jsonl:每行一个 JSON 对象的文本格式,可无限追加——这也是 A3 铁律②的物理基础)。"会话工程"的问题是:
怎么保证第 N 轮的请求,能准确接上第 N-1 轮的文件——跨天、跨设备、跨进程重启。
这本质是个键值问题:用户手里的会话标识(session key,如 web-0928-1830)
怎么稳定映射到磁盘上的 transcript 文件。三个坑都是这个映射在某些条件下会漂移或断裂。
症状:两天后回来继续聊,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 回退纪律的完整版:回退只发生在新会话,绝不在会话中途。
一致性 > 可用性,在会话这个场景是铁律。
症状:会话文件还在,但一续聊就 400 "Invalid signature",会话永久打不开。
根因链(docstring 写得很清楚):OpenClaw 存 Claude thinking 块时丢失 signature →
Bedrock 类网关回放历史时校验签名失败 → 400。也就是说:存的时候丢了一样东西,
读的时候严格校验,上游 bug 在下游爆雷。
补丁:每轮 spawn 前跑 heal——删掉无签名 thinking 块和空消息。
四个工程品质值得学:幂等(跑一遍跑十遍结果一样,可安全重复执行)、
原子写(写临时文件再 rename,不产生半修复状态)、可选备份(修前留底)、
带 selftest(验证"删的是该删的"——好 thinking 块必须毫发无损)。
哲学:"上游 bug 无法改,就在下游门口修"——理想主义是修上游,现实主义是门口装过滤器,
并留 issue 链路让上游终有一天修好。
| 坑 | 失效条件 | 补丁 | 可迁移模式 |
|---|---|---|---|
| 绑定过期 | 空闲 >24h | uuid5 确定性 ID | 纯函数替代有状态映射 |
| 传输劈叉 | 会话中途换路径 | 钉子文件 | 首次决策持久化 |
| 历史损坏 | 上游存坏 + 下游严校 | 启动前 heal | 幂等修复 + 门卫模式 |
坑 1:信任平台的会话绑定。"平台肯定帮我管好"——24h 过期这种条件不读源码/issue 根本不知道。坑 2:修复脚本无备份直改。heal 删错了内容,会话从"打不开"变"内容缺失"。 坑 3:回退只测新鲜会话。A13 的回退链在"会话中途触发"时才是真考验。
设计题:为你的 agent 设计断线恢复方案,必须覆盖——用户 20 分钟后回来怎么接着看? 换台机器怎么续聊?历史文件损坏怎么办?讲给我听。通过即 M4 毕业,进入 M5 执行层。
一手源:scripts/session_heal.py 全文(182 行,含 selftest——看它怎么验证"删的是该删的")。