# 50 · 竞争候选 + 对抗批评 + 综合——新图骨架详细设计草稿 > **状态**: 📝 **设计草稿,不代表已拍板**。这是 [49 号文档](./49-antigravity-inspired-parallel-verification-proposal.md) > §二"提案 A"的展开——49 号只给了三节点拓扑草图,本文档补上骨架 JSON 形状、命名、critic/synthesis > 分工、预算风险的具体数字。**49 号文档已明确:没有真实场景验证价值前不建议立项写代码**,本文档只是 > 把"如果要做,该怎么做"想清楚,供下一步决定要不要排期时参考,不是开工指令。 > **日期**: 2026-09-04 > **前置阅读**: [49 号文档](./49-antigravity-inspired-parallel-verification-proposal.md) §二(提案动机与 > 场景边界)、[27-P4](../../done/system-design/27-p4-parallel-and-artifacts.md)(并行执行引擎,本设计的地基)、 > [27-naming-conventions.md](./27-naming-conventions.md)(命名规则) --- ## 一、跟现有骨架的本质区别(复述 49 号 §2.1,作为设计前提) `executor-fanout.json` 是**分解型** fan-out:`split` 把任务拆成互不重叠的子任务分给不同分支,`merge` 做的是**拼接**(把互补的子产物合并成一个整体)。竞争候选是**同型** fan-out:N 个分支做的是**同一件事**, 下游节点要做的是**评判 + 择一/融合**,不是拼接。两者拓扑相似、语义完全不同——这是本设计最容易踩错的 地方,写骨架 JSON 时必须在 `whenToUse`/`dataContractNotes` 里把这条区别说透,否则 `workgraph-planning` 选型时会混淆(现有 `executor.json` vs `deliverable.json` 就发生过一次真实混淆事故,见 §三.2 引用)。 --- ## 二、骨架 JSON 形状草稿 沿用 `system/work-graph-templates/README.md:129-165` 定义的字段结构,不发明新字段。 ```jsonc // system/work-graph-templates/candidate-council.json(暂定名,未定案) { "id": "candidate-council", "label": "候选方案竞争评审", "whenToUse": "验收标准客观可判定,但实现路径不唯一、单次尝试容易收敛到局部最优解的子任务。典型信号词:\ '设计一个策略/方案'、'优化/选型'、'有几种做法都值得试'。跟 executor-fanout 的区别:executor-fanout 是\ 把一个任务拆成不重叠的子任务分给不同分支各自完成(分解型);本骨架是让多个分支各自独立给出同一个目标\ 的完整方案,再由 critic 评判、synthesis 择优或融合(同型竞争)。不确定任务是该分解还是该竞争时,默认\ 选分解型(executor-fanout)——竞争型成本是 N 倍,只在"真的不确定哪条路线更优"时才划算。", "ownerOrgNode": "{{orgNode}}", "nodes": [ { "nodeName": "{{orgNode}}-candidate-a", "systemAgentId": "{{candidateAgent}}" }, { "nodeName": "{{orgNode}}-candidate-b", "systemAgentId": "{{candidateAgent}}" }, { "nodeName": "{{orgNode}}-critic-a", "systemAgentId": "candidate-critic" }, { "nodeName": "{{orgNode}}-critic-b", "systemAgentId": "candidate-critic" }, { "nodeName": "{{orgNode}}-synthesis", "systemAgentId": "candidate-synthesis" }, { "nodeName": "{{orgNode}}-validator", "systemAgentId": "{{validatorAgent}}" } ], "dataContractNotes": "候选分支数按实际任务增减(本文件给 2 路起手式,同 executor-fanout 的先例)。\ 每路候选各配一个独立的 critic 节点——critic 只评判、不产出最终方案,也不是候选自己的下游必经步骤,\ 是平行的另一个节点。critic 产出的评语(kind=artifact)与候选产出(kind=artifact)都要能被 synthesis \ 节点区分来源,建议候选各自产出走 contract kind(分支间不冲突,同 executor-fanout 对 split 节点的处理),\ critic 评语走 artifact kind。千万不要把 critic 和 synthesis 合并成一个节点——qa-judge 早期版本就是\ "既提修改意见又下终态判决"合一设计,导致过一次真实的无限循环事故(20 次调用/31 分钟才熔断,见\ deliverable.json 的反模式记录),这条教训必须在本骨架延续。", "edges": [ { "from": "{{orgNode}}-candidate-a", "to": "{{orgNode}}-critic-a", "condition": "success", "produces": ["contract"] }, { "from": "{{orgNode}}-candidate-b", "to": "{{orgNode}}-critic-b", "condition": "success", "produces": ["contract"] }, { "from": "{{orgNode}}-critic-a", "to": "{{orgNode}}-synthesis", "condition": "success", "produces": ["artifact"] }, { "from": "{{orgNode}}-critic-b", "to": "{{orgNode}}-synthesis", "condition": "success", "produces": ["artifact"] }, { "from": "{{orgNode}}-synthesis", "to": "{{orgNode}}-validator", "condition": "success", "produces": ["artifact"] }, { "from": "{{orgNode}}-candidate-a", "to": "{{orgNode}}-candidate-a", "condition": "fail", "edgeKind": "backward" }, { "from": "{{orgNode}}-candidate-b", "to": "{{orgNode}}-candidate-b", "condition": "fail", "edgeKind": "backward" }, { "from": "{{orgNode}}-synthesis", "to": "{{orgNode}}-candidate-a", "condition": "fail", "edgeKind": "backward" } ] } ``` ⚠️ 以上是**未经 `validateGraph`/`work-graph-skeletons.test.js` 实测**的草稿,backward 边、fail 兜底边 的具体挂法(尤其 synthesis 失败该退回哪个候选,还是退回两个候选的公共上游)需要按 `workgraph-planning/ agent.md` 约束三(每个非入口节点至少一条不带 `failReason` 的兜底边)与约束五(backward 边判定)逐条 过一遍才能定案,本文档只给方向,不做最终结构校验。 ### 关键设计决策:critic 和 synthesis 必须是两个不同节点 这不是本文档凭空要求,是仓库里**已有真实事故记录**——`system/agents/qa-judge/` 是仓库唯一一个"既提 修改意见又下终态判决"的合一设计先例,`system/work-graph-templates/deliverable.json` 的 `dataContractNotes`(第 6 行)明确记录了一次真实故障:`org-hunt` 套 `executor.json` 后误选中 `qa-judge`,critic/judge 合一导致无限重复确认,最终判 `infra_flake`,烧了 **20 次调用 / 31 分钟**才 被熔断拦下。同一份文档同时留了正面先例:`validator-triager`(只归因不判定)与 `benchmark-validator` (只打分不算总分,算术交给不跑 LLM 的 `goal-contract.js`)——这两个都是"评价类角色拆分"的既有范式, 新的 critic/synthesis 分工应该照这个模式设计:**critic 只给评语和分数,不下终态;synthesis 只做择优/ 融合决策,不重新评分**。 --- ## 三、命名(按 27-naming-conventions.md §3.1/§3.2) | 层 | 名称 | 依据 | |---|---|---| | Work Graph 节点 | `{父org节点全名}-candidate-a`/`-candidate-b`、`-critic-a`/`-critic-b`、`-synthesis` | §3.1:Work Graph 节点带父节点全名前缀 | | System Agent 本体(candidate) | 不新增,复用宿主领域已有的实现类 agent(比如 `org-coder` 场景下就是 `org-coder` 本体本身,跑两次、走两个不同 session scope,见 27-P4 §4.2 的多实例隔离机制) | 候选不是新角色,是同一个实现角色跑两次不同方案,不需要新本体 | | System Agent 本体(critic) | `candidate-critic`(不带前缀) | §3.2:本体不带前缀,避免同一本体因前缀被迫复制成多份 | | System Agent 本体(synthesis) | `candidate-synthesis`(不带前缀) | 同上 | **候选节点为什么不需要新本体**:候选做的是"用不同方法解决同一个问题",这本质上是**同一个实现角色** (比如 `org-coder`)被调用两次、每次给不同的方法论提示("方案 A 用递归实现,方案 B 用迭代实现"这类 差异化指令可以放在节点的任务描述里,不需要两个不同的本体)。这与 27-P4 §4.2 已经落地的"同一 system agent 的多实例隔离"(`session-pool.js` 的 `scope` 参数)正好对上——两路候选各自拿到独立 session, 互不污染上下文,机制已经具备,不需要新代码。 **避坑**:`system/work-graph-templates/README.md:210-212` 列出的 6 个命名冲突 id(`agent-creator`/ `digester`/`evolver`/`evolver-youtube-crawler`/`innovator-*` 系列)不能用;`critic`/`synthesis` 这两个 职能词目前在 27-NAMING 速查表里没有先例,需要在正式立项时补进 §七 速查表,避免未来又冒出"judge"/ "reviewer"这类近义词造成混淆(`triager` vs `validator` 已经因为语义邻近踩过一次坑,见 27-NAMING §3.5)。 --- ## 四、`workgraph-planning` 选型判据(新增,未定案) 参照 `planner.json` 五节点裁剪判据表(`workgraph-planning/agent.md:181-233`)的成功模式——那张表训练 前只给"你被允许裁剪"这句空泛授权时打分 34/100,训练出具体的信号+阈值判据表后才到 100/100。新骨架的 选型判据必须避免重蹈"同义反复式空泛授权"的覆辙,至少要在两处补齐: 1. **骨架自身的 `whenToUse` 字段**(已在 §二草稿里写了初版,需要经过同款训练/评审流程收敛,不能只 凭这份草稿直接上生产)。 2. **`workgraph-planning/agent.md` 新增一条硬规则**,给出"要不要开竞争候选、开几路"的具体信号词和 阈值——候选参考信号: - S1:验收标准是否已经客观可判定(有明确的正确性/性能基准,不是主观描述)——`否` 则不该开竞争, 应该先把验收标准搞清楚,这是另一个问题。 - S2:该子任务此前是否已经因为"方案有问题需要打回重做"触发过 backward 边——如果是,说明单次尝试 成功率确实低,竞争候选可能划算;**如果从未撞过**,不该凭空开竞争(这条呼应 49 号 §2.4 的开放 问题 4:"目前没有实证需求")。 - S3:候选数 N——没有代码层硬顶(见 §五),判据必须给出建议范围(比如 2-3 路),避免 LLM 现场 漫无边际地开出过多分支。 **这条判据表本身要不要现在写、写成什么样,属于"立项时才需要拍板"的内容,本文档只指出"必须有"和 "不能是空泛授权"这两条硬要求,不代本节写最终版本。** --- ## 五、预算与并发风险(量化数字,来自 2026-09-04 代码核实) - `advanceBatch` 的并发上限取 `run.max_concurrency`,缺省 `DEFAULT_MAX_CONCURRENCY = 5` (`server/services/work-graph-db.js:62`)。 - `checkBudget`(`server/services/graph-engine.js:3029-3044`)只在**取出候选之后、真正 claim 之前** 查一次,判定的是"当前已花是否达到上限",**不是"这一批 claim 之后会不会超支"**。也就是说,如果预算 只剩 3 次调用余量,但这一轮一次性取出了 5 个 ready 候选(默认并发上限),5 个都会被 claim 并真正 跑起来——**实际消耗可能超出预算上限,超出幅度最多是 `maxConcurrency - 1`(默认最多多花 4 次调用)**, 下一轮 `checkBudget` 才会拦下后续批次。 - 对竞争候选设计的直接影响:如果一次性并行展开 N 路候选(N ≤ 5 会被一次性全部 claim),预算消耗是 N 倍(候选节点 + 各自的 critic 节点,粗算一次三节点竞争至少是 6 个 agent 调用:2 候选 + 2 critic + 1 synthesis + 后续 validator),且这个倍数消耗**不会被预算闸门提前拦住**。立项时如果要控制这个 风险,需要考虑给竞争型子图单独设一个更保守的 `max_concurrency`,或者在 `workgraph-planning` 的 选型判据里对候选数 N 直接设建议上限(见 §四 S3)。 --- ## 六、明确不做(本文档范围外) - 不实现具体的 `candidate-critic`/`candidate-synthesis` agent 人设(`agent.md`/`tools.md`)——那是 立项后的工作,本文档只给分工原则(critic/synthesis 分离)。 - 不修改 `work-graph-templates/README.md` 或 `27-naming-conventions.md` 补录新职能词——留给立项时做。 - 不实现 §四的选型判据表——只给了判据应该覆盖的信号维度,具体阈值/文案需要走跟 `planner.json` 五节点 判据表同款的训练/评审流程才能定案,不能直接照抄草稿上生产。 - 不解决"候选数 N 没有代码层硬顶"这件事本身——如果立项,这可能是最先要补的安全阀,但本文档只指出 风险,不给实现。 --- ## 七、与既有文档的关系 - [49-antigravity-inspired-parallel-verification-proposal](./49-antigravity-inspired-parallel-verification-proposal.md): 本文档是它 §二 提案 A 的展开,结论一致——没有真实场景前不建议立项。 - [27-P4](../../done/system-design/27-p4-parallel-and-artifacts.md):本设计完全建立在它已完成的并行执行能力(`advanceBatch`/ 多实例 session 隔离)之上,不需要 27-P4 补任何代码。 - `system/work-graph-templates/deliverable.json`:critic/synthesis 分离的设计依据直接来自它记录的 `qa-judge` 真实反模式事故,本文档延续同一条教训。 - `system/agents/validator-triager/`、`system/agents/benchmark-validator/`:评价类角色"归因/打分/ 算术判定三者分离"的既有范式,是设计 critic/synthesis 分工时的直接参照。