# Work Graph 执行历史 Time-Travel 调试——需求文档 **日期**: 2026-08-05 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应 `docs/todo/graph-engineering-open-items.md` 条目 D 的确认结果——"time travel 可以立项"。 **状态**: 草案,待评审。 **命名消歧(仿照 CLAUDE.md「两个 Supervisor」的先例)**:本文档说的"Time-Travel 调试"跟 `docs/done/supervisor-auto-rollback-design.md` 描述的 Deploy Supervisor 自动回滚是两件完全不同的事——不要混淆: | | Time-Travel 调试(本文档) | Deploy Supervisor 自动回滚 | |---|---|---| | 回滚的对象 | 某一次任务的 Work Graph 执行过程(某个 `work_node_execution` 的中间状态) | graphlemon 自身的代码版本(git commit) | | 触发者 | 人工调试意图("我想看看如果这一步换个输入会怎样") | 自动健康检查失败信号 | | 影响范围 | 单个 task 的执行历史,不影响其他任务 | 整个 graphlemon 进程的运行版本 | | 相关文档 | 本文档 | `docs/done/supervisor-auto-rollback-design.md` | ## 1. 背景 综述版对照笔记(`docs/reference/graph-engineering-vs-loop-engineering-overview.diff-with-graphlemon.md` §6)指出:LangGraph 的 checkpointer 机制带来四个能力——人在回路、记忆、时间旅行调试、容错。逐项对照 graphlemon 现状: | LangGraph 能力 | graphlemon 现状 | |---|---| | 容错(部分节点失败保留已成功节点输出) | ✅ 已具备(`work_node_execution` 按节点独立持久化状态) | | 人在回路(暂停/恢复) | ✅ 已具备(`src/task_execution.py` 的 Start/Pause/Stop) | | 记忆(多轮交互间保留上下文) | ✅ 已具备(`.claude/agent-memory/`) | | 时间旅行调试(回到任意历史检查点重放/分叉) | ❌ 未实现,现状是"用 git 历史 + 手动重跑" | `work_node_execution` 表(`src/schema.py:138`)目前按节点持久化 `input_payload`/`output_payload`/`status`/`retry_count` 等字段,`work_graph_run` 表(`src/schema.py:118`)持久化 `graph_manifest` 和整体 `status`——这些数据本身已经具备"回放某一次执行发生了什么"的信息基础,缺的是"从某个历史节点分叉出一条新的执行分支"这个操作本身。 ## 2. 目标 1. 允许人工从某次 Work Graph 执行的某个历史节点(`work_node_execution`)出发,用新的输入(不同的 prompt、不同的上游产物)重新执行该节点及其下游依赖节点,产出一条与原执行并存、可独立查看的新分支,而不覆盖或污染原始执行记录。 2. 分叉出来的新分支要能在控制台里被查看、区分(哪条是原始记录、哪条是分叉出来的调试分支)。 3. 这个能力主要服务于调试/复盘场景(例如:"如果 SDD 阶段换一版方案,后续的 Coder 实现会有什么不同"),不是生产环境的正常任务执行路径的一部分。 ## 3. 非目标 1. **不是自动化机制**。分叉重放由人工在控制台里主动触发,不是系统自主决定"要不要回溯重跑"的行为——这跟 Org-level Loop 那种系统自主触发的回退是两回事。 2. **不追求支持任意粒度的时间点**(比如"回到某个节点执行到一半的中间状态")。粒度限定在 `work_node_execution` 这个已有的持久化单元——从"某个节点完成之后(它的 `output_payload` 已经落库)"这个时间点分叉,不试图捕获节点内部一次 `claude -p` 调用执行过程中的中间状态。 3. **不在第一版里做完整的可视化 diff/比较工具**(比如"原分支和新分支的产物并排对比高亮差异")。第一版先解决"分叉出一条新分支并能分别查看"这个核心能力,比较/diff 视图留给后续迭代。 ## 4. 功能需求 | # | 需求描述 | 优先级 | |---|---|---| | FR-1 | 给定一个已完成(`status='done'`)的 `work_node_execution`,人工可以指定新的输入(覆盖原 `input_payload` 的部分或全部字段),触发从这个节点开始的重新执行。 | 必须 | | FR-2 | 分叉执行产生的是一次**新的** `work_graph_run`(不是修改原 `work_graph_run` 的记录),需要能追溯"这次运行是从哪个历史节点、哪个原始 run 分叉出来的"。 | 必须 | | FR-3 | 分叉出的新 run 只重新执行"从分叉点开始,到该节点的下游依赖链",分叉点**之前**的节点(分叉点本身的上游)直接复用原始 run 已经产出的 `output_payload`,不重新执行——这跟现有断点续跑"复用已完成节点输出"的逻辑是同一个原则的延伸。 | 必须 | | FR-4 | 控制台(架构文档 §13 的人机交互控制台)需要能列出"某个 task 下所有的分叉分支",并支持切换查看每条分支各自的节点执行详情(复用现有 v_task_detail 一类页面的节点级展示能力)。 | 应该 | | FR-5 | 分叉出的调试分支不应该影响原始任务的正常执行状态——即使分叉分支的执行结果比原分支"更好",也不会自动替换/合并回原始任务的 `task.status`,是否采纳分叉分支的结果需要人工另外决定(比如人工把分叉分支的产物手动复制回主线,或者认可分叉分支本身就是新的正确路径)。 | 必须 | ## 5. 验收标准 1. 给定一个已经跑完的简单 Work Graph(比如 Planner 的 BRD→PRD→SDD→Benchmark 四节点链),从 PRD 节点分叉、换一个不同的 PRD 内容,触发重新执行,验证 SDD、Benchmark 两个下游节点用新的 PRD 内容重新跑了一遍,而 BRD 节点没有被重新执行(直接复用原值)。 2. 验证原始 run 的记录在分叉操作后完全不受影响——分叉前后分别查询原始 `work_graph_run`/`work_node_execution`,字段值一致。 3. 验证同一个历史节点可以被分叉多次,产生多条互不干扰的分支,且每条分支都能独立追溯到"从哪个节点、哪个原始 run 分叉而来"。 4. 控制台里能看到某个 task 下全部分支的列表,并能分别打开查看。 ## 6. 约束与假设 - `work_graph_run` 表当前有一条唯一约束:`ux_work_graph_run_active`——同一个 `(task_id, org_node)` 组合同一时间只能有一个非终态的 run(`src/schema.py:134`)。分叉产生的新 run 如果沿用同一个 `task_id`,会撞上这条约束(分叉分支和原分支可能同时处于非终态)。需要在设计阶段决定:分叉出的 run 是否需要一个独立的标识维度(比如新增一个 `parent_run_id`/`fork_of_node_exec_id` 字段,并调整唯一约束的范围,使其只约束"同一条分支线"内部,而不是整个 `task_id`),这是本需求对现有数据模型影响最大的一处,必须在设计阶段仔细过一遍现有依赖这条约束的代码路径(比如断点续跑逻辑是否假设了"同一 task_id+org_node 只有一个活跃 run")。 - 分叉执行复用现有的每节点独立子进程执行机制(`work_node_run.run_work_node`/`work_graph_execute.execute_work_graph`),不需要新的执行引擎——分叉本质上是"用一份新的 `graph_manifest`(去掉已复用的上游节点)+ 新的初始 `outputs` 字典(预填充复用节点的输出)调用一次 `run_graph()`",复用度很高。 ## 7. 风险 - **数据模型改动的影响面需要在设计阶段仔细评估**,`ux_work_graph_run_active` 这条唯一约束背后可能还有其他代码路径隐式依赖"一个 task_id+org_node 只有一个活跃 run"这个假设(比如查询任务当前状态的逻辑、WebSocket 推送逻辑),改动前需要逐一排查,不能只看 schema 本身。 - **控制台可视化工作量可能比后端数据模型改动更大**——FR-4 涉及新的 UI 交互(选择分叉点、对比分支列表),需要跟现有 `docs/wireframes/` 里的 `v_task_detail` 类页面设计对齐,这部分工作量需要在设计阶段单独评估,不要低估。 ## 8. 开放问题(留给详细设计阶段回答) - [ ] `parent_run_id`/`fork_of_node_exec_id` 这类新字段的具体 schema 设计,以及 `ux_work_graph_run_active` 约束要怎么调整。 - [ ] 分叉操作的触发入口——只在控制台里做,还是也要开一个后端 API 供其他方式调用? - [ ] FR-5"是否采纳分叉分支结果"的人工操作具体是什么交互(一个"设为主线"按钮?还是纯只读展示,人工另外手动处理)? - [ ] 是否需要给分叉分支设置生命周期/清理策略(长期堆积的调试分支会不会需要定期清理,类似原始执行日志的保留天数机制)?