按 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>
189 lines
17 KiB
Markdown
189 lines
17 KiB
Markdown
# 蜂群收敛协议(共识 / 冲突消解 / 终止函数)
|
||
|
||
> 状态:**已无条件接入 `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`)。此为竞争路径的隔离保证,与聚合主路径并存。
|