Commit 0a4c2cfb8b45b5489530853f8aee5ba477feadb3

Authored by bboonnee
1 parent 1813f9d6

docs: move fully-implemented todo docs to docs/done/

work-graph-partial-artifact-persistence and work-graph-self-planning-
and-loop-back (requirements + design) are all shipped and tested;
move them out of docs/todo/ and fix up the docs/todo/*.md and
src/*.py path references that pointed at their old location.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/todo/work-graph-partial-artifact-persistence-requirements.md renamed to docs/done/work-graph-partial-artifact-persistence-requirements.md
docs/todo/work-graph-self-planning-and-loop-back-design.md renamed to docs/done/work-graph-self-planning-and-loop-back-design.md
1 1 # Planner 动态自规划 Work Graph(含节点判断与失败回路)——详细设计(SDD)
2 2  
3 3 **日期**: 2026-08-08
4   -**性质**: 详细设计,对应需求文档 `docs/todo/work-graph-self-planning-and-loop-back-requirements.md`(已确认)。本设计给出可直接实施的 schema/接口/算法/测试方案,逐条落实需求文档「8. 已确认的决策」。
  4 +**性质**: 详细设计,对应需求文档 `docs/done/work-graph-self-planning-and-loop-back-requirements.md`(已确认)。本设计给出可直接实施的 schema/接口/算法/测试方案,逐条落实需求文档「8. 已确认的决策」。
5 5 **依赖代码**:`src/work_graph_orchestrate.py`(`run_graph`/`topological_layers`/`_run_one_node`/`_verify_and_maybe_regenerate`/`_ensure_node_row`/`_LockedConn`)、`src/work_graph_plan.py`(`_PROMPT_TEMPLATE`/`_normalize_graph_manifest`/`GRAPH_MANIFEST_SCHEMA`)、`src/e2e_pipeline.py`(`plan_documents`/`check_script_compiles`/`_check_prd_consistency`)、`src/settings_config.py`(`get_/upsert_concurrency_config` 先例)、`src/schema.py`(`migrate_expand_artifact_types` 先例)。
6 6 **状态**: ✅ 已实现(2026-08-08)。实施顺序按第 11 节执行,`python3 -m unittest discover -s src`(692 tests)+ `discover -s supervisor -p "test_*.py"`(126 tests)全绿。实现过程中用真实测试发现并修正了本设计原文的几处问题(设计文档本身未回填,实现是最终准确来源):
7 7 - §2 最初的 `on_fail.target_node_id` 字段设计,在实现前按用户反馈改成了从 `on_failure` 边读取(已反映在正文),但 §4.0(`topological_layers()` 必须过滤 `on_failure` 边)和 §8.3(`_validate_loop_back_targets` 改成校验边而不是字段)是**开始写代码之后**才发现遗漏并现场修的两处,不是设计阶段就想全的。
... ... @@ -425,7 +425,7 @@ def _resolve_loop_limit(conn, org_node, graph_manifest, target_node_id):
425 425 def migrate_add_loop_back_event_type(conn):
426 426 """2026-08-08: 给 execution_event.event_type 的 CHECK 枚举加
427 427 'loop_back_triggered',用于记录 Work Graph 内部回路——见
428   - docs/todo/work-graph-self-planning-and-loop-back-design.md §7。
  428 + docs/done/work-graph-self-planning-and-loop-back-design.md §7。
429 429 幂等,安全对已有数据库无条件调用(同 migrate_expand_artifact_types 的写法)。
430 430 """
431 431 row = conn.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='execution_event'").fetchone()
... ...
docs/todo/work-graph-self-planning-and-loop-back-requirements.md renamed to docs/done/work-graph-self-planning-and-loop-back-requirements.md
... ... @@ -2,7 +2,7 @@
2 2  
3 3 **日期**: 2026-08-08
4 4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应用户 2026-08-07/08-08 两轮讨论:先是"要不要让 Planner 自己判断要不要有 test_script 节点",深入后明确为一个更完整的诉求——Planner 第一步应该是自己规划 Work Graph(要不要 brd/prd/sdd/benchmark/test 的组合、未来还有更多 tool/skill 可组合调用),并为每个节点规划输入输出边约束、判断成功/失败的方式,失败时把结果送回更早的节点形成回路(loop),并把 loop 过程中的产出记录下来供后续轮次参考改进。详细设计(SDD 层面:graph_manifest 的具体 JSON 形状、`topological_layers()`/`run_graph()` 的具体改造点、循环历史的具体持久化方式)留到本文档确认后单独产出。
5   -**状态**: ✅ 已实现(2026-08-08)。原「8. 开放问题」的 7 个分叉点均已拍板,记录见「8. 已确认的决策」,正文相应章节已按决策更新。详细设计见 `docs/todo/work-graph-self-planning-and-loop-back-design.md`;Part A(判断+回路引擎)与 Part B(Planner 自规划)均已实现,`python3 -m unittest discover -s src`(692 tests)+ `discover -s supervisor -p "test_*.py"`(126 tests)全绿。尚未做真实 `RUN_REAL_AGENT_E2E=1` 端到端验证(需要真实 API 额度,等待用户决定是否现在跑)。
  5 +**状态**: ✅ 已实现(2026-08-08)。原「8. 开放问题」的 7 个分叉点均已拍板,记录见「8. 已确认的决策」,正文相应章节已按决策更新。详细设计见 `docs/done/work-graph-self-planning-and-loop-back-design.md`;Part A(判断+回路引擎)与 Part B(Planner 自规划)均已实现,`python3 -m unittest discover -s src`(692 tests)+ `discover -s supervisor -p "test_*.py"`(126 tests)全绿。尚未做真实 `RUN_REAL_AGENT_E2E=1` 端到端验证(需要真实 API 额度,等待用户决定是否现在跑)。
6 6  
7 7 ## 1. 背景
8 8  
... ...
docs/todo/org-graph-as-work-graph-unified-loop-requirements.md
1 1 # Org Graph 本身也变成一个 Work Graph(Planner/Coder 之间加判断+精确回路)——需求文档
2 2  
3 3 **日期**: 2026-08-08
4   -**性质**: 需求文档草稿(BRD/PRD 层面),尚未逐条讨论确认——跟 `docs/todo/work-graph-self-planning-and-loop-back-requirements.md` 不同,那份是走完"逐条开放问题讨论→用户拍板"流程后才写的,这份是用户提出想法后直接要求"先记到 todo 里",还没有走那个流程。「6. 待确认的问题」列的都是需要讨论、不是已经拍板的。
  4 +**性质**: 需求文档草稿(BRD/PRD 层面),尚未逐条讨论确认——跟 `docs/done/work-graph-self-planning-and-loop-back-requirements.md` 不同,那份是走完"逐条开放问题讨论→用户拍板"流程后才写的,这份是用户提出想法后直接要求"先记到 todo 里",还没有走那个流程。「6. 待确认的问题」列的都是需要讨论、不是已经拍板的。
5 5 **触发场景(真实复现)**:任务 `2084590a`(修复 `work_node_run.py` 的 `execution_log_file` bug)里,Planner 自规划出的 `benchmark` 节点连续 4 次产出的都是"叙述我做了什么"的散文(跟 `docs/todo/validator-test-script-narrative-output-issue.md` 同一类问题,但这次发生在 benchmark 上,不是 test_script),LLM judge 的 `on_fail=retry_self` 循环耗尽 `max_attempts=3` 后任务直接 `blocked_pending_human`。用户追问引出两个观察:① Validator 从来没有机会在 Coder 开始实现之前复核 Planner 自己的产出;② 现有的 `org_level_loop.py`/`org_loop_route.py` 只在 Coder 完整实现之后才起作用,而且只能粗粒度地把整个任务打回"Planner 阶段"或"Coder 阶段"重来,不能精确跳到某个具体 Work Graph 节点。
6 6 **状态**: 草稿,待讨论。
7 7  
... ... @@ -51,5 +51,5 @@
51 51  
52 52 ## 7. 相关文档
53 53  
54   -- `docs/todo/work-graph-self-planning-and-loop-back-requirements.md`/`-design.md`:今天实现的 Work-Graph-内部判断+回路机制,这份需求本质是把它推广到 Org Graph 层面。
  54 +- `docs/done/work-graph-self-planning-and-loop-back-requirements.md`/`-design.md`:今天实现的 Work-Graph-内部判断+回路机制,这份需求本质是把它推广到 Org Graph 层面。
55 55 - `docs/todo/validator-test-script-narrative-output-issue.md`:叙述文字问题的原始记录(目前只覆盖 test_script),这次真实复现证明同一个根因也会发生在 benchmark 上,需要更新范围。
... ...
docs/todo/task-execution-resume-assumes-planner-artifacts-exist.md
1 1 # `task_execution.py` 恢复逻辑假设 Planner 一定已产出 sdd/prd/benchmark artifact——问题记录
2 2  
3 3 **日期**: 2026-08-08
4   -**性质**: 问题记录(真实复现,根因已定位,未修复)。发现于 `docs/todo/work-graph-self-planning-and-loop-back-requirements.md`(Planner 自规划)上线后的第一次真实端到端验证。
  4 +**性质**: 问题记录(真实复现,根因已定位,未修复)。发现于 `docs/done/work-graph-self-planning-and-loop-back-requirements.md`(Planner 自规划)上线后的第一次真实端到端验证。
5 5 **状态**: 待修复,未立项。
6 6  
7 7 ## 1. 现象(真实复现)
... ... @@ -16,7 +16,7 @@
16 16  
17 17 `org_node_lifecycle.trigger_org_node_full(conn, "planner", ...)` 在 `run_task_lifecycle()` 里**先于** `plan_documents()` 执行,并把 `task.status` 设成 `_POST_STATE["planner"] = "planning_brd_prd_sdd"`(`org_node_lifecycle.py:32`)——这个状态转移只代表"Planner 的 ready 确认调用成功了",不代表"Planner 的真实规划(`plan_documents()`)已经产出了任何东西"。
18 18  
19   -`task_execution.py::run_or_resume_task_lifecycle()` 里 `planning_brd_prd_sdd` 分支(`task_execution.py:126-129`)的注释写的是"Planner done, Coder not yet triggered"——这个假设在 Work Graph 自规划(`docs/todo/work-graph-self-planning-and-loop-back-requirements.md`)上线前基本成立:固定链的第一个节点是 brd,几乎不可能在第一个节点就失败到零产出。自规划之后这个假设不再可靠——自规划出的图可能把一个更容易失败的节点(比如需要更复杂 judgment 的 sdd)排在第一位,如果它在第一次尝试就彻底失败(耗尽 loop_back/retry 上限,或者像这次一样是一个跟自规划本身相关的新 bug),Planner 的 `work_graph_run` 会整个失败,不会有任何 artifact 落盘,但 `task.status` 已经先一步被置成了 `planning_brd_prd_sdd`。
  19 +`task_execution.py::run_or_resume_task_lifecycle()` 里 `planning_brd_prd_sdd` 分支(`task_execution.py:126-129`)的注释写的是"Planner done, Coder not yet triggered"——这个假设在 Work Graph 自规划(`docs/done/work-graph-self-planning-and-loop-back-requirements.md`)上线前基本成立:固定链的第一个节点是 brd,几乎不可能在第一个节点就失败到零产出。自规划之后这个假设不再可靠——自规划出的图可能把一个更容易失败的节点(比如需要更复杂 judgment 的 sdd)排在第一位,如果它在第一次尝试就彻底失败(耗尽 loop_back/retry 上限,或者像这次一样是一个跟自规划本身相关的新 bug),Planner 的 `work_graph_run` 会整个失败,不会有任何 artifact 落盘,但 `task.status` 已经先一步被置成了 `planning_brd_prd_sdd`。
20 20  
21 21 `test_dataset_content`/`test_script_content` 已经用 `_artifact_content_or_default()`(读不到就给默认值,不硬失败)处理过同一类问题(2026-08-06,那次是"这两个 artifact 类型本来就允许不存在"),但 `sdd`/`prd`/`benchmark` 三个仍然用会硬失败的 `_artifact_content()`——这个区分在固定链时代是对的(sdd/prd/benchmark 曾经是保证存在的),自规划之后不再成立。
22 22  
... ...
docs/todo/validator-test-script-narrative-output-issue.md
... ... @@ -2,7 +2,7 @@
2 2  
3 3 **日期**: 2026-08-07
4 4 **性质**: 问题记录 + 待调查,不是已确认根因的需求文档(区别于同目录下其他几份——那几份是已经定位到具体代码 bug 并修复过的,这份目前还只是"观察到的现象",根因未确定)。
5   -**状态**: 根因已定位(见第 6 节)。用户决策(2026-08-07):不再继续在提示词/重试反馈上打补丁,**先直接禁用 test_script/validator_test_script 节点**(Planner 的 `work_graph_plan._PROMPT_TEMPLATE` 不再要求这个节点,Validator 的 `e2e_pipeline._VALIDATOR_MANIFEST` 从 3 节点缩成 2 节点),后续如果真的需要再重新设计(见第 8 节的结构性修复方向:把"探索"和"最终作答"拆成两次独立调用)。`_NARRATION_RETRY_HINT`(第 7 节)代码仍保留在 `_test_script_verify_fn` 里,只是当前这条路径不会被触发——如果以后重新启用这个节点,这个缓解依然生效。**2026-08-08 更新(第四轮,与自规划 Work Graph 需求的交叉决策)**:`docs/todo/work-graph-self-planning-and-loop-back-requirements.md`(Planner 自规划节点集合 + 失败回路需求)上线后,test_script 会重新作为可选节点开放给 Planner 自由规划——用户明确决定**不等本文档的结构性修复**,narrative-output 问题可能因此重现,这是已知且被接受的风险。本文档记录的根因(第 6 节)的真正结构性修复,安排在**另一份尚未撰写、关于"给 Coder agent 加入 test_script 模块"的文档**里一起解决,不是本文档单独继续跟进的范围。**2026-08-07 更新(第二轮):不只是 Validator 的问题——`911861fe` 这个纯后端任务在 Planner 自己的 `n_test_script` 节点上也复现了同一模式。累计 3 个任务、6 次到达 test_script 类节点,6 次全部复现(100%),横跨 Planner 和 Validator 两种场景。建议提高优先级,不要再靠重试撞运气。** **2026-08-07 更新(第三轮,根因排查):这是 08-06 "Benchmark 叙述事故"(commit `07a0a27`)的同类复发——test_script 类节点当时已经同时拿到提示词纠偏和机械拦截网双重防护,仍然 6/6 复现。根因不是"self-hosting 词汇污染"这个泛泛的猜测,而是 test_script 提示词本身要求"先做工具探索再产出内容",模型探索后的收尾话术天然是"汇报我做了/启动了什么"而不是切换成纯文本吐出模式;self-hosting 只是放大器,让叙述读起来更精确(因为探索对象就是 graphlemon 自己,词汇表精确对应),不是必要条件。BRD/PRD/SDD/Benchmark/test_dataset 这几个不需要先探索的纯组合型节点,自 08-06 后再未复现,与此吻合。**
  5 +**状态**: 根因已定位(见第 6 节)。用户决策(2026-08-07):不再继续在提示词/重试反馈上打补丁,**先直接禁用 test_script/validator_test_script 节点**(Planner 的 `work_graph_plan._PROMPT_TEMPLATE` 不再要求这个节点,Validator 的 `e2e_pipeline._VALIDATOR_MANIFEST` 从 3 节点缩成 2 节点),后续如果真的需要再重新设计(见第 8 节的结构性修复方向:把"探索"和"最终作答"拆成两次独立调用)。`_NARRATION_RETRY_HINT`(第 7 节)代码仍保留在 `_test_script_verify_fn` 里,只是当前这条路径不会被触发——如果以后重新启用这个节点,这个缓解依然生效。**2026-08-08 更新(第四轮,与自规划 Work Graph 需求的交叉决策)**:`docs/done/work-graph-self-planning-and-loop-back-requirements.md`(Planner 自规划节点集合 + 失败回路需求)上线后,test_script 会重新作为可选节点开放给 Planner 自由规划——用户明确决定**不等本文档的结构性修复**,narrative-output 问题可能因此重现,这是已知且被接受的风险。本文档记录的根因(第 6 节)的真正结构性修复,安排在**另一份尚未撰写、关于"给 Coder agent 加入 test_script 模块"的文档**里一起解决,不是本文档单独继续跟进的范围。**2026-08-07 更新(第二轮):不只是 Validator 的问题——`911861fe` 这个纯后端任务在 Planner 自己的 `n_test_script` 节点上也复现了同一模式。累计 3 个任务、6 次到达 test_script 类节点,6 次全部复现(100%),横跨 Planner 和 Validator 两种场景。建议提高优先级,不要再靠重试撞运气。** **2026-08-07 更新(第三轮,根因排查):这是 08-06 "Benchmark 叙述事故"(commit `07a0a27`)的同类复发——test_script 类节点当时已经同时拿到提示词纠偏和机械拦截网双重防护,仍然 6/6 复现。根因不是"self-hosting 词汇污染"这个泛泛的猜测,而是 test_script 提示词本身要求"先做工具探索再产出内容",模型探索后的收尾话术天然是"汇报我做了/启动了什么"而不是切换成纯文本吐出模式;self-hosting 只是放大器,让叙述读起来更精确(因为探索对象就是 graphlemon 自己,词汇表精确对应),不是必要条件。BRD/PRD/SDD/Benchmark/test_dataset 这几个不需要先探索的纯组合型节点,自 08-06 后再未复现,与此吻合。**
6 6  
7 7 **2026-08-08 更新(第五轮,真实复现,推翻了第三轮"Benchmark 不需要先探索、所以不会复现"这个判断)**:任务 `2084590a`(Work Graph 自规划 + 失败回路上线后的第一次真实端到端验证)里,Planner 自规划出的 `benchmark` 节点连续 4 次真实产出全部是叙述文字,例如:"The planner Work Node finished. It produced a Benchmark artifact covering a real bug in `src/work_node_run.py::run_work_node()`... **No further action taken unless the user wants this handed off to the Coder**",以及 "**No files were changed in this checkout (git status clean) — this run only generated the benchmark text; it did not apply the fix or commit anything.**"——都是"我做了什么"的散文,不是真实的 benchmark 场景+断言内容。这次是自规划场景下 `judgment.kind="llm"` 的 LLM judge(不是机械检查)在把关,4 次里第 3 次的 judge 反馈甚至精确诊断出了这一点:"the text only asserts scenarios exist, without stating them... No benchmark.md matching this content was found on disk anywhere in the repo... recommend surfacing the actual artifact file/text... rather than a prose summary of it"——但这个诊断只是混在其他反馈条目里的一条,没有变成能强制拦住的机制,第 4 次原样复现,最终 `loop_back` 耗尽 `max_attempts=3`,任务 `blocked_pending_human`。
8 8  
... ...
src/schema.py
... ... @@ -274,7 +274,7 @@ CREATE INDEX idx_execution_event_project_created ON execution_event(project_id,
274 274 CREATE INDEX idx_execution_event_task_created ON execution_event(task_id, created_at);
275 275  
276 276 -- 1.11.1 work_graph_loop_limit_config (Work Graph self-planning + loop-back,
277   --- docs/todo/work-graph-self-planning-and-loop-back-design.md §6)
  277 +-- docs/done/work-graph-self-planning-and-loop-back-design.md §6)
278 278 CREATE TABLE work_graph_loop_limit_config (
279 279 config_id TEXT PRIMARY KEY,
280 280 max_loop_attempts INTEGER NOT NULL,
... ... @@ -596,7 +596,7 @@ def migrate_relax_org_node_constraints(conn):
596 596  
597 597 def migrate_add_work_graph_loop_limit_config(conn):
598 598 """2026-08-08 (Work Graph self-planning + loop-back,
599   - docs/todo/work-graph-self-planning-and-loop-back-design.md §6): adds
  599 + docs/done/work-graph-self-planning-and-loop-back-design.md §6): adds
600 600 work_graph_loop_limit_config on an ALREADY-POPULATED database. Plain
601 601 `CREATE TABLE IF NOT EXISTS` -- a brand-new table, no CHECK constraint
602 602 to rebuild around, unlike migrate_expand_artifact_types. Idempotent and
... ... @@ -612,7 +612,7 @@ def migrate_add_work_graph_loop_limit_config(conn):
612 612  
613 613 def migrate_add_loop_back_event_type(conn):
614 614 """2026-08-08 (Work Graph self-planning + loop-back,
615   - docs/todo/work-graph-self-planning-and-loop-back-design.md §7): adds
  615 + docs/done/work-graph-self-planning-and-loop-back-design.md §7): adds
616 616 'loop_back_triggered' to execution_event.event_type's CHECK enum on an
617 617 ALREADY-POPULATED database -- same create-copy-drop-rename dance as
618 618 migrate_expand_artifact_types (SQLite has no ALTER-based constraint
... ...
src/task_query.py
... ... @@ -174,7 +174,7 @@ def get_task(conn, task_id):
174 174 # on_failure edge / a retry_self node's "sent back Nx" badge --
175 175 # neither is derivable from graph_manifest/work_node_execution
176 176 # alone (loop_back_triggered is execution_event-only by design,
177   - # see docs/todo/work-graph-self-planning-and-loop-back-design.md §7).
  177 + # see docs/done/work-graph-self-planning-and-loop-back-design.md §7).
178 178 "loop_back_events": _loop_back_events_for_run(conn, w[0]),
179 179 }
180 180 for w in runs
... ...
src/test_schema.py
... ... @@ -748,7 +748,7 @@ class MigrateAddWorkGraphLoopLimitConfigTest(unittest.TestCase):
748 748 self.conn = schema.connect(":memory:")
749 749 old_sql = schema.SCHEMA_SQL.replace(
750 750 "-- 1.11.1 work_graph_loop_limit_config (Work Graph self-planning + loop-back,\n"
751   - "-- docs/todo/work-graph-self-planning-and-loop-back-design.md §6)\n"
  751 + "-- docs/done/work-graph-self-planning-and-loop-back-design.md §6)\n"
752 752 "CREATE TABLE work_graph_loop_limit_config (\n"
753 753 " config_id TEXT PRIMARY KEY,\n"
754 754 " max_loop_attempts INTEGER NOT NULL,\n"
... ...
src/work_graph_plan.py
... ... @@ -308,7 +308,7 @@ def _normalize_graph_manifest(manifest):
308 308 hardcoded canonical chain to fall back on if an LLM response omits
309 309 dependency info, so a real reproduction of the 2026-08-03 incident
310 310 shape is a genuine, currently-unmitigated risk this design accepted
311   - (see docs/todo/work-graph-self-planning-and-loop-back-requirements.md
  311 + (see docs/done/work-graph-self-planning-and-loop-back-requirements.md
312 312 §7's risk list) -- FR-B3's own acceptance criterion only covers the
313 313 "wrong key names" failure mode this backfill already handles, not a
314 314 complete absence of dependency info with no canonical chain to guess from.
... ...