Skip to content

feat(checkpoint): 统一 Checkpoint、代码回退与运行恢复#550

Merged
pionxe merged 17 commits into1024XEngineer:mainfrom
phantom5099:file-checkpoint
May 4, 2026
Merged

feat(checkpoint): 统一 Checkpoint、代码回退与运行恢复#550
pionxe merged 17 commits into1024XEngineer:mainfrom
phantom5099:file-checkpoint

Conversation

@phantom5099
Copy link
Copy Markdown
Collaborator

@phantom5099 phantom5099 commented May 4, 2026

问题

NeoCode 缺乏代码回退、会话回滚与运行恢复的统一机制:

  1. 代码状态不可独立回退 — agent 修改的代码无法精确恢复,用户只能依赖自己的 Git 或手动撤销。
  2. 代码与会话没有绑定恢复点 — session 中的 transcript、task/todo、plan 没有和代码基线绑定,回退后状态错位。
  3. 运行恢复依赖猜测 — 进程中断后无法稳定回答"当前 run 执行到哪一步"。
  4. bash 工具修改缺乏追踪sed -imvrm 等 shell 命令的文件副作用此前未纳入回退范围。
  5. 恢复状态提交边界分散 — transcript、tool result、task/todo、checkpoint 分散写入,没有统一原子提交。
    关联issue:feat(session):代码、上下文回退 #521

方案

采用三层恢复模型,代码快照后端选用 Per-Edit Snapshot Store(纯文件版本化增量历史),不依赖 Git。

三层模型

职责 实现
代码回退 保存/恢复被 agent 触碰的文件 PerEditSnapshotStore:按 pathHash@vN.bin/.meta 存储版本,checkpoint 只存 (pathHash -> version) 映射
会话回退 保存/恢复 transcript/todo/plan/skills SessionCheckpoint:SQLite 事务内写入 HeadJSON + MessagesJSON
运行恢复 记录/恢复运行中断位置 ResumeCheckpoint:SQLite,每个 session 仅保留最新一条

代码快照关键设计

恢复算法:对 checkpoint 中的每个 (pathHash, v_A),查找版本链中下一个版本 v_next,把 v_next.bin 写回 workdir(Existed=false 时删除)。不在 checkpoint 映射中的文件保持不变。

捕获机制

  • 写工具(filesystem_write_file / edit / move / copy / delete / create_dir / remove_dir)在调用前即时 CapturePreWrite
  • bash 工具通过 BashLikelyWritesFiles 启发式识别写命令,提取源文件路径批量 capture;同时记录 fingerprint,执行后对比发现未覆盖变更时 emit EventBashSideEffect

Checkpoint 创建时机

  1. 每轮 turn 开始时pre_write):固化上一轮 pending capture。有代码快照则创建完整 checkpoint;无 pending 则退化为 session-only checkpoint(CodeCheckpointRef 为空)。
  2. 工具执行完成后end_of_turn):若本轮有 workspace write,再次 Finalize 固化。
  3. compact 前compact):创建 session-only checkpoint(有 pending writes 则同时固化代码快照)。
  4. restore 前pre_restore_guard):自动创建 guard 快照,供 undo 使用。

CheckpointReasonPlanModeCheckpointReasonManual 已预留,runtime 尚未触发。

Restore 流程

  1. 校验 checkpoint 状态(available + restorable + session 匹配)。
  2. 不做冲突检测(per-edit 只还原 snapshot 覆盖的文件,其余文件保持不变)。
  3. 创建 pre_restore_guard 快照。
  4. 代码恢复:per-edit store Restore 还原 snapshot 覆盖的文件。
  5. 会话恢复:SQLite 事务内截断 messages + 恢复 session head。
  6. 标记目标之后的 checkpoint 为 restored
  7. 刷新 runtime 快照(删除缓存,使下次读取从 DB 重新加载)。

跨存储原子性

file-history 与 SQLite 是两个独立存储,采用"先写 file-history -> 再写 DB -> 失败则补偿"的两阶段协议。启动时扫描 status=creating 的残留记录,有 session_checkpoint_ref 则更新为 available,无则删除孤儿记录。

保留策略

  • 每个 session 最近 10 个自动 checkpoint 可恢复。
  • manual checkpoint 和 pre_restore_guard 始终可恢复(manual 已预留,runtime 未触发)。
  • 超出窗口的旧自动 checkpoint 标记为 pruned,关联 SessionCheckpoint 删除,file-history 保留。

不使用 Git 影子仓库:

NeoCode 选择基于文件的 Per-Edit Snapshot Store 而非 Git影子仓库,主要出于以下技术考量:

  1. 架构边界更清晰:影子仓库会在 Runtime与文件系统之间引入一个外部 Git进程作为“隐式中间层”,破坏了主链路“用户输入 → Gateway →Runtime → Tools”的直接性。Per-Edit 方案把版本控制内聚在Runtime 内部,不需要 fork/exec git。
  2. 性能与频率匹配:Agent 的 checkpoint粒度是“每轮工具调用”,触发频率远高于人类提交代码。Git 的 add → write-tree → commit 序列在 Windows 上尤其昂贵;而 Per-Edit只做必要的文件复制和一次 JSON 元数据写入。
  3. 增量存储更精确:Git 以 blob 为单位存储对象,一次 checkpoint会生成 tree/commit 等额外对象;Per-Edit 只给实际被修改的文件生成新版本(pathHash@vN.bin),未变更的文件零成本。
  4. 跨平台一致性:Git 在 Windows上的路径处理、换行符转换、文件模式、符号链接等行为与 Unix存在差异,会增加测试和排障负担。纯 Go文件操作在各个平台表现一致。
  5. 恢复语义更灵活:Per-Edit的“下一版本即修改后状态”对偶天然支持细粒度回滚到任意checkpoint,无需维护分支、reflog 或 stash。删除/重命名/空文件等边界情况在文件级元数据中显式表达(Existed bool、IsDirbool),比 Git 的 tree diff 更直观。
  6. 应对用户缺少git环境: 有些用户缺少git环境,不适合使用影子仓库

增加系统工具

"增加工具种类"有两个层面的必要性:Agent 本身的能力完整性,以及 Checkpoint 精确恢复的数据完整性。

  1. Agent 编码场景需要更完整的文件操作

仅靠 read_file / write_file / edit 无法覆盖真实开发中的常见操作:

  • 复制文件(copy_file):复用配置、模板、测试用例
  • 移动/重命名(move_file):重构时重命名文件或调整目录结构
  • 创建目录(create_dir):新建包、模块目录
  • 删除文件(delete_file):清理废弃代码
  • 删除目录(remove_dir):移除整个模块或构建产物

如果缺少这些专用工具,Agent 只能通过 bash 执行 cp / mv / mkdir / rm 等命令。但 bash 的问题是:

  • 路径解析不可靠:相对路径、空格、特殊字符容易出错
  • 错误反馈不结构化:返回的是 stderr 文本,难以被 Runtime 解析
  • WorkspaceWrite 事实难以判定:Runtime 只能启发式猜测 bash 是否写了文件(BashLikelyWritesFiles +
    SourceFilesInWorkdir),经常漏捕或误捕

专用工具让 Agent 能直接表达意图,同时返回结构化的 paths / bytes / overwrite 等元数据。

  1. Checkpoint 恢复需要针对不同工具采取不同捕获策略
    如果不区分工具种类,统一按"单路径 pre-write capture"处理:
  • delete_file 删除后没有 post-delete 版本 → restore 到该 checkpoint 时文件不会被删除
  • remove_dir 只 capture 了目录本身,子文件从未被记录 → restore 后目录可能被重建,但子文件全部丢失

正是为了支持精确的目录树恢复,我们才必须识别 remove_dir 并做递归 pre-capture + CapturePostDelete。

  1. 安全与权限边界

专用工具在 Execute 内部会做统一的路径校验(resolvePath / tools.ResolveWorkspaceTarget),确保操作不逃出 Workspace。bash
命令很难做这种细粒度控制。新增的工具越多,Agent 越不需要依赖 bash,整体安全性越高。

修改范围

核心新增

  • internal/checkpoint/per_edit_snapshot.goPerEditSnapshotStore(Capture / Finalize / Restore / Diff / ChangedFiles)
  • internal/checkpoint/fingerprint.go — workdir 指纹扫描与 diff
  • internal/checkpoint/bash_capture.go — bash 启发式写命令识别与路径提取
  • internal/runtime/checkpoint_restore.go — Restore / Undo / Guard / CheckpointDiff
  • internal/runtime/checkpoint_gate.go — start_of_turn / end_of_turn / compact checkpoint 创建
  • internal/runtime/checkpoint_resume.goupdateResumeCheckpoint
  • internal/runtime/file_snapshot.go — 工具执行前后快照与 unified diff 计算

集成修改

  • internal/runtime/run.go — turn 循环插入 checkpoint 创建、工具 diff emit、resume checkpoint 更新
  • internal/runtime/toolexec.go — 写工具前置 capture + bash 启发式 capture + fingerprint 兜底 + diff 计算
  • internal/runtime/events.go — 新增 EventCheckpointCreated / EventCheckpointWarning / EventCheckpointRestored / EventCheckpointUndoRestore / EventToolDiff / EventBashSideEffect
  • internal/session/sqlite_store.go — schema v6 迁移,新增三张 checkpoint 表
  • internal/app/bootstrap.go — 注入 SQLiteCheckpointStore + PerEditSnapshotStore,启动时补偿恢复
  • internal/gateway/ — 注册 checkpoint 查询 / restore / undo / diff 的 RPC handler
  • internal/tools/filesystem/ — 新增 copy_file / create_dir / delete_file / move_file / remove_dir

TUI 接入

1. 实时观测本轮修改

订阅 runtime 事件流:

  • EventToolDiff — 每个写工具执行后,展示文件变更和 unified diff。
  • EventBashSideEffect — bash 产生未捕获变更时,提示未覆盖文件路径。
  • EventCheckpointCreated — 可选,用于展示 checkpoint 时间线。

ToolDiffPayload 同时提供单文件兼容字段(FilePath / Diff / WasNew)和多文件字段(Files + Diffs),TUI 按 Files 长度判断场景。

2. Checkpoint 列表与 Diff 预览

通过 Gateway RPC:

  • ListCheckpoints(sessionID) — 获取可恢复 checkpoint 列表,按 Reason 分组展示,标记包含代码快照的项(CodeCheckpointRef 非空)。
  • CheckpointDiff(sessionID, checkpointID) — 查询目标 checkpoint 相对于上一个代码 checkpoint 的端到端差异,返回 Files(Added/Deleted/Modified 分类)和 Patch(unified diff)。建议在 restore 确认弹窗中展示,帮助用户决策。

3. Restore 与 Undo

通过 Gateway RPC:

  • checkpoint_restore — 输入 session_id + checkpoint_id + force
  • checkpoint_undo_restore — 输入 session_id,恢复到最近一次 restore 前的 guard 点。

Restore 成功后 TUI 会收到 EventCheckpointRestored,应刷新消息列表、todo、plan、以及已打开的文件内容。

4. 运行时快照刷新(自动)

Restore 成功后 runtimeSnapshots 缓存被删除,TUI 下次调用 GetRuntimeSnapshot 时自动从 DB 重新加载恢复后的状态,无需额外处理。

预期收益

  1. 每轮可回退 — 无论是否有工具调用,用户均可回退到上一轮开始前的代码和会话状态。
  2. 完整三层恢复 — 代码快照 + 会话快照 + 运行位置统一恢复,避免回退后状态错位。
  3. bash 修改可追溯 — 启发式识别 + fingerprint 兜底,将 shell 副作用纳入回退范围。
  4. 实时变更观测EventToolDiffCheckpointDiff 使用户直观看到每轮改了什么。
  5. 安全恢复 — restore 前自动创建 guard 快照,支持一键 undo。
  6. 运行断点续传ResumeCheckpoint 记录精确 phase/turn,进程重启后不再猜测。
  7. 无外部依赖 — 纯文件实现,不依赖 Git 二进制,无降级模式复杂度。

风险

  1. restore 不做冲突检测:若用户手动修改了 agent 之前碰过的同一文件,restore 会静默覆盖。TUI 应在 restore 确认弹窗中提示此风险。
  2. plan_mode / manual 预留未实现:类型层已定义,runtime 未触发。
  3. TranscriptRevision 未填充:schema 已预留,运行时未赋值,resume 一致性校验尚未启用。
  4. file-history 无自动 GCpruned 不清理 .bin/.meta,长期运行可能磁盘膨胀,需后续引入独立 GC。

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@phantom5099
Copy link
Copy Markdown
Collaborator Author

/code 修复编译报错:ok neo-code/cmd/neocode-gateway 0.100s coverage: 100.0% of statements

neo-code/internal/gateway/adapters/urlscheme [neo-code/internal/gateway/adapters/urlscheme.test]

Error: internal/gateway/adapters/urlscheme/dispatcher_integration_unix_test.go:37:40: cannot use runtimeStub (variable of type *urlschemeIntegrationRuntimeStub) as gateway.RuntimePort value in argument to server.Serve: *urlschemeIntegrationRuntimeStub does not implement gateway.RuntimePort (missing method CheckpointDiff)
FAIL neo-code/internal/gateway/adapters/urlscheme [build failed]
并提高测试覆盖率

Copy link
Copy Markdown

@fennoai fennoai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 regressions that look user-visible in the new checkpoint/tooling flow.

Comment thread internal/tools/filesystem/copy_file.go Outdated
Comment thread internal/checkpoint/per_edit_snapshot.go
Comment thread internal/runtime/run.go
@fennoai
Copy link
Copy Markdown

fennoai Bot commented May 4, 2026

完成

已修复 internal/gateway/adapters/urlscheme/dispatcher_integration_unix_test.gourlschemeIntegrationRuntimeStub 缺失 CheckpointDiff 导致的 gateway.RuntimePort 编译错误,并补齐两处测试覆盖:

  • internal/cli/gateway_runtime_bridge_test.go:新增 CheckpointDiff 的参数转发与结果映射断言。
  • internal/gateway/bootstrap_test.go:新增 checkpoint_diff frame handler 成功路径测试,以及 decodeCheckpointDiffPayload 分支测试。

已执行:

  • go test ./internal/gateway/... ./internal/cli/...
  • go test -cover ./internal/gateway ./internal/cli

改动已提交并推送到当前 PR 分支 phantom5099:file-checkpoint,提交为 2420cea

View job run

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
@phantom5099

This comment was marked as resolved.

@fennoai

This comment was marked as resolved.

xgopilot and others added 3 commits May 4, 2026 04:25
Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
@1024XEngineer 1024XEngineer deleted a comment from fennoai Bot May 4, 2026
@phantom5099

This comment was marked as resolved.

@fennoai

This comment was marked as resolved.

xgopilot and others added 2 commits May 4, 2026 05:35
Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
test(checkpoint): increase restore coverage
@fennoai

This comment was marked as resolved.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
@1024XEngineer 1024XEngineer deleted a comment from fennoai Bot May 4, 2026
@Cai-Tang-www
Copy link
Copy Markdown
Collaborator

没问题,加的工具必要就行,如果测试过能用我没啥问题了

@phantom5099 phantom5099 requested a review from pionxe May 4, 2026 08:02
@pionxe pionxe merged commit a89de27 into 1024XEngineer:main May 4, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants