检查点与分支谱系
学习目标
理解 CrewAI 的检查点系统如何实现事件驱动触发、JSON/SQLite 双后端存储、分支谱系追踪和版本迁移。
项目实践
RuntimeState 运行时状态
RuntimeState(RootModel) 管理所有活动实体:
_provider: BaseProvider— 检查点存储后端_event_record: EventRecord— 事件记录图- 谱系追踪:
_checkpoint_id、_parent_id、_branch - 方法:
checkpoint()、acheckpoint()、fork()、from_checkpoint()、afrom_checkpoint() - 基于版本的迁移系统(
_migrate())用于向后兼容
EventRecord 事件记录图
EventNode 包装事件,带有类型边(parent/child/trigger/triggered_by/next/previous/started/completed_by)。
EventRecord 提供:
- O(1) 节点查找
- BFS 后代遍历
- 根节点查找
- 通过
RWLock实现线程安全
CheckpointConfig
# 伪代码CheckpointConfig( location="./.checkpoints", on_events=["task_completed", "crew_finished", ...], # 100+ 事件类型 provider=JsonProvider() | SqliteProvider(), max_checkpoints=100, # 修剪上限 restore_from="checkpoint_id", # 从检查点恢复)JSON Provider
文件系统 JSON 存储:
- 文件名格式:
{ts}_{uuid8}_p-{parent}.json位于location/branch/下 - 分支目录验证防止路径逃逸
- 按文件修改时间修剪
- 通过
aiofiles实现异步支持
SQLite Provider
SQLite 存储:
- 表结构:
checkpoints(id TEXT PRIMARY KEY, created_at TEXT, parent_id TEXT, branch TEXT, data JSONB) - WAL 日志模式支持并发读取
- 位置格式:
db_path#checkpoint_id - 通过
rowid排序修剪
CheckpointListener
- 延迟处理程序注册(仅在第一个 CheckpointConfig 解析时)
_find_checkpoint()遍历关系链:Task → Agent → Crew_do_checkpoint()序列化状态、通过 provider 写入、发射事件- 每次写入后修剪(如果配置了
max_checkpoints)
问题与规避
max_checkpoints 修剪可能删除需要的历史
- 修剪策略基于文件修改时间或 rowid,可能删除你需要回退的检查点
- 对策:对需要保留的检查点使用
restore_from明确引用,或设置更大的max_checkpoints
分支目录路径逃逸
- 如果 branch 名称包含
..,可能写入到非预期目录 - 对策:JSON Provider 的分支目录验证拒绝包含
..的路径
版本迁移的兼容性
- 旧版本检查点的序列化格式可能与新版本不兼容
- 对策:
_migrate()方法在从旧检查点恢复时自动执行格式转换
设计取舍
JSON vs SQLite 双后端
| 维度 | JSON | SQLite |
|---|---|---|
| 可读性 | 高(纯文本) | 低(需要工具查询) |
| 查询能力 | 差 | 优(SQL) |
| 并发 | 中等(文件锁) | 高(WAL 模式) |
| 适用场景 | 调试、审计 | 生产、频繁读写 |
CrewAI 同时支持两种后端,用户根据场景选择。
事件驱动触发 vs 定时触发
- 事件驱动:在特定事件(如 task_completed)时触发检查点
- 优势:精确控制何时保存,避免不必要的检查点
- 代价:需要在 CheckpointConfig 中显式声明 100+ 事件类型
参考来源
- 源码验证:
lib/crewai/src/crewai/state/runtime.py - 源码验证:
lib/crewai/src/crewai/state/checkpoint_config.py - 源码验证:
lib/crewai/src/crewai/state/provider/json_provider.py - 源码验证:
lib/crewai/src/crewai/state/provider/sqlite_provider.py