第 41 讲:Thread Store 线程存储——JSONL 是正本,SQLite 是可重建视图

Codex 源码解析 · 第 41 讲

Codex 源码解析系列 第 41 讲:Thread Store 线程存储——JSONL 是正本,SQLite 是可重建视图 基于 OpenAI Codex 源码 · 2026-11-04 💡 本讲一句话: Codex 的对话存在"两层"——JSONL 是唯一正本,SQLite 只是可随时重建的索引视图;ThreadStore trait 把本地/内存后端统一在一个边界后面,每次启动还有一个增量迁移引擎悄悄把旧文件升级成新格式。读完你能看懂 Codex 为什么"写失败不丢数据、读历史不全量回放"。 一、为什么需要一个"存储中立"的边界 上一讲我们拆了 rollout 录制与压缩:对话以 JSONL 事件流的形式落盘。但 core(会话运行时)并不想直接操作文件——它要支持本地 SQLite、内存后端(测试/云端)、未来可能的远端存储。于是 thread-store crate 定义了整条持久化链路的唯一入口: ThreadStore trait。 📄 codex-rs/thread-store/src/store.rs(第 66-76、129-249 行,核心方法摘录) /// Future returned by [`ThreadStore`] operations. // 统一返回类型:把异步操作装箱成 Send future,trait 才能跨后端共享 pub type ThreadStoreFuture<'a, T> = Pin<Box<dyn Future<Output = ThreadStoreResult<T>> + Send + 'a>; // 生命周期 'a 绑定到调用方持有的 store 引用 /// Data a [`ThreadStore`] has already loaded while preparing a thread for resume. // 恢复线程前预加载的数据包 pub struct ThreadStoreResumePreparation { // core 不解析内容,只负责原样传回给 store state: Box<dyn Any + Send>, // 不透明类型:里面装的是实现私有的预加载状态(如已打开的游标) } impl ThreadStoreResumePreparation { // 提供装箱/拆箱两个操作 pub fn new<T: Any + Send>(state: T) -> Self { // 把任意类型塞进不透明包 Self { state: Box::new(state) } // 堆上分配,调用方不再持有原对象 } pub fn downcast<T: Any + Send>(self) -> Result<T, Self> { // 拆箱:类型对就取出,不对就把原包还回去(不丢资源) match self.state.downcast::<T>() { // 运行时类型检查 Ok(state) => Ok(*state), // 类型匹配:交出内部值 Err(state) => Err(Self { state }), // 类型不符:原样包回,调用方可继续尝试别的实现 } } } /// Storage-neutral thread persistence boundary. // 存储中立的线程持久化边界:core 只认这个接口 pub trait ThreadStore: Any + Send + Sync { // 要求 Any:才能 as_any() 下转;Send+Sync:跨线程共享 /// Return this store as [`Any`] for implementation-owned escape hatches. // 暴露具体类型句柄,供 core 调用实现专属能力 fn as_any(&self) -> &dyn Any; // 返回 trait object 引用,不消耗 self /// Creates a new live thread. // 创建一条"活"线程并安装它的写者 fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreFu…

阅读全文:Codex 源码解析 第 41 讲