Commit 1813f9d66e03703967ef29cfd86dc5bb520a3dc7

Authored by bboonnee
1 parent 798660b6

Record benchmark narration recurrence + propose Org-Graph-as-Work-Graph loop

Real end-to-end verification of Work Graph self-planning + loop-back
(task 2084590a) hit the narrative-output problem again -- this time on a
self-planned benchmark node, not test_script, disproving the earlier
"BRD/PRD/SDD/Benchmark won't recur" hypothesis in validator-test-script-
narrative-output-issue.md. The judge caught it once (round 3) but couldn't
force a fix; the loop_back retry limit correctly exhausted and blocked the
task cleanly instead of hanging or crashing.

Also drafts org-graph-as-work-graph-unified-loop-requirements.md: extending
today's Work-Graph-internal judgment/on_fail/loop_back mechanism up to the
Org Graph itself, so Coder doesn't start from an unreviewed Planner output
and a Validator FAIL can route to a precise node instead of a whole phase.
Flags the real hard part (Planner/Coder each plan their own graph_manifest,
so node ids don't share an address space across the org boundary) as the
first open question, not yet implemented.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/todo/org-graph-as-work-graph-unified-loop-requirements.md 0 → 100644
  1 +# Org Graph 本身也变成一个 Work Graph(Planner/Coder 之间加判断+精确回路)——需求文档
  2 +
  3 +**日期**: 2026-08-08
  4 +**性质**: 需求文档草稿(BRD/PRD 层面),尚未逐条讨论确认——跟 `docs/todo/work-graph-self-planning-and-loop-back-requirements.md` 不同,那份是走完"逐条开放问题讨论→用户拍板"流程后才写的,这份是用户提出想法后直接要求"先记到 todo 里",还没有走那个流程。「6. 待确认的问题」列的都是需要讨论、不是已经拍板的。
  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 +**状态**: 草稿,待讨论。
  7 +
  8 +## 1. 背景:现在有两套独立的、粒度不同的循环机制
  9 +
  10 +- **`work-graph-self planning-and-loop-back`(今天刚做完)**:一个 Org Agent **自己的** Work Graph 内部,节点声明 `judgment`/`on_fail`,失败可以 `loop_back` 跳回同一张图里更早的节点,历史记录在 `execution_event`,上限可配置。只在**单个 Org Agent 的图内部**生效。
  11 +- **`org_level_loop.py`/`org_loop_route.py`(已实现,更早)**:Validator 对 Coder 的最终实现给出 FAIL/INCONCLUSIVE 判决后,`p_validator_root_cause_trace` 判根因(planning/coder/skill_defect),路由到 `fast_lane`(回 Coder)或 `full_replan`(回 Planner),`task.loop_round` 封顶 3 轮。这是**跨 Org Agent 边界**的循环,但粒度是"整个阶段重来",不是"跳到具体哪个节点";而且触发条件是"Coder 已经完整实现完,Validator 也已经判过"——Planner 自己的产出(BRD/PRD/SDD/Benchmark)在被 Coder 使用之前,从来没有任何独立角色复核过。
  12 +
  13 +这次真实复现暴露的具体问题:Planner 自己的 Work Graph 内部循环耗尽后,直接 `blocked_pending_human`,整个任务卡死——既没有像 `org_level_loop` 那样有根因判断和路由,也没有人能在 Coder 动手之前发现"这个 Benchmark 其实是叙述文字,根本不能用"。
  14 +
  15 +## 2. 用户提出的方向
  16 +
  17 +把 Planner → Coder → Validator 这条 Org Graph 本身,也按照今天 Work Graph 那套"节点 + judgment + on_fail + loop_back"机制来跑:
  18 +
  19 +1. Planner 完成后,先过一道 judgment(可以是 Validator,也可以是别的判断机制),不通过就 `loop_back` 回 Planner(或者回 Planner 自己 Work Graph 里更精确的某个节点,比如就回那个 benchmark 节点,而不是"重新触发整个 Planner")。
  20 +2. 通过了才进入 Coder。
  21 +3. Coder 完成后过 judgment(现有 Validator 逻辑),不通过时要先判断根因是 Planner 的问题还是 Coder 的问题,再精确 `loop_back` 到对应阶段的具体节点,而不是现在这种"笼统打回整个阶段"。
  22 +
  23 +本质上是把"Org Graph 本身"也建模成一张 Work Graph(Planner/Coder/Validator 是三个"节点",每个节点内部又是一张自己的、可能自规划的子图),对失败的处理机制上下两层统一。
  24 +
  25 +## 3. 目标(草案,待讨论后细化为正式 FR)
  26 +
  27 +1. Coder 开始实现之前,Planner 自己的产出(至少是 Benchmark,可能还有 SDD)要经过一次独立判断,能拦住这次真实复现的那种"叙述文字冒充 benchmark"的情况,不必等到 Coder 把错误的实现做完才被 Validator 发现。
  28 +2. 失败时的路由要能精确到**具体是哪个节点出的问题**(比如"benchmark 节点内容不对"),而不是只知道"Planner 阶段"或"Coder 阶段"这种粗粒度。
  29 +3. 跨 Org Agent 边界的循环(现有 `org_level_loop.py` 覆盖的场景)和单个 Org Agent 内部的循环(今天刚做的 Work Graph loop_back)用同一套机制表达,不是两套平行的、概念不同的循环实现。
  30 +4. 循环上限、历史记录复用今天已经建立的模式(`settings_config.py` 风格的可配置上限、`execution_event` 记录历史),不重新发明。
  31 +
  32 +## 4. 一个明显更难的地方:跨 Org Agent 的"节点"地址空间不一样
  33 +
  34 +今天的 `loop_back` 是在**同一个 `work_graph_run`**(同一张图)内部跳转——`_on_failure_target`/`_transitive_dependents` 全部假设 `target_node_id` 和当前失败节点在同一个 `graph_manifest["nodes"]` 里。但 Planner 和 Coder 是**两次独立的 `plan_work_graph()` 调用**,各自产出各自的 `work_graph_run`/`graph_manifest`,节点 id 空间互不相通——"Coder 阶段发现问题,要跳回 Planner 阶段的 benchmark 节点",这个"benchmark 节点"活在另一个、可能早就标记为 `done` 的 `work_graph_run` 里,不是当前正在跑的这张图的一部分。今天的 `_LoopBackRequested`/`run_graph()` 主循环完全没有考虑"目标节点在另一张图里"这种情况。
  35 +
  36 +这不是一个小的实现细节——这是这次需求跟今天那次最大的架构差异,需要先想清楚:是把 Planner 和 Coder 的图**合并成同一张跨阶段的大图**(这样 `loop_back` 天然能跳),还是**保留现在"每个 Org Agent 各自一张图"的边界**、在图与图之间另设计一层专门的跨图路由(更接近现有 `org_loop_route.py` 的做法,只是把"回 Planner"精确到"回 Planner 图里的哪个节点")。
  37 +
  38 +## 5. 非目标(草案)
  39 +
  40 +1. 不在这份需求里改变 `task.status` 现有的 `queued/planning_brd_prd_sdd/coding/validating` 状态机本身的字面值——如果要做,这套状态机可能也需要重新设计("Planner 阶段内部又在 loop_back"这种状态目前完全没有对应的 task.status 值),但那是另一个需要谨慎讨论的问题,不在这次草稿里预先决定。
  41 +2. 不假定"给 Coder 开始前加一道判断"用的一定是 Validator——可能是 Validator 的一个轻量子集,也可能是完全独立的判断机制,待讨论。
  42 +
  43 +## 6. 待确认的问题(这份文档的主要目的——先把问题摆出来)
  44 +
  45 +- [ ] **要不要完全取代 `org_level_loop.py`/`org_loop_route.py`,还是新机制只是给现有机制"打补丁"(比如只加"Coder 开始前先判断 Planner 产出"这一段,Coder 完成后的现有路由逻辑不动)?**
  46 +- [ ] **§4 提到的跨图节点寻址问题:合并成一张大图,还是图与图之间单独设计一层路由?** 这个决定几乎决定了整个实现方式,建议第一个讨论。
  47 +- [ ] **"Coder 开始前判断 Planner 产出"这一步谁来做?** 复用 Validator(但 Validator 现在的定位是"独立验收最终实现",拿它来判断"BRD/PRD/SDD/Benchmark 写得好不好"是不是错位?)、还是让 Planner 自己的 Work Graph 内部多一个"自我复核"节点(这样其实不需要跨 Org Agent,本质是今天 Part A 机制的延伸,不需要碰 Org Graph)?
  48 +- [ ] **根因判断("这次失败该回 Planner 还是回 Coder")复用现有 `p_validator_root_cause_trace` 的三选一分类,还是需要更细粒度(精确到某个 Work Graph 节点)的判断机制?**
  49 +- [ ] **`task.loop_round`/`_LOOP_LIMIT=3` 这个既有的、粗粒度的计数器要不要保留?** 如果循环变成能精确到节点级别,是不是也需要参考今天 `settings_config.py` 风格的按 org_node/节点类型可配置上限,而不是一个全局固定的 3?
  50 +- [ ] **这次真实触发这个讨论的具体症状(benchmark 叙述文字),本身要不要单独先用更小的手段缓解**(比如给 benchmark 也加一个类似 `check_script_compiles` 的机械启发式检查,判断"这段内容像不像散文而不是结构化 benchmark"),**而不必等这个更大的 Org Graph 改造做完?** 这个可以先记到 `docs/todo/validator-test-script-narrative-output-issue.md`(已经在追踪同一类问题,只是要把范围从 test_script 扩到 benchmark)。
  51 +
  52 +## 7. 相关文档
  53 +
  54 +- `docs/todo/work-graph-self-planning-and-loop-back-requirements.md`/`-design.md`:今天实现的 Work-Graph-内部判断+回路机制,这份需求本质是把它推广到 Org Graph 层面。
  55 +- `docs/todo/validator-test-script-narrative-output-issue.md`:叙述文字问题的原始记录(目前只覆盖 test_script),这次真实复现证明同一个根因也会发生在 benchmark 上,需要更新范围。
docs/todo/validator-test-script-narrative-output-issue.md
@@ -4,6 +4,12 @@ @@ -4,6 +4,12 @@
4 **性质**: 问题记录 + 待调查,不是已确认根因的需求文档(区别于同目录下其他几份——那几份是已经定位到具体代码 bug 并修复过的,这份目前还只是"观察到的现象",根因未确定)。 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/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 后再未复现,与此吻合。**
6 6
  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 +
  9 +这推翻了第三轮"BRD/PRD/SDD/Benchmark/test_dataset 不需要先探索,所以不会复现"的判断——这次的 goal 是一个真实 bug 修复(不是新功能),SDD 节点的 judge_prompt 里精确引用了 `work_node_run.py` 第 272/274-278/420-424 行的真实代码事实,说明模型确实主动去读了真实代码来支撑 SDD/Benchmark 的内容——**触发条件看起来不是"节点类型是不是 test_script",而是"这次任务本身需不需要模型先探索真实代码才能写出扎实的内容"**,跟 test_script 一样都会诱导"先探索、再收尾汇报"的模式,只是 benchmark 类型的节点被诱导的条件更宽(只要目标是修复已有真实代码,不需要是"要不要写可执行脚本"这么窄的条件)。另外一个新发现:**benchmark 没有像 test_script 的 `check_script_compiles` 那样的机械拦截网**,只能靠 LLM judge 自己识别"这是散文还是真 benchmark"——judge 有能力识别(第 3 次证明了),但识别到不等于能强制拦住,下一轮又原样复现。
  10 +
  11 +后续讨论方向记在 `docs/todo/org-graph-as-work-graph-unified-loop-requirements.md`(草案,待讨论)——包括要不要给 benchmark 也加一个类似的启发式机械检查、以及要不要在 Coder 开始实现之前对 Planner 产出多一道独立复核。
  12 +
7 ## 1. 现象 13 ## 1. 现象
8 14
9 `validate_implementation_with_own_graph()`(Validator 自己的 benchmark→test_dataset→test_script 验证图)里的 `validator_test_script` 节点,连续两次真实生产运行中都输出了**叙述性文字**而不是可执行代码,导致 `check_script_compiles()` 正确拦截(py_compile/node --check 都报语法错误),`verify_fn` 判定失败,重试一次仍然失败,最终 `ConsistencyCheckFailedError`。 15 `validate_implementation_with_own_graph()`(Validator 自己的 benchmark→test_dataset→test_script 验证图)里的 `validator_test_script` 节点,连续两次真实生产运行中都输出了**叙述性文字**而不是可执行代码,导致 `check_script_compiles()` 正确拦截(py_compile/node --check 都报语法错误),`verify_fn` 判定失败,重试一次仍然失败,最终 `ConsistencyCheckFailedError`。