verifier-fan-out-jury-design.md 8.26 KB

三视角评审团——Work Graph Fan-out 实现——详细设计(SDD)

日期: 2026-08-06 性质: 详细设计,对应需求文档 docs/done/verifier-fan-out-jury-requirements.md(条目 F,已确认)。回答该需求文档第 8 节的开放问题。 依赖代码:src/root_cause_trace.py(judge_root_cause/_PERSPECTIVE_FRAMINGS)、src/org_level_loop.py(run_fix_loop,唯一真实生产调用方)、src/work_graph_orchestrate.py(G 已实现的 run_graph()/topological_layers())。 状态: 设计草案,待评审后实施。

1. 汇总节点不是 Work Node——这是"zero_token 边"原则的直接应用

需求文档背景里"1 个汇总节点"的表述容易被误读成"汇总也要是一次 claude -p 调用"。实际不需要:>=2/3 投票本身是纯粹的计数聚合,不需要 Agent 推理——这正是 E 设计文档里强调的 edge_type: zero_token 场景本身。汇总逻辑保持为一个普通 Python 函数,在 run_graph() 返回之后,在调用方所在的进程里直接对 outputs 做计数,不产生额外的 claude -p 调用、不产生额外 token 成本(呼应需求文档第 7 节风险"token 成本上升"的顾虑,此处直接不引入这块新增成本)。

验收标准 1 要求"能在 execution_event/日志里看到三次独立的节点执行记录"——这只要求三个视角节点是真 Work Node(run_graph()/run_work_node() 本来就会为每个节点写 node_start/node_completed 日志),不要求汇总也是。

2. 判定规则复用,不重写(FR-3)

root_cause_trace.judge_root_cause 现有的 >=2/3 计数逻辑被抽成一个共享私有函数 _tally_perspectives(judgments),judge_root_cause(保留,闭包版本)和新的 judge_root_cause_via_work_graph(Work Graph 版本)都调用它——两套判定规则物理上是同一份代码,不是两份逻辑各自实现、容易失配。judgments 的 dict 形状({"category", "detail"})两个版本完全一致,_tally_perspectives 不需要知道调用方是闭包还是 Work Node 输出。

3. judge_root_cause_via_work_graph:新的 Work Graph 版本

def judge_root_cause_via_work_graph(
    conn, task_id, target_folder_path, bug_description, context,
    org_node="validator", skill_id="qa-task-validation", timeout=300,
):

内部结构:

  1. _PERSPECTIVE_FRAMINGS(已存在,三个视角的 framing 文本)驱动构造每个视角的完整提示词(复用现有 framing 文本,不改视角内容本身)。
  2. 构造一个菱形(严格说是"N 节点单层,无边")graph_manifest:nodes = [{"node_id": "perspective-" + name, "depends_on": []} for name in perspectives],edges = []——三个节点互不依赖,topological_layers() 天然把它们分进同一层,G 自动并发调度,不需要额外的调度代码。
  3. 像 e2e_pipeline.py::implement_code/validate_implementation_with_own_graph 一样,手动 INSERT INTO work_graph_run (..., status='running')(不经过 plan_work_graph()——这个图的形状是固定的、代码已知的,不需要一次额外的 LLM 规划调用,同样是"机制用代码"原则的应用)。
  4. run_graph() 执行:每个视角节点用真实 schema 约束的结构化输出({"type": "object", "required": ["category", "detail"]}),不再像现有 real_validator_perspective 那样手工解析"CATEGORY:/DETAIL:"文本行——work_graph_orchestrate._make_run_fn 走的是单节点 dynamic-workflow 脚本机制(agent(prompt, {{ agentType, schema }})),这条路径已经被 2026-08-01 的真实调用验证过 structured_output 真的能拿到(work_graph_orchestrate.py 模块 docstring 有记录),跟 work_graph_plan.py 那条"--json-schema 对 --agent 无效"的已知限制是两回事,不受它影响。
  5. _tally_perspectives([outputs["perspective-" + name] for name in perspectives]),返回值形状跟现有 judge_root_cause 完全一致({"root_cause_category", "root_cause_detail", "judgments"}),调用方不需要区分。

每个视角节点获得独立、干净的上下文(验收标准 3):_ensure_node_row/run_work_node 本身就是每个 node_exec_id 一次全新的 workflow 脚本渲染 + 独立子进程调用,input_payload 只含 {"node_id": ...},视角提示词通过 build_prompt 的闭包参数传入,节点之间不共享对话历史——这是 run_graph() 现有机制的天然属性,不需要额外设计。

4. org_level_loop.run_fix_loop 迁移(FR-5,唯一真实生产场景)

现状:

perspective_fns = [root_cause_trace.real_validator_perspective(...) for name in (...)]
judgment = root_cause_trace.judge_root_cause(perspective_fns)

改为:

judgment = root_cause_trace.judge_root_cause_via_work_graph(
    conn, task_id, target_folder_path, bug_description, context,
)

real_validator_perspective/闭包版 judge_root_cause 不删除——需求文档第 8 节开放问题已问过"要不要保留作为轻量备选",答案是保留:src/test_root_cause_trace.py 现有单测(JudgeRootCauseTest/RealValidatorPerspectiveParsingTest)覆盖的是判定规则本身的正确性,这些测试不需要真实 claude 子进程调用就能跑(用纯 Python 闭包 mock),继续作为规则正确性的快速回归保障(_tally_perspectives 被两边共用,这些测试事实上也在验证 Work Graph 版本的判定逻辑);闭包版本本身作为"不需要 worktree/Work Graph 开销的场景"的备选实现继续存在,不强制废弃。

5. 测试改造范围(真实影响面,如实记录)

src/test_org_level_loop.py 现有 3 个测试全部通过 mock org_node_trigger.trigger_org_node(拦截"CATEGORY:"提示词)来伪造三视角判断——这个 mock 边界在迁移后失效(新实现走 work_graph_execute.execute_work_graph,不再调用 trigger_org_node 做视角判断)。必须同步把这三个测试的 mock 边界改成 work_graph_execute.write_workflow_script/execute_work_graph(跟 test_work_graph_orchestrate.py 现有模式一致),org_node_trigger.trigger_org_node 的 mock 保留但只用于 fixer/verify 两次调用(迁移范围明确不包括这两处)。这是本次改动风险最高的一步——run_fix_loop 是真实生产闭环的一部分,测试改造必须逐条比对新旧断言等价,不能只是"能跑通"就算数。

6. 不实现的部分(诚实记录,对应需求文档非目标 + FR-4 的"应该"优先级)

  • FR-4(verify_fn 通用对接)不在本轮实现:把评审团机制包装成一个可以传给 run_graph(verify_fn=...) 的工厂函数,用于升级 _check_prd_consistency 这类现有单一验证器场景——需求文档标注"应该"而非"必须",且"哪些验证点该升级成评审团"本身是待定的判定标准(需求文档第 8 节开放问题未回答)。本轮只交付 FR-5(根因溯源场景本身)这一个"必须"级别的真实落地点,judge_root_cause_via_work_graph 的函数签名设计上保持通用(视角 framing/数量可参数化),为未来接 FR-4 留出空间,但不在本轮消费它。
  • 视角节点并发时间收益的实测(验收标准 4):G 已实现,topological_layers() 会自动把三个视角节点分进同一层走 ThreadPoolExecutor,机制上必然并发;但"在真实 claude -p 调用下测出时间重叠"需要 RUN_REAL_AGENT_E2E=1 真实调用,不在 mock 测试范围内验证,留给后续真实运行观察确认(跟 G 自己的 RunGraphConcurrencyTest 用 mock+time.sleep 验证并发的方式已经覆盖了"机制确实并发"这件事,不需要为 F 重新证明一遍 G 的能力)。

7. 回答需求文档第 8 节开放问题

  • judge_root_cause(闭包版)保留作为轻量备选,不废弃(第 4 节)。
  • FR-4 接口设计:本轮不实现,见第 6 节。
  • 除根因溯源外还有哪些验证点该升级:本轮不判定,留待 FR-4 一起决定。
  • 视角数量 N 是否可配置:judge_root_cause_via_work_graph/_tally_perspectives 设计上已经是对任意数量视角通用的(不硬编码 3),根因溯源场景固定传 3 个,未来场景可以传不同数量而不需要改动这两个函数本身。