雨天小六

读懂 Codex(8.32):State DB 中 Thread、Goal、Memory 和 Log 的职责

· 更新于 2026-08-03 · 专栏:读懂 Codex

#Codex#Agent Runtime#持久化#Rollout#软件架构

“State DB”不是一张万能表:StateRuntime 管理五个 SQLite 文件,把 Thread 元数据、Log、Goal、Memory 和可重建 Paginated History 隔离,以降低写锁竞争并明确恢复等级。

本节解决的不是“把对象存一下”这种抽象问题,而是把问题限定在:梳理各数据库文件和 API 职责,不展开 Goal/Memory 的业务算法。输入:Thread metadata/edges、tracing logs、goal state、memory jobs 和 history projection;状态所有者:StateRuntime、GoalStore、MemoryStore 与独立 history pool;成功结果:面向不同消费者的持久状态与查询接口。先把这些边界钉住,后面的顺序、失败和恢复才不会混成一句“持久化失败后重试”。

状态边界

问题本节答案
输入Thread metadata/edges、tracing logs、goal state、memory jobs 和 history projection
状态所有者StateRuntime、GoalStore、MemoryStore 与独立 history pool
成功产物面向不同消费者的持久状态与查询接口
研究范围梳理各数据库文件和 API 职责,不展开 Goal/Memory 的业务算法

正常路径:先看顺序点

State DB 中 Thread、Goal、Memory 和 Log 的职责的正常路径时序图,展示创建 sqlite_home、分别打开并迁移各数据库、初始化 backfill/timestamp 状态、构造专用 Store/Pool、关闭时逐个 drain pool
图 8.32-1:创建 sqlite_home → 分别打开并迁移各数据库 → 初始化 backfill/timestamp 状态 → 构造专用 Store/Pool → 关闭时逐个 drain pool。这张图标出本节的实际顺序点与状态所有者。

这条路径可以压缩成五步:

  1. 创建 sqlite_home。
  2. 分别打开并迁移各数据库。
  3. 初始化 backfill/timestamp 状态。
  4. 构造专用 Store/Pool。
  5. 关闭时逐个 drain pool。

图中的箭头不是“可能调用”的依赖图,而是源码中决定可见性和所有权转移的先后关系。前一步没有确认时,后一步不能替它作出更强的成功承诺。

源码机制拆解

Primary state 保存 Thread 元数据和边

threads、thread sections、dynamic tools、backfill state、thread_spawn_edges、remote-control enrollment 等位于主 state DB。它支持列表、标题/preview/cwd/git 更新和 AgentGraph adapter。

Logs 使用独立数据库

高频 tracing/log entries 通过 LogDbLayer 批量写入,query_logs/feedback 导出由 logs pool 服务。独立文件避免日志写入长期占用 Thread metadata 的 SQLite writer slot。

Goals 独立持有状态机与用量

GoalStore 提供 insert/update/pause/usage-limit/delete、continuation deferral 和 usage accounting。Goal 是用户请求的长期任务状态,不是从聊天 Rollout 自动重建的简单投影。

Memories 有独立 job/output 生命周期

MemoryStore 管理 stage1 output、job claim/heartbeat/success/failure、global phase2 consolidation 和污染标记,同时可引用主 state 中的 Thread metadata。它有自己的清理入口。

Thread History 明确标注 rebuildable

Paginated Turn/Item 数据库通过 open_thread_history_db 单独打开,不常驻 StateRuntime 字段;它由 JSONL 投影生成,可以落后或重建,与主 Thread metadata 的可修复程度不同。

Python 风格伪代码

下面的伪代码只保留设计职责、状态和失败顺序;它不逐行翻译 Rust,也不借 Python 语法虚构源码中不存在的事务:

async def init_state_runtime(sqlite_home, default_provider):
    ensure_directory(sqlite_home)
    state_pool = await open_and_migrate('state.sqlite')
    try:
        logs_pool = await open_and_migrate('logs.sqlite')
        goals_pool = await open_and_migrate('goals.sqlite')
        memories_pool = await open_and_migrate('memories.sqlite')
    except BaseException:
        await close_all_opened_pools()
        raise

    await ensure_backfill_singleton(state_pool)
    updated_clock, recency_clock = await read_max_thread_timestamps(state_pool)
    return StateRuntime(
        thread_metadata=state_pool,
        logs=logs_pool,
        goals=GoalStore(goals_pool),
        memories=MemoryStore(memories_pool, state_pool),
        monotonic_thread_clocks=(updated_clock, recency_clock),
    )

async def open_rebuildable_history(runtime):
    return await open_and_migrate(runtime.sqlite.thread_history_db_path)

阅读时要特别看三处:哪个对象拥有可变状态,哪一个 await 是可观察屏障,以及失败后保留的是已提交前缀、未提交后缀,还是完全独立的外部副作用。

失败、取消与恢复

State DB 中 Thread、Goal、Memory 和 Log 的职责的失败路径图,区分后一个 DB 初始化失败、把 history projection 当唯一历史、Logs 与 metadata 共用热点 writer、Goal/Memory 被误当 Rollout 投影
图 8.32-2:同一操作在不同故障点留下的状态不同,恢复责任必须回到拥有该状态的组件。
故障点已留下的状态可观察结果恢复责任
后一个 DB 初始化失败前面 pools 已打开Runtime 不可用按已打开顺序全部 close
把 history projection 当唯一历史DB 可删除/落后Resume 语义丢失仍从 Rollout JSONL 重建
Logs 与 metadata 共用热点 writer高频日志竞争前台列表更新延迟独立 logs DB
Goal/Memory 被误当 Rollout 投影包含独立 job/state machine扫描聊天无法恢复使用各自 Store 的耐久协议

这里没有统一的“回滚一切”。内存状态、日志行、SQLite 投影、父子拓扑和工具造成的文件/网络变化分别有自己的提交点。恢复代码只能根据已经存在的权威证据继续,不能用较弱的投影替较强的事实背书。

必须保持的不变量

  • 五类数据库路径由同一 SqliteConfig 解析
  • 初始化失败不泄漏已打开 pool
  • Paginated history 标明可重建且不领先 JSONL
  • Goal/Memory/Log 的写模型不由 Thread Rollback 自动撤销

这些不变量比“最终能 Resume”更严格:正常路径要成立,Writer 竞争、任务取消、坏尾行、投影落后和旧格式兼容时也必须成立。

设计取舍

多数据库增加迁移、备份和诊断文件数量,却隔离高频写锁与不同数据保留策略,比一份巨型 SQLite 更可控。

源码可以直接证明字段、分支、调用顺序和测试期望;“为什么这样设计”的表述是基于这些事实作出的工程归纳,不把它包装成未公开的产品承诺。

Mini Codex 复刻

至少分 event-log authority 与 query DB;若加入 Goal/Memory,把它们做成独立 Store,不塞进 conversation reducer。

复刻时先验证协议不变量,再补性能优化。一个能在故障注入下说明“留下了什么”的小实现,比一个只在正常路径调用 save() 的演示更接近真实 Runtime。

源码导航

相邻测试也很重要:

本节结论

“State DB”不是一张万能表:StateRuntime 管理五个 SQLite 文件,把 Thread 元数据、Log、Goal、Memory 和可重建 Paginated History 隔离,以降低写锁竞争并明确恢复等级。

阅读导航

上一节:8.31 · 下一节:8.33

评论


← 返回文章列表