# Review Loop 协议:从 Supervisor Retry 到蜂群交叉验证(issue #11) > 状态:**已接入在线 finalize 路径(去中心化重构 P5)**。`main.py` 新增 `review_decision` WS 分支与 `handle_review_decision`(收集 ≥2 同伴独立评审)+ `run_cross_review`(聚合仲裁→分歧记录→拒绝则 reopen 返工目标 + 归因),在 `refresh_swarm_run_status` 中先于单 critic Master 评审。feature flag `ENABLE_CROSS_REVIEW`(构建期;cutover 取代 Master 评审)。集成测试 `scripts/test-swarm-cross-review.py`,模块单测 `scripts/test-cross-review.py`。 > > 对应 issue #11 —「Review Loop 等价于 Supervisor Retry:评审重做机制未形成蜂群协同验证闭环」。 > > 实现:`orchestrator/cross_review.py`(纯模块,仅依赖标准库)。单测:`scripts/test-cross-review.py`。 > 配套:[`../benchmark/swarm-metrics-schema.md`](../benchmark/swarm-metrics-schema.md)(`P_rework` / `Reward`)、`orchestrator/main.py`(现有评审循环)、`orchestrator/master_agent.py`、`orchestrator/planner.py`。 ## 1. 现状:今天的评审循环是「单评审 + 重试」 当 `ENABLE_REVIEW_LOOP=1` 时,`orchestrator/main.py` 在 run 完成前调用一次主控评审(`maybe_run_review_cycle`): 1. `master_agent.review_and_decide(objective, tasks, results)` 委托 `planner.review(...)`; 2. `planner.review` 用**单个** LLM(或无模型时的启发式一致性检查)返回 `{accepted, summary, retry_tasks}`; 3. 若 `accepted=False`,编排器把 `retry_tasks` 里的任务 `reopen_task` 重新入队,`review_cycles += 1`,run 退回 `running`,受 `MAX_REVIEW_CYCLES`(默认 2)约束; 4. 重做完成后再次 finalize;预算耗尽或无可重做项即接受。 **本质:这是一个 Supervisor Retry。** 由**单一权威**(主控)判 pass/fail 并指派重做,没有第二个独立意见、没有记录分歧、没有结构化的「为什么重做 / 谁引入的缺陷」。因此: - 不构成蜂群式**交叉验证闭环**(cross-validation)——验证仍是中心化的一票否决; - 无法为基准 `P_rework`(见 §4)提供**可归因**的输入:每次重做只是一个无差别的 retry 计数,无法区分是需求 / 实现 / 测试 / 文档 / 协作哪一环引入。 ## 2. 目标:多评审交叉验证 + 重做归因 `orchestrator/cross_review.py` 提供一个**纯协议层**(无 Redis / 无 WebSocket / 无模型调用),把「单评审 pass/fail」升级为「≥2 独立评审 → 检测分歧 → 仲裁 → 结构化重做归因」: | 能力 | 今天(Supervisor Retry) | 交叉验证(本模块) | |---|---|---| | 评审者数量 | 1(主控) | ≥ 2 独立评审者(`aggregate_reviews` 强制) | | 分歧 | 不存在概念 | `disagreement` 显式检测并记录 | | 仲裁 | 主控单方裁定 | `majority` / `weighted`,平票安全偏向拒绝 | | 证据 | `summary` 一行 | `evidence` / `failed_criteria` / `affected_tasks` 结构化 | | 重做归因 | 仅 `retry_tasks` | `rework_reason` / `root_cause` / `source_task_id` / `introduced_by_agent_id` | | 基准输入 | 无差别 retry 计数 | 按 `ReworkCategory` 分类的 `P_rework` 输入 | ### 2.1 数据结构 - **`ReviewDecision`**(单评审者的结构化裁决) - `verdict`:`"pass"` / `"fail"`(构造时校验,非法即 `ValueError`); - `reviewer_agent_id`(必填)、`evidence[]`、`failed_criteria[]`、`affected_tasks[]`、`recommended_rework[]`; - `confidence`(自评 0–1)、`weight`(仲裁权重,如角色信任 / 资历)、`summary`。 - **`AggregatedVerdict`**(交叉验证结果) - `accepted`、`method`(实际使用的仲裁法)、`disagreement`、`pass_votes`/`fail_votes`、`rework_targets[]`、`decisions[]`、`summary`。 - **`ReworkAttribution`**(一次重做的归因) - `target_task_id`、`rework_reason`、`root_cause`(`ReworkCategory`)、`source_task_id`、`introduced_by_agent_id`、`detected_by_agent_id`、`evidence[]`。 - **`ReworkCategory`**:`requirement` / `implementation` / `test` / `doc` / `collaboration` / `unknown`。 - `unknown` 是显式取值:**无信号不伪造原因**(组织诚信规则 #9)。 ### 2.2 仲裁规则(`aggregate_reviews`) 1. **强制 ≥ 2 评审者**——单评审者就是 Supervisor Retry,不是交叉验证,少于 2 抛 `ValueError`; 2. **检测分歧**:pass 与 fail 同时出现 → `disagreement=True`,并写入 `summary`; 3. **仲裁**: - `majority`:fail 多于 pass → 拒绝;**平票安全偏向拒绝**(绝不静默接受分裂裁决); - `weighted`:比较 pass 侧与 fail 侧的 `Σ(weight·confidence)`,重侧胜,平局 → 拒绝; 4. **合并重做目标**:取所有 **fail 评审者** 的 `recommended_rework ∪ affected_tasks`,去重保序; 5. **接受条件**:仲裁非拒绝 **且** 无重做目标。 ### 2.3 重做分类(`classify_rework`) 确定性关键词打分(与 `planner._heuristic_consistency_check` 同族,可解释、无模型): - 各 `ReworkCategory` 有关键词信号集;命中即加分,最高分胜; - **分歧偏置**:当 `disagreement=True` 时,`collaboration` 既 +1 票**又赢平票**——评审者分裂本身就是跨专家一致性缺口的证据; - 全无信号 → `unknown`(不伪造)。 ## 3. 接入现状(已无条件接入,取代单评审) > 同伴交叉评审是蜂群**唯一**的评审路径,**无开关**:去中心化重构已**删除**单 critic 主控评审环(`maybe_run_review_cycle` / `review_loop_enabled`)。`main.py: run_cross_review` 在 `refresh_swarm_run_status` 中无条件运行(<2 评审时为 no-op,放行收敛)。 ### 3.1 评审者来源(≥ 2 独立意见) 同伴 Agent 经 WS `review_decision` 消息提交独立评审 → `main.py: handle_review_decision` 累积到 `run.metadata["reviews"]`;`run_cross_review` 在收齐 ≥ 2 条时 `aggregate_reviews(...)` 仲裁。`reviewer_agent_id` 取提交者,`weight` 可按角色信任赋值。`weighted` 法用 `weight·confidence` 比较,平票安全偏向拒绝。 ### 3.2 状态与事件 - **状态**:`run.metadata["cross_review"]` 存 `AggregatedVerdict.to_dict()`,`run.metadata["rework_attributions"]` 存 `[ReworkAttribution.to_dict()]`,与 `review_cycles` 并存(`MAX_REVIEW_CYCLES` 预算复用);拒绝则 `reopen_task` 返工目标、run 退回 `running`。 - **事件不进 Manager 流**:`review.*` / `rework.*` 的 payload builder 已实现但**不经 `emit_event` 外发**(未在 Manager `agent_callback.go` 注册;与 `swarm.health`/`convergence.*` 同策略,避免向订阅全部的回调投递未登记事件)。重开通过既有 `timeline.updated` 反映。登记后方可启用 Manager 侧发送。 ### 3.4 不变量 - 不改 Manager 面接口、HMAC 回调、审批链、计费 / 审计字段语义; - 信息素学习(`decision_engine`)、沙箱双门控(`quality`)等其它开关不受影响; - 纯协议层无副作用:可在无密钥、无 Redis、无 WS 的环境单测(见 §5)。 ## 4. 与基准 `P_rework` / `Reward` 的关系 基准执行层(`swarm-metrics-schema.md` §2): ``` R = ... − w₈·P_rework (w₈ = 0.05) P_rework = ReworkCount/TotalTasks×100 ``` schema 标注 `P_rework` 当前是「retry 派生、已知低估口径」。本模块把每次重做升级为带 `root_cause` 的 `ReworkAttribution`: - `ReworkCount` 可由 `rework.requested` 事件数(或 `rework_attributions` 长度)精确计数,而非从 `retry_count` 反推; - 可**按 `ReworkCategory` 分桶**(requirement/impl/test/doc/collaboration),让采集器区分重做归属的阶段,而不是一个无差别 retry 数; - `collaboration` 类重做(评审者分歧驱动)正是「蜂群协同验证」要暴露的信号——它对应 `S_collaboration`(§3 蜂群层)的反面证据。 > 接入采集是后续工作:本任务仅提供结构化输入与协议;`benchmark/collectors/run_collector.py` 的消费留待接入 PR,且须遵守 schema 的「无信号 → NaN,不伪造」口径。 ## 5. 测试 ``` ..\.venv\Scripts\python.exe scripts\test-cross-review.py ``` Hermetic(无 WS / 无模型 / 无 Redis),覆盖:单评审者被拒绝;两评审者分歧检测与 majority/weighted 仲裁;一致通过即接受;三评审者多数否决与重做目标去重;重做归因与 `ReworkCategory` 分类(含分歧偏置 collaboration、无信号 → unknown);四个事件 payload builder;非法 verdict / 缺评审者 id 的构造校验。