work-graph-time-travel-debugging-requirements.md
8.52 KB
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/supervisor-auto-rollback-design.md 描述的 Deploy Supervisor 自动回滚是两件完全不同的事——不要混淆:
| Time-Travel 调试(本文档) | Deploy Supervisor 自动回滚 | |
|---|---|---|
| 回滚的对象 | 某一次任务的 Work Graph 执行过程(某个 work_node_execution 的中间状态) |
graphlemon 自身的代码版本(git commit) |
| 触发者 | 人工调试意图("我想看看如果这一步换个输入会怎样") | 自动健康检查失败信号 |
| 影响范围 | 单个 task 的执行历史,不影响其他任务 | 整个 graphlemon 进程的运行版本 |
| 相关文档 | 本文档 | docs/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. 目标
- 允许人工从某次 Work Graph 执行的某个历史节点(
work_node_execution)出发,用新的输入(不同的 prompt、不同的上游产物)重新执行该节点及其下游依赖节点,产出一条与原执行并存、可独立查看的新分支,而不覆盖或污染原始执行记录。 - 分叉出来的新分支要能在控制台里被查看、区分(哪条是原始记录、哪条是分叉出来的调试分支)。
- 这个能力主要服务于调试/复盘场景(例如:"如果 SDD 阶段换一版方案,后续的 Coder 实现会有什么不同"),不是生产环境的正常任务执行路径的一部分。
3. 非目标
- 不是自动化机制。分叉重放由人工在控制台里主动触发,不是系统自主决定"要不要回溯重跑"的行为——这跟 Org-level Loop 那种系统自主触发的回退是两回事。
- 不追求支持任意粒度的时间点(比如"回到某个节点执行到一半的中间状态")。粒度限定在
work_node_execution这个已有的持久化单元——从"某个节点完成之后(它的output_payload已经落库)"这个时间点分叉,不试图捕获节点内部一次claude -p调用执行过程中的中间状态。 - 不在第一版里做完整的可视化 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. 验收标准
- 给定一个已经跑完的简单 Work Graph(比如 Planner 的 BRD→PRD→SDD→Benchmark 四节点链),从 PRD 节点分叉、换一个不同的 PRD 内容,触发重新执行,验证 SDD、Benchmark 两个下游节点用新的 PRD 内容重新跑了一遍,而 BRD 节点没有被重新执行(直接复用原值)。
- 验证原始 run 的记录在分叉操作后完全不受影响——分叉前后分别查询原始
work_graph_run/work_node_execution,字段值一致。 - 验证同一个历史节点可以被分叉多次,产生多条互不干扰的分支,且每条分支都能独立追溯到"从哪个节点、哪个原始 run 分叉而来"。
- 控制台里能看到某个 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"是否采纳分叉分支结果"的人工操作具体是什么交互(一个"设为主线"按钮?还是纯只读展示,人工另外手动处理)?
- [ ] 是否需要给分叉分支设置生命周期/清理策略(长期堆积的调试分支会不会需要定期清理,类似原始执行日志的保留天数机制)?