# 三视角评审团——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 版本 ```python 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,唯一真实生产场景) 现状: ```python perspective_fns = [root_cause_trace.real_validator_perspective(...) for name in (...)] judgment = root_cause_trace.judge_root_cause(perspective_fns) ``` 改为: ```python 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 个,未来场景可以传不同数量而不需要改动这两个函数本身。