Files
Agentswarm/docs/swarm/convergence-protocol.md
T
gongzhiyongandClaude Opus 4.8 1288fd19d7
CI / tests (push) Failing after 15m3s
CI / guardrails (push) Failing after 15m3s
feat(swarm): 协作聚合收敛取代蜂后选优 + sandbox 用 pytest 验证
按 juejin 协作聚合模型重构收敛(取代 best-of-N 选优):
- 删蜂后选优(queen.py/test-queen.py)
- 新增聚合节点 result_aggregator.py:共享池收集→同文件 LLM/AST 整合→沙箱验证→单次落 main
- 质量驱动闭环:不达标打回迭代(AGGREGATE_ACCEPTANCE_THRESHOLD + MAX_REVIEW_CYCLES)
- agent 停 git 工作分支,产出走 task.result.files 共享池(AGENT_GIT_PUSH_ENABLED 默认 false)
- sandbox_runner 改用 pytest(原生支持 pytest 风格 class),修 stdlib runner 收集失败
- 文档同步重写为协作聚合模型

本地验证:产物仓单分支 main + 三函数完整 + pytest 12/12 pass_rate=100 一次达标。
影响:Swarm 收敛/聚合层;Manager/客户端契约不变(artifact字段/sequence/状态机;契约测试全过)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:03:00 +08:00

189 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 蜂群收敛协议(共识 / 冲突消解 / 终止函数)
> 状态:**已无条件接入 `refresh_swarm_run_status`(去中心化重构,无开关)**。每次 run 终态都产 `ConvergenceReport` 存 `run.metadata["convergence"]`,并把 `termination_reason` 附到 `timeline.updated`。**当前为解释性**——产出 `termination_reason`/共识/冲突,但**不覆盖 `run.status`**(今天 next_status 与报告对 completed/failed 一致);authoritative 状态覆盖为后续。
>
> 对应 Issue #12:「蜂群收敛机制缺失:缺少共识、冲突消解与终止函数,无法解释 Swarm 为什么结束」。
>
> 实现:`orchestrator/convergence.py`(纯计算,无 I/O)+ `main.py: compute_convergence_report`(装配 run_state,shadow 落库);单测 `scripts/test-convergence.py` + 集成测试 `scripts/test-swarm-convergence.py`。计划见 [decentralized-rework-plan.md](./decentralized-rework-plan.md)。
## 0. 问题:今天「为什么结束」是不可解释的
当前一次 run 走到终态,**唯一**判定在 `orchestrator/main.py:refresh_swarm_run_status`:
- 所有已知任务到达终态后,`next_status = "failed"`(任一任务 `FAILED`)否则 `"completed"`;
- 可选地(仅 `ENABLE_REVIEW_LOOP`)先跑主控评审,被拒则重开任务再 finalize。
也就是说,**今天的终止 = 「所有任务做完 + (可选)评审通过」**,没有:
- **共识模型**:没有任何「多少产出处于一致状态」的度量;
- **一等公民的冲突检测 / 消解**:两个 Agent 对同一文件写出不同内容、依赖未满足却标记完成等,均无显式识别;
- **可机读的终止原因**:`completed` / `failed` 无法区分「质量达标」「预算耗尽」「轮次上限」「被风险阻断」。
本协议补齐上述三者。**已无条件接入**(无开关),但当前**作为解释层**:产出报告 + `termination_reason` 存 run,**不覆盖** run 的终态语义(规则 #9:authoritative 覆盖为后续)。
## 1. 设计原则
- **纯函数、可测**:`evaluate_convergence(run_state)` 不做任何 I/O,不修改 run;调用方(`main.py: compute_convergence_report`)负责持久化。
- **无条件(无开关)**:`refresh_swarm_run_status` 每次终态都计算报告——本仓即 swarm 运行时,无 `ENABLE_*` 门控。
- **不伪造信号**(规则 #9):无质量评分 ≠ 通过;无测试信号 ≠ 失败;无预算 ≠ 充足。缺信号一律退出判定,不补 0、不补 100。
- **解释优先于干预**:本版**只解释**「为什么结束」,不自动改写终态、不自动 merge 冲突产物。
## 2. 数据结构(`orchestrator/convergence.py`)
```python
@dataclass
class ConvergenceReport:
status: ConvergenceStatus # running / converged / failed / blocked
termination_reason: TerminationReason | None # 终态必有,running 时为 None
consensus_score: float # [0,100],无冲突牵连的已完成产出占比
conflicts: list[Conflict] # 全部检出冲突
resolved_conflicts: list[Conflict] # 已消解
unresolved_risks: list[dict] # 阻断性风险 + 未消解硬冲突
budget_state: dict # 预算限额 / 消耗 / exhausted
quality_state: dict # fixture 评分 / 阈值 / reached
```
### 2.1 终止原因枚举(`TerminationReason`)
| 值 | 含义 | 触发输入 |
|---|---|---|
| `quality_reached` | 质量 / 验收门达标 | `quality.test_pass_rate ≥ acceptance_threshold`(默认 1.0) |
| `budget_exhausted` | token / 成本 / 时长预算耗尽 | `usage ≥ budget` 任一限额 |
| `max_rounds_reached` | 评审 / 重做轮次达上限 | `review_cycles ≥ max_review_cycles` |
| `risk_blocked` | 被未消解阻断性风险终止 | 输入阻断风险或未消解硬冲突 |
| `tasks_completed` | **诚实回退**:仅「所有任务做完」 | 以上均不适用——**即今天唯一真实信号** |
> `tasks_completed` 明确对应「现状」:当没有质量评分、预算、轮次或风险信号时,我们能说的**只有**「任务都做完了」,不冒充更强的解释。
### 2.2 冲突类型(`ConflictType`)
| 类型 | 检测器 | 判据 |
|---|---|---|
| `artifact_mismatch` | `detect_artifact_mismatch` | 两个已完成任务对同一 `files_modified` 路径记录了不同 `file_hashes`/`file_contents` 指纹 |
| `test_failure` | `detect_test_failures` | 任务结果 `tests_passed is False` 或 `test_pass_rate < 1` |
| `review_disagreement` | `detect_review_disagreement` | 主控评审 `accepted=False`(沿用 `master_agent.review_and_decide` 的 verdict 形状) |
| `dependency_inconsistency` | `detect_dependency_inconsistency` | 任务已完成,但其 `depends_on` 依赖在同 run 内未完成 |
## 3. 终止函数判定顺序
`evaluate_convergence(run_state)` 依次判断,**先匹配先返回**:
1. 无任务 / 任一任务处于 `pending|assigned|in_progress` → `RUNNING`(无 reason)。
2. 存在阻断风险或未消解硬冲突 → `BLOCKED` + `risk_blocked`。
3. 仅有 `blocked` 任务(无阻断风险) → `RUNNING`(与 `refresh_swarm_run_status` 中「blocked 但有活跃子任务保持 running」对齐)。
4. 任一任务 `failed` → `FAILED`,原因取(预算耗尽→`budget_exhausted`;否则轮次到顶→`max_rounds_reached`;否则 `tasks_completed`)。
5. 全部完成且预算耗尽 → `CONVERGED` + `budget_exhausted`。
6. 全部完成且轮次到顶 → `CONVERGED` + `max_rounds_reached`。
7. 全部完成且质量达标 → `CONVERGED` + `quality_reached`。
8. 其余(全部完成) → `CONVERGED` + `tasks_completed`(诚实回退)。
**不变式**:任何终态(converged/failed/blocked)必带且仅带一个 `termination_reason`;`running` 必为 `None`。单测逐条断言。
### 3.1 共识分(`compute_consensus_score`)
```
consensus = 100 × (未被任何冲突牵连的已完成任务数 / 已完成任务总数)
```
无已完成任务 → `0.0`。这是一个**可解释的真实代理量**(仅来自任务状态 + 检出冲突),不是模型主观打分。
### 3.2 消解(`resolve_conflicts`)——保守首版
- `artifact_mismatch`:仅当评审 `accepted=True`(评审隐式选定了胜出版本)才标记 resolved;否则 unresolved。
- `review_disagreement`:verdict 给出可操作 `retry_tasks` 时标记 resolved(蜂群可通过重开任务行动);空泛拒绝无可操作项 → unresolved。
- `test_failure` / `dependency_inconsistency`:视为硬阻断,本版**不自动消解**,留给重做循环或人工。
每个冲突就地写回 `resolved` 与 `resolution`(消解方式或无法消解的原因)。
## 4. 事件载荷构造器
按 `SwarmRuntime.emit_event(run, event_type, payload=...)` 约定,每个构造器返回 `(event_type, payload)`,payload 为 JSON 安全 dict:
| 函数 | event_type | 用途 |
|---|---|---|
| `event_convergence_started` | `convergence.started` | 收敛评估开始 |
| `event_conflict_detected` | `conflict.detected` | 每检出一个冲突 |
| `event_conflict_resolved` | `conflict.resolved` | 每消解一个冲突 |
| `event_consensus_updated` | `consensus.updated` | 当前共识分与冲突计数 |
| `event_convergence_reached` | `convergence.reached` | 终态成功,附 `termination_reason` |
| `event_convergence_failed` | `convergence.failed` | 终态失败/阻断,附 `termination_reason` |
> ⚠️ 上述六个 `event_type` **尚未登记进 Manager 事件契约**(heicode `agent_callback.go`)。在订阅或回调消费前,必须先按下方集成说明走 Manager 侧契约登记,否则属于未登记事件。
## 5. 测试
```
..\.venv\Scripts\python.exe scripts\test-convergence.py
```
模块级、无 WS/Redis/模型。覆盖:两个冲突产物 → 检出 artifact_mismatch + review_disagreement;review_disagreement 可消解、artifact_mismatch 未消解;产出带具体 `termination_reason` 的 `ConvergenceReport`;**断言每个终态都带 termination_reason**;断言至少一个冲突「检出 + 消解」闭环;以及五个终止原因各自的终态用例。全部 PASS。
## 6. 接入现状与诚实差距(规则 #9)
**已接入**(`main.py: compute_convergence_report` + `refresh_swarm_run_status`,无开关):每次 run 终态装配 `run_state`(tasks/budget/usage/quality/review_cycles)→ `evaluate_convergence` → 报告存 `run.metadata["convergence"]`,`termination_reason` 附到 `timeline.updated` 载荷。
**诚实差距**:
- **解释性,非权威**:报告**不覆盖** `run.status`(今天 next_status 与报告对 completed/failed 一致)。authoritative 模式(如 `BLOCKED`→置 run `blocked`)改变 Manager 面终态语义,须先过 `scripts/test-runtime-contract.py` + 契约评审——列为后续。
- **质量/预算/风险输入有条件**:`quality` 依赖 Group B fixture 评分(绑定 fixture 时);`budget`/`usage` 需 run 提供;`risks` 需上游注入。缺失时相应原因不触发,回退 `tasks_completed`(不伪造)。
- **消解为保守首版**:`convergence.py` 的冲突消解层不做自动选胜,硬冲突留待重做或人工。注意这与 §7 的**协作聚合**是不同层次:聚合节点(§7.2)对**互补**子产物做 LLM 合并属正常收口,不是「冲突消解」;冲突消解针对的是同路径不一致/测试失败等异常。互补聚合不依赖「选胜」。
- **收敛事件不进 Manager 流**:六个 `convergence.*`/`conflict.*`/`consensus.*` 事件**构建器已实现但不经 `emit_event` 外发**(未在 Manager `agent_callback.go` 注册;与 `swarm.health` 同策略,避免向订阅全部的回调投递未登记事件)。登记后方可启用 Manager 侧发送。`termination_reason` 以**新增可选字段**附在 `timeline.updated`,对旧消费方向后兼容。
## 7. 收敛 = 协作聚合(不是 best-of-N 选优)
> **理念来源**:去中心化蜂群范式(掘金《蜂群智能多 Agent 框架》理念,舆情分析案例)——「**个体简单、群体智能**」。涌现来自**分工协作 + stigmergy 间接协调**,**不是**多个独立解相互竞争后选一个最优。本节据此把旧的「蜂后 best-of-N 选最优」收敛模型**重写为协作聚合模型**。
### 7.0 两种收敛语义(先区分,再选默认)
蜂群里「收口」有两种本质不同的语义,必须分开处理:
| 语义 | 何时产生 | 收敛方式 | 是否本仓默认 |
|---|---|---|---|
| **互补聚合**(complementary aggregation) | 一个种子任务经**自主分解(#7)**铺成多个**互补**子任务(各做一块,产出不重叠) | **聚合合并**:从共享池读所有子任务产出 → 整合成一份完整产物 | ✅ **主路径** |
| **多解选优**(best-of-N selection) | 同一个**原子任务**被多 Agent **竞争(#8)**各自给出**可互换**的完整解 | 选优:按质量排序取胜出解 | 仅竞争原子任务时的次要路径 |
> 文章主推**前者**:舆情案例里「情感分析 Agent」「趋势分析 Agent」产出**互补**,由「报告生成 Agent」**汇总所有分析结果**成一份报告——这是合并,不是选优。本仓 fan-out 的产物来自**自主分解的互补子任务**,因此默认走聚合合并。选优只在「同一原子任务有多个可互换完整解」时才适用,属次要路径。
### 7.1 共享结果池:中间产物不进交付仓
对齐文章 `SwarmEnvironment.results`(agent 把产出 `append` 进共享池):
- 每个子任务完成时,产物以 `task.result.files` 写入**共享结果池**(Redis run 状态里的任务结果,对应文章的 `environment.results`),**而不是**直接提交进交付(git)仓库。
- 共享池是聚合节点的**唯一输入源**:聚合前没有任何子任务分支落到产物仓。
- **产物仓永远只有 `main` 一份,无 per-agent 工作分支**——子任务产出停留在共享池(运行时状态)里,落仓是聚合之后的**单次**动作(§7.3)。这也消除了「artifact 碎在各结果分支、需要事后 merge」的旧问题。
### 7.2 聚合节点(ResultAggregator,扮演「报告生成 Agent」)
对齐文章的**专门聚合节点** + `ResultAggregator`(合并结果 / 按质量排序 / 格式转换)+ DAG `mode=wait_all`:
- **wait_all 栅栏**:聚合节点是 DAG 的汇聚点,**等所有上游互补子任务到达终态**后才触发——即文章舆情案例里报告节点 `mode=wait_all` 等情感/趋势分析全部完成。本仓以「该种子下所有互补子任务均完成」作为 wait_all 条件(无活跃 `pending|assigned|in_progress` 互补子任务)。
- **从共享池读取**:聚合节点从 `SwarmEnvironment.results`(共享结果池)取出该种子下所有子任务的 `task.result.files`,**不**重新执行子任务。
- **LLM 整合成完整产物**:把互补产出(各子任务的文件/片段)交 LLM **整合**为一份完整、自洽的产物(代码:合并到一致的文件树并消解接口/依赖;文档:汇总成一篇)。这是「报告生成 Agent」职责的实现——`master_agent.synthesize` 作为汇总工具在此复用(仅文字汇总能力;代码整合的边界见 §7.5)。
- **跑测试验证**:整合后的完整产物跑 held-out / 共享测试(沙箱见下方门控)做验收,而**不是**对每个候选解分别打分选优。
- **单次落 main**:验证通过后,聚合产物**一次性**提交到产物仓 `main`,并发**单一** `artifact.created`(见 §7.4 契约一致性)。
> `ResultAggregator` 三职责映射:**合并** = LLM 整合互补产出;**按质量排序** = 仅在 §7.0「多解选优」次要路径下对可互换解排序;**格式转换** = 把异构子产物归一为交付格式。主路径用「合并」,不用「排序选优」。
### 7.3 落 main 的单次提交
- 聚合 + 验证通过 → orchestrator 把整合后的完整产物**一次** push 到产物仓 `main`。
- 全程**无 per-agent 工作分支**、无多分支后置 merge:子任务产出活在共享池,分支层面只有 `main`。
- 失败隔离仍适用:个别互补子任务失败不必拖垮整个 run;聚合节点对**已到达共享池**的产出做整合,缺失部分按 `tasks_completed` / 冲突语义如实反映(不伪造,规则 #9)。
### 7.4 与冻结契约的一致性(FROZEN v1,不得违背)
聚合收敛流程**完全落在**现有冻结契约内,不新增/不改字段、类型、状态机:
- **单一 artifact**:聚合后**只**发一个 `artifact.created`(整合产物),扁平字段 `{uri, checksum, task_id, size_bytes?, created_at}` 不变。子任务中间产物**不**各发 `artifact.created`(它们在共享池里,不是交付物)。
- **sequence 递增**:聚合相关回调沿用 per-swarm 严格递增 `sequence`,无空洞。
- **状态机不变**:聚合是 run 收敛前的内部步骤,不引入新 run/task 状态;终态仍由 §3 终止函数判定(`completed`/`failed` 语义不变)。
- **13 类客户端事件冻结**:聚合不新增客户端可见事件类型;wait_all/合并属内部编排,对客户端仅体现为既有 `task.*` 与最终 `artifact.created` + `swarm.completed`。
### 7.5 诚实差距(规则 #9,不主张未实现的)
- **本节为目标模型**:上述聚合收敛是按文章理念重写的**协作聚合设计**;与之相对,旧的 **best-of-N 选优**(`queen.aggregate_run` → `score_candidates` → `select_best`,winner 标 `deliverable.selected`)**已废弃为主路径**,仅在 §7.0「多解选优」次要语义下保留(同一原子任务多个可互换解时排序取胜出)。文档以聚合为主路径,不再把选优当作默认收口。
- **代码整合非纯文字汇总**:`master_agent.synthesize` 当前仅文字汇总;把互补**代码**子产物整合成一致文件树(消解接口/import/依赖冲突)是更强能力,列为后续实现,不冒充已完成。
- **落 main 待端到端**:聚合产物 push 到 `main` 需 orchestrator 的 git CLI + 凭据持久化打通,端到端待验证。
- **测试沙箱门控**:聚合后跑验证测试依赖沙箱隔离双门控(`ENABLE_QUALITY_EVAL` + `HEICODE_SANDBOX_ISOLATED`);未确认隔离则不执行测试、不伪造分数(unscored ≠ 0/100,规则 #9)。
- **convergence 全 authoritative(后续)**:让 `ConvergenceReport.status` 完全覆盖 `run.status` 动 Manager 终态语义,仍列为后续(同 §6)。
- **防跨 run 抢夺(P0 / #8,保留)**:`extract_swarm_from_agent` / `_agent_belongs_to_run`——agent 只能竞争/认领自己 run 的 task;`swarm_dispatch` 过滤、`handle_task_bid/yield/takeover` 拒绝跨 run(`cross_run_denied`)。此为竞争路径的隔离保证,与聚合主路径并存。