轻量方案:ProjectVersion + ProjectDraft + ProjectTurn + SandboxSnapshot
Table of Contents
1. 文档目的
本文给出一套比“run + checkpoint + event sourcing + projection”明显更轻的架构方案,目标是在当前项目开发阶段,以更低的理解成本解决以下问题:
- Agent 的 Tool 调用历史需要保存,便于 Continue / 重建
- Agent 每个 QA Turn 需要有明确边界,便于继续和回滚
- Agent 视角下的代码必须始终是最新的
- Code / Character / WorldSettings 需要统一纳入版本管理
- Sandbox 需要通过 Snapshot 快速重建
本方案最终只围绕 4 个核心对象展开:
ProjectVersionProjectDraftProjectTurnSandboxSnapshot
它刻意不引入更重的概念:
- 不做复杂 checkpoint 链
- 不做 event sourcing
- 不做多级 run lineage
- 不做复杂消息 projection 管线
- 不把 ToolCall 单独建成一等主对象
适用前提:
- 当前项目仍在开发中
- 没有线上用户需要兼容
- 允许做较大重构,但希望控制复杂度
2. 核心思想
2.1 只保留两类项目状态
对项目状态只做两层划分:
正式状态:ProjectVersion
表示项目当前稳定、可恢复、可回滚的正式版本。
草稿状态:ProjectDraft
表示项目当前尚未完成、但可以恢复和继续的唯一活跃草稿。
这意味着:
- 正式版本只保留一条当前真相链
- 草稿只保留一个当前未完成状态
- 不做多个活跃草稿并存
- 不做每个细粒度步骤一个 checkpoint
2.2 Turn 是主要运行边界
本方案不再把 Message、ToolCall、Run、Checkpoint 拆成多个独立层,而是统一收敛到:
ProjectTurn
它表示:
一个 QA Turn 中,用户输入、Agent 输出、Tool 调用历史的聚合记录
因此:
- Continue 基于当前 Draft + 最近一个未完成 Turn
- Rollback 基于 Turn 边界
- Tool 调用历史作为 Turn 内部的一部分保存
2.3 Snapshot 只是加速恢复,不是事实来源
SandboxSnapshot 的职责仅限于:
- 加快 sandbox 重建
- 减少 materialize 成本
它不负责定义代码真相。
3. 四个核心对象
3.1 ProjectVersion
定义
项目的正式版本对象。
它表示:
当前项目完整且稳定的正式状态
包含的状态
Code
- 当前代码文件树的 manifest
- 对应 blob 存在 R2
Character
- 当前角色数据快照
WorldSettings
- 当前世界设定快照
建议字段
idproject_idparent_version_idcode_manifest_keycharacters_state_jsonworldsettings_state_jsonsummarycreated_by_turn_idcreated_at
作用
- 项目 head 指向它
- 新 sandbox 默认从它恢复
- rollback 的目标版本是它
- publish 的最终落点是它
设计原则
ProjectVersion只表示正式状态- Agent 运行过程中不直接修改
ProjectVersion - 只有当一轮工作确认完成时,才由 Draft 提升为新的
ProjectVersion
3.2 ProjectDraft
定义
项目当前唯一活跃草稿。
它表示:
当前 Agent 还没完成,但已经产生的最新可恢复状态
为什么必须有它
如果没有 Draft:
- Agent 运行中断后无法恢复
- Agent 视角看不到最新代码
- Character / WorldSettings 的未提交修改无处落地
- Continue 只能依赖临时内存或 Redis
建议字段
idproject_idbase_version_idcode_manifest_keycharacters_state_jsonworldsettings_state_jsonstatusrunninginterruptedpausedpublishing
last_turn_idsnapshot_idpartial_response_json(可选)created_atupdated_at
包含的内容
代码草稿
当前未提交代码的 manifest。
Character 草稿
当前未提交的角色状态。
WorldSettings 草稿
当前未提交的世界设定。
Partial Response
当前 agent 尚未完成的 assistant partial 输出,用于 UI 恢复。
关键语义
一个项目在任意时刻:
- 要么没有 draft
- 要么只有一个 active draft
不支持多个 draft 并存。
这样能显著简化:
- Continue
- Rollback
- Sandbox materialization
- 前端状态判断
3.2.1 Draft 的代码语义
ProjectDraft.code_manifest_key 表示的是:
当前整个项目工作区的草稿文件树
它不是单文件草稿,而是整个代码工作区的草稿快照。
因此一个 Draft 可以自然覆盖:
- 多文件新增
- 多文件修改
- 多文件删除
3.2.2 为什么代码继续用 manifest
轻量方案虽然做了概念收缩,但代码部分仍建议保留:
code_manifest_key- 对应 R2 blob
而不是把整棵文件树直接塞进 PG jsonb。
原因:
- 代码天然是多文件结构
- 单个文件可能较大
- 搜索、diff、bundle、materialize 更适合 manifest 语义
- 更贴近当前仓库已有实现
3.2.3 partial_response_json 的定位
partial_response_json 不是核心业务真相,而是一个可选的 UI 缓存。
它的作用仅限于:
- 页面刷新后还能显示上一轮未完成的 assistant partial 输出
它不应该:
- 进入模型恢复上下文
- 成为 Continue 的核心依据
- 成为项目状态恢复的真相
因此:
- 如果要进一步简化实现,可以第一阶段不做
- 如果要保留更好的 UI 体验,可以保留,但必须明确它是弱状态
3.3 ProjectTurn
定义
一次完整的 QA Turn 聚合记录。
它表示:
用户发起的一轮 agent 工作边界,以及这一轮中的 Agent 输出和 Tool 调用历史
为什么它不能省略
如果只有 Version + Draft + Snapshot:
- 无法清楚表达“本轮 Turn 做了什么”
- 无法按 Turn 回滚
- Continue 时缺少明确的逻辑边界
- Debug 时很难定位是哪一轮引入的问题
所以 ProjectTurn 是必要的最小运行语义对象。
建议字段
idproject_iddraft_idbase_version_idstatusrunningcompletedinterruptedfailedrolled_back
user_message_jsonassistant_message_jsontool_calls_jsonstarted_atended_at
tool_calls_json 的语义
tool_calls_json 保存这一轮 Turn 中的工具调用历史,按顺序排列。
示例结构:
[
{
"seq": 1,
"tool_name": "view_file",
"args": {"file_path": "src/App.tsx"},
"status": "completed",
"result": {"message": "..." },
"started_at": "...",
"finished_at": "..."
},
{
"seq": 2,
"tool_name": "edit_file",
"args": {"file_path": "src/App.tsx"},
"status": "failed",
"result": {"error": "..." },
"started_at": "...",
"finished_at": "..."
}
]
3.3.1 为什么不单独建 AgentToolCall
在当前阶段,ToolCall 的真实需求是:
- 属于某个 Turn
- 参与 Continue / 重建
- 用于 UI 展示和审计
而不是:
- 独立分页查询
- 单独做复杂状态机
- 做全局工具维度分析
所以当前阶段直接把 Tool 调用历史放进 ProjectTurn.tool_calls_json 更轻,也更贴近真实需求。
如果未来工具历史变得特别多,再拆成独立表也不晚。
3.3.2 Tool 历史写入规则
建议在每次工具调用的两个边界更新 Turn:
tool_started
工具开始前先 append / update 一条记录:
tool_nameargsstatus = startedstarted_at
tool_completed / tool_failed
工具结束后更新这条记录:
statusresultfinished_at
这样即使中途断开,也能知道:
- 这轮最后执行到哪个工具
- 哪个工具已完成
- 哪个工具开始了但没完成
3.3.3 Turn 与 Draft 的关系
- Turn 是过程边界
- Draft 是当前状态容器
也就是说:
- 一个 Draft 在生命周期内可以经历多个 Turn
- 一个 Turn 只负责描述“一轮工作做了什么”
- Draft 始终描述“当前项目未完成状态是什么”
Continue 的定义
Continue 不是恢复旧进程,而是:
- 基于当前
ProjectDraft - 新建一个新的
ProjectTurn - 继续使用同一个 draft 状态
这样:
- Turn 边界清晰
- 状态机简单
- 审计也更容易
Rollback 的定义
建议只支持 Turn 粒度回滚,不支持更细粒度恢复。
未完成 Turn 回滚
- 直接丢弃当前 draft
- 回到
base_version_id
已完成 Turn 回滚
- 回滚到上一个
ProjectVersion
3.3.4 Runtime Intent 的处理方式
轻量方案里不再单独维护 runtime_intents_json 作为核心状态。
运行时意图直接作为 tool_calls_json 中的普通工具历史记录,例如:
tool_name = install_packagetool_name = restart_dev_server
这样可以直接复用:
- 顺序
- 参数
- 结果
- 状态
- Continue 上下文
如果未来发现 runtime intent 需要独立状态机,再单独拆表;第一阶段不建议先上。
3.4 SandboxSnapshot
定义
用于快速恢复 sandbox 的快照对象。
它表示:
某个 ProjectVersion 或 ProjectDraft 的 sandbox 可恢复副本
建议字段
idproject_idsource_kindversiondraft
source_ide2b_snapshot_ref或bundle_keystatuscreated_at
作用
- 项目打开时快速恢复正式版本
- interrupted draft 继续时快速恢复草稿
- 降低大工作区 materialize 成本
重要限制
SandboxSnapshot 只是缓存和加速器,不是代码真相。
如果 Snapshot 丢失:
- 仍然可以从
ProjectVersion/ProjectDraft重建
4. 组件职责分配
4.1 PostgreSQL
负责保存结构化状态:
ProjectVersionProjectDraftProjectTurnSandboxSnapshot元数据SandboxSession元数据
PG 是项目和运行状态的主控制面。
4.2 R2
负责保存实际内容:
- code manifest
- code blob
- snapshot bundle
R2 用于内容持久化,不直接提供活跃工作区文件系统。
说明:
- Code 继续使用
manifest + blob - Character / WorldSettings 在轻量方案中直接使用
jsonb
4.3 Redis
只保留瞬态能力:
- SSE buffer
- lock
- session heartbeat
Redis 不能作为 Continue 或版本恢复的真相来源。
4.4 E2B
负责:
- 执行 agent tool 需要的命令
- 运行 dev server
- 提供 preview
- 恢复 sandbox snapshot
E2B 是执行环境,不是代码真相。
5. 代码读取与 Agent 视角
5.1 最重要的原则
Agent 视角下的代码必须始终是最新的
因此:
view_filesearch_codefind_files
都必须优先读取 ProjectDraft,而不是直接读 sandbox。
5.2 读取优先级
如果项目存在 active draft
所有只读文件工具都读取:
ProjectDraft.code_manifest_key
如果项目不存在 draft
所有只读文件工具都读取:
ProjectVersion.code_manifest_key
5.3 Sandbox 的定位
sandbox 不是 agent 的逻辑视图来源,而是执行副本。
只有在需要执行命令、运行 preview、安装依赖时,才需要把 draft 或 version materialize 到 sandbox。
6. 典型工作流
6.1 开始一个新的 Turn
- 用户发起新的消息
- 如果项目没有 active draft:
- 从
ProjectVersion派生ProjectDraft
- 从
- 创建新的
ProjectTurn - Agent 开始工作,所有修改都写到
ProjectDraft
6.2 Tool 调用
每次 tool 调用时:
- 在
ProjectTurn.tool_calls_json中记录tool_started - 执行工具逻辑
- 工具结果写回
ProjectDraft - 更新
ProjectTurn.tool_calls_json中对应记录为completed/failed
如果工具本质上代表运行时意图,例如:
install_packagerestart_dev_server
则仍然作为普通 tool history 处理,不再额外维护一份 draft 级 intents JSON。
6.3 Turn 中断
如果运行中断:
ProjectTurn.status = interruptedProjectDraft.status = interrupted- partial response 可选保留在 draft 中
- tool call 历史保留在 turn 中
此时项目仍然保留一个可恢复的 active draft。
6.4 Continue
Continue 的实现:
- 找到当前项目的
ProjectDraft - 创建新的
ProjectTurn - Agent 读取当前 draft 的最新代码
- Agent 可参考上一轮 Turn 的工具历史和对话历史
- 继续工作
Continue 不等于恢复旧进程,而是:
基于当前 Draft,新开一个 Turn
6.5 Publish
如果当前工作完成,需要正式提交:
- 从
ProjectDraft生成新的ProjectVersion - 更新
project.current_version_id - 清除
active_draft_id - 可异步生成对应的
SandboxSnapshot
6.6 Rollback
回滚未完成 Draft
- 删除 active draft
- 回到对应的 base version
回滚已提交版本
- 把项目 head 切回某个旧
ProjectVersion
7. Character 与 WorldSettings 的版本管理
7.1 为什么要和 Code 一起管理
你的需求已经明确:
- Turn 相关版本管理不仅有代码
- 还包括 character、worldsettings 等
因此不能继续把它们当作完全独立的领域单独处理。
7.2 在 ProjectVersion 中的表现
正式版本中保存:
characters_state_jsonworldsettings_state_json
这样回滚一个版本时,三者一起回滚:
- Code
- Character
- WorldSettings
7.3 在 ProjectDraft 中的表现
草稿中同样保存:
characters_state_jsonworldsettings_state_json
这样一次 Turn 修改任意一种资源,都能统一纳入草稿语义。
8. 为什么这个方案比重方案更轻
8.1 少了什么
它刻意不做:
- 复杂 checkpoint 体系
- 每次副作用一个恢复点
- 事件溯源
- projection pipeline
- 多层 revision lineage
- 独立 ToolCall 主表
8.2 保留了什么
它保留了当前最关键的业务能力:
- Turn 历史
- Tool 调用与结果历史
- Draft 恢复
- 正式 Version
- Snapshot 快速恢复
8.3 代价
它的限制也很明确:
- 不适合多个 agent 并发改同一项目
- 不支持极细粒度中间恢复
- 不适合非常复杂的运行期事件审计
- 如果单个 Turn 的 tool 历史过大,后续可能需要把
tool_calls_json拆表
但对当前项目阶段,这些限制通常是可接受的。
9. 收敛后的推荐结构
基于当前讨论,推荐进一步收敛为:
ProjectVersion
code_manifest_keycharacters_state_jsonworldsettings_state_json
ProjectDraft
code_manifest_keycharacters_state_jsonworldsettings_state_jsonpartial_response_json(可选)
ProjectTurn
user_message_jsonassistant_message_jsontool_calls_jsonstatus
SandboxSnapshot
- 仅做恢复加速器
换句话说:
- 去掉
runtime_intents_json - 去掉独立
AgentToolCall主对象 - 保留
partial_response_json但降级为可选 UI 缓存 - Character / WorldSettings 改用
jsonb - Code 继续使用
manifest + blob - Tool 历史并入
ProjectTurn.tool_calls_json
10. 推荐实现顺序
Phase 1
先落:
ProjectDraftProjectTurn- 统一读取 Draft 的代码视图
目标:
- 先解决 Continue
- 先解决 Agent 视角代码一致性
Phase 2
再落:
ProjectVersion- Draft -> Version 的 publish
- Version rollback
目标:
- 形成稳定的正式版本体系
Phase 3
最后补:
SandboxSnapshot- sandbox 快速恢复优化
目标:
- 提升恢复效率,而不是改变真相结构
11. 最终结论
轻量方案的核心不是减少功能,而是减少概念层级。
对当前项目来说,更合适的不是重型的:
- run
- checkpoint
- event
- projection
- revision tree
而是更直接的:
ProjectVersion:正式状态ProjectDraft:当前草稿ProjectTurn:一轮 QA Turn 的完整记录SandboxSnapshot:sandbox 恢复加速器
一句话概括:
用 Draft 表达“当前未完成状态”,用 Turn 表达“本轮工作边界 + Agent 输出 + Tool 历史”,用 Version 表达“正式版本”,用 Snapshot 表达“恢复加速”。
这套方案比重方案更容易理解,也更适合当前阶段快速重构落地。