复审整改(PR #26):删死开关 helper、文档对齐"无条件"、swarm.health 改内部、#6 降为 Refs

回应 Fasthei 终审三点:

1) [P1 文档/代码冲突 + 死代码] 删除从不被调用的 *_enabled() helper(autonomous_tasks.proposals_enabled
   / task_competition.task_competition_enabled / convergence.convergence_report_enabled)及其
   import os;模块 docstring 与四份协议文档(autonomous-task-generation / task-competition-protocol
   / review-loop-protocol / convergence-protocol)从"默认关/未接入/待 PR/cutover 转无条件"全部改为
   "无条件接入(无开关)",删除引用死 helper 的过时集成代码样例;同步删除三个模块单测里的
   "flag default OFF" 断言。

2) [P1 验收] #6 "Closes" 降为 "Refs":#6 DoD 需 ARB 决策记录链接,当前只有 owner 指示断言、无链接。
   product-positioning.md 改为如实记录决策来源(owner 指示 + 本 PR + 文档)并把"补 ARB 记录链接(或
   owner 明确接受断言)"列为关闭 #6 的前置;纠正其"flag 门控、默认行为不变"的过时表述(重构已无条件)。

3) [P2 契约卫生] assess_swarm_health 不再 emit_event("swarm.health")(避免向订阅全部的 Manager 回调
   投递未注册事件);改为存 run.metadata["health"] + 内部 health_log。test-swarm-guard 相应断言
   "无 swarm.health 外发 + 内部 health_log 已记"。

本地受影响 11 套全绿。影响范围:agent_swarm(orchestrator 模块/文档/测试);不改 Manager↔Swarm 契约。

Refs #6
Refs #7
Refs #8
Refs #11
Refs #12
Refs #18

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Songhaoz666
2026-06-10 16:48:27 +08:00
co-authored by Claude Opus 4.8
parent f281370209
commit f2662e4268
13 changed files with 69 additions and 292 deletions
+5 -2
View File
@@ -1,6 +1,8 @@
# 产品定位说明(Product Positioning)
> 状态:**已选定 Path B 并启动去中心化重构(owner 指示,分阶段进行)**。本文回应工单 #6「蜂群模式产品定义偏差」:当前实现仍为 **Master 中心化**,heicodeDocs 蜂群定义为**弱中心**;现按 **Path B** 推进到弱中心自组织(计划见 [swarm/decentralized-rework-plan.md](./swarm/decentralized-rework-plan.md))。重构全程 flag 门控、默认行为不变,直至各阶段验收后再切默认。**形式 ARB 记录待补**;两条路径取舍保留如下以备追溯。
> 状态:**已选定 Path B 并完成去中心化重构(owner 指示)**。本文回应工单 #6「蜂群模式产品定义偏差」:原实现为 **Master 中心化**,heicodeDocs 蜂群定义为**弱中心**;现已按 **Path B** 重构为弱中心自组织并设为**唯一行为**(无开关;计划见 [swarm/decentralized-rework-plan.md](./swarm/decentralized-rework-plan.md))。
>
> **决策记录与 #6 关闭口径**:路径选择的依据 = owner 在工单/会话中的指示 + 本重构 PR + 本文 + [swarm-definition-gap.md](./swarm-definition-gap.md)。但**尚无形式 ARB 决策记录的链接**。故重构 PR 以 **`Refs #6`**(不自动关闭)引用本单;待补 ARB 记录链接(或 owner 明确接受上述断言为准)后由 owner 关闭 #6。两条路径取舍保留如下以备追溯。
>
> 配套:[`swarm-definition-gap.md`](./swarm-definition-gap.md)(逐条差距与文件/行号引用)、[README.md](../README.md)(能力边界表)、[`integration/runtime-contract.md`](./integration/runtime-contract.md)、[`benchmark/IMPORTANT-metric-coverage-gaps.md`](./benchmark/IMPORTANT-metric-coverage-gaps.md)。
@@ -32,7 +34,8 @@
## 4. 待裁定项(ARB 决策,非本仓自决)
- [x] **已选定 Path B(owner 指示,分阶段重构)** —— 见 `swarm/decentralized-rework-plan.md`。形式 ARB 记录待补;仍可在任一阶段验收门回退(重构 flag 门控、默认未启用)。
- [x] **已选定 Path B(owner 指示)并完成重构** —— 见 `swarm/decentralized-rework-plan.md`(swarm 已是唯一行为,无开关)。
- [ ] **关闭 #6 前置**:补一个**形式 ARB 决策记录链接**(或 owner 明确接受「owner 指示 + 本 PR + 本文」为决策记录)。在此之前重构 PR 用 `Refs #6` 不自动关闭。
- [ ] 若选 Path A:确认收敛后的对外定位口径与 heicodeDocs 标准无冲突(如有冲突,按 CLAUDE.md 输出冲突点交标准源裁定)。
- [ ] 若选 Path B:确认 #7/#8/#9/#11/#12 的范围、依赖顺序与 Benchmark 验收门(基线对比,见 `benchmark/IMPORTANT-metric-coverage-gaps.md` §5)。
+7 -83
View File
@@ -11,8 +11,8 @@ Issue #7:执行单元(Agent)在工作中会**发现**当前共享状态缺
**关键边界(务必先读)**:
- **是 Agent 在提案,不是 Master 在生成任务。** Master/planner 的自顶向下分解走的是既有路径(`master_agent.plan` + `ENABLE_PLANNER_FALLBACK`),与本机制**互不混用**。本路径产出的提案恒带 `source="agent_proposed"` 与 `proposed_by_agent_id`;`assert_not_master_origin()` 在运行时拒绝任何 proposer/trigger 名含 `master`/`planner` 的提案,防止 Master 借此路径「洗白」任务来源、绕过 planner 契约。
- **纯机制、默认关闭。** 整个能力由环境开关 `ENABLE_AGENT_TASK_PROPOSALS`(默认 `false`)门控;关闭时运行时行为与改动前逐字节一致(与 `ENABLE_PLANNER_FALLBACK` / `ENABLE_REVIEW_LOOP` / `ENABLE_SUBTASK_HANDOFF` 同一约定)。
- **是 Agent 在提案,不是 Master 在生成任务。** Master/planner 的自顶向下分解路径**已在去中心化重构中删除**;本机制(自底向上提案)是蜂群**唯一**的分解路径。提案恒带 `source="agent_proposed"` 与 `proposed_by_agent_id`;`assert_not_master_origin()` 在运行时拒绝任何 proposer/trigger 名含 `master`/`planner` 的提案,防止 Master 借此路径「洗白」任务来源。
- **无条件接入(无开关)。** 该机制是蜂群运行时的固有行为,**无 `ENABLE_*` 开关**(本仓即 swarm 运行时,模式选择在仓外);`main.py` 的 WS `task_proposal` 分支与 `handle_task_proposal` 始终生效。
- **Agent 只提案,编排器才决策。** Agent 给出自评置信度 `proposal_confidence`,但「收 / 拒 / 并」由编排器侧的策略函数 `review_proposal` 依据置信度阈值、提案预算、与现存任务去重来裁决——不调用任何模型。
## 2. 数据模型(`orchestrator/autonomous_tasks.py`)
@@ -69,91 +69,15 @@ proposed ──accept──▶ accepted ──ingest──▶ (新建 PENDING
事件类型常量:`EVENT_SUBMITTED` / `EVENT_ACCEPTED` / `EVENT_REJECTED` / `EVENT_MERGED`。
## 4. 已实现 vs. 未集成(诚实声明,规则 #9)
## 4. 接入现状(已无条件接入)
**已实现且测试覆盖**:`TaskProposal` / lineage / 状态枚举、`review_proposal` 三路裁决(置信度 / 预算 / 去重)、`ingest_accepted_proposal` 的 spec 映射与 lineage 透传、生命周期事件构建、`assert_not_master_origin` 守卫、`ENABLE_AGENT_TASK_PROPOSALS` 开关读取。
**已落地于 `orchestrator/main.py`**(去中心化重构,无开关):WS `task_proposal` 分支 → `handle_task_proposal`:构造 `TaskProposal` → `review_proposal`(置信度/去重合并/每 run 预算 `AGENT_PROPOSAL_BUDGET`)→ ACCEPT 即 `task_queue.create_task` 入队为真实 PENDING 任务(带 lineage、`source=agent_proposed`、发标准 `task.created`);提案生命周期存 `run.metadata["proposals"]`(**内部遥测,非 Manager 事件**)。这是蜂群唯一的分解路径。集成测试 `scripts/test-swarm-autonomous.py`。
**尚未集成(需集成方按 §5 手工接入)**:
模块本身(`autonomous_tasks.py`)仍是纯策略/数据层(无 Redis/WS/模型):`TaskProposal`/lineage/状态枚举、`review_proposal` 三路裁决、`ingest_accepted_proposal` 映射、生命周期事件、`assert_not_master_origin` 守卫。
- 未接入 `orchestrator/main.py` 的实时 WS 主循环——目前没有 `task_proposal` 入站分支,Agent 端也未发送该消息。
- 未接入派发器——ACCEPTED 提案产出的 spec 尚未被任何代码实际 `create_task` 入队。
- `TaskProposal` / `SwarmRun` 的持久化字段未添加(本次按硬约束只新建文件,不改共享文件)。
- `expired` 状态的过期判定(按预算/时限)逻辑未实现,仅定义了枚举。
- 提案事件**不是**已注册的 Manager 事件,按内部遥测处理(与 `peer_message`、ACO `decisions` 同策略),集成方需决定映射为既有 HM 事件还是仅作 telemetry。
**仍为后续**:`expired` 过期判定仅定义枚举未实现;`Task`/`SwarmRun` 未加持久化提案字段(提案存于 `run.metadata`);真实 Agent 执行器基于 LLM 决定「提哪些子任务」为后续(`scripts/stub_agent.py` 已演示种子→提案)。
## 5. 集成说明(Integration notes)
以下改动需由集成方应用到**共享文件**(本次交付未修改这些文件):
### 5.1 新增 WS `message_type`:`task_proposal`(`orchestrator/main.py`)
在 `websocket_endpoint` 的消息分支中(与 `peer_message` / `handoff_request` 同级)新增:
```python
elif message_type == "task_proposal":
# 默认关闭:开关未开则直接丢弃,运行时行为不变。
if not proposals_enabled():
continue
proposal = TaskProposal(
proposed_by_agent_id=agent_id,
title=message.get("title"),
description=message.get("description", ""),
proposal_reason=message.get("proposal_reason", ""),
proposal_confidence=message.get("proposal_confidence", 0.0),
agent_role=message.get("agent_role", "general"),
required_capabilities=message.get("required_capabilities") or [],
depends_on=message.get("depends_on") or [],
lineage=ProposalLineage(
origin_task_id=message.get("origin_task_id"),
trigger_event=message.get("trigger_event"),
shared_state_snapshot=message.get("shared_state_snapshot") or {},
),
)
run = await swarm_runtime.get_run_for_task(message.get("origin_task_id")) \
if message.get("origin_task_id") else None
# 用 run 任务图构造 ExistingTaskRef 列表做去重
existing = [
ExistingTaskRef(t.task_id, t.description, t.agent_role, t.status.value)
for t in await load_runtime_tasks(run)
] if run else []
policy = ProposalPolicy(
remaining_proposal_budget=int((run.metadata.get("proposal_budget", 3)) if run else 3),
)
outcome = review_proposal(proposal, policy, existing_tasks=existing)
if run:
for ev in build_lifecycle_events_for_outcome(proposal, outcome):
await swarm_runtime.emit_event(run, ev["event_type"], task_id=proposal.lineage.origin_task_id,
agent_instance_id=agent_id, payload=ev)
if outcome.decision == ProposalDecision.ACCEPT and run:
spec = ingest_accepted_proposal(proposal, swarm_id=run.swarm_id)
new_task = await task_queue.create_task(
task_id=f"{run.swarm_id}-{proposal.proposal_id}",
title=spec["title"], description=spec["description"],
agent_role=spec["agent_role"], required_capabilities=spec["required_capabilities"],
depends_on=spec["depends_on"], parent_task_id=spec["parent_task_id"],
root_task_id=spec["root_task_id"] or f"{run.swarm_id}-{proposal.proposal_id}",
source=spec["source"], context={**spec["context"], "swarm_id": run.swarm_id},
max_retries=3,
)
await swarm_runtime.attach_task(run, new_task.task_id)
run.metadata["proposal_budget"] = max(0, policy.remaining_proposal_budget - 1)
await swarm_runtime.save_run(run)
await websocket.send_json({"type": "task_proposal_ack",
"proposal_id": proposal.proposal_id,
"decision": outcome.decision.value})
```
并在 `main.py` 顶部新增 import:
```python
from .autonomous_tasks import (
proposals_enabled, TaskProposal, ProposalLineage, ProposalPolicy,
ProposalDecision, ExistingTaskRef, review_proposal, ingest_accepted_proposal,
build_lifecycle_events_for_outcome,
)
```
### 5.2 Agent 端发送(`agent/main.py`)
## 5. Agent 端发送(`agent/main.py`)
新增一个方法(与 `request_handoff` 同风格),由执行器在发现缺口时调用:
+11 -76
View File
@@ -1,6 +1,6 @@
# 蜂群收敛协议(共识 / 冲突消解 / 终止函数)
> 状态:**纯函数层已落地并通过单测;已接入 `refresh_swarm_run_status`(shadow 模式,去中心化重构 P1)**。feature flag `ENABLE_CONVERGENCE_REPORT`(构建期;cutover 转无条件):每次 run 终态产 `ConvergenceReport` 存 `run.metadata["convergence"]`,并把 `termination_reason` 附到 `timeline.updated`;**shadow——暂不覆盖 `run.status`**(cutover 转 authoritative)。
> 状态:**已无条件接入 `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 为什么结束」。
>
@@ -19,12 +19,12 @@
- **一等公民的冲突检测 / 消解**:两个 Agent 对同一文件写出不同内容、依赖未满足却标记完成等,均无显式识别;
- **可机读的终止原因**:`completed` / `failed` 无法区分「质量达标」「预算耗尽」「轮次上限」「被风险阻断」。
本协议补齐上述三者,且**严格作为旁路只读层**:不改变任何 run 的现有终态语义(规则 #9:如实标注未接入)。
本协议补齐上述三者。**已无条件接入**(无开关),但当前**作为解释层**:产出报告 + `termination_reason` 存 run,**不覆盖** run 的终态语义(规则 #9:authoritative 覆盖为后续)。
## 1. 设计原则
- **纯函数、可测**:`evaluate_convergence(run_state)` 不做任何 I/O,不修改 run;调用方负责持久化与发事件。
- **默认关闭**:`ENABLE_CONVERGENCE_REPORT`(默认 `false`),与 `ENABLE_REVIEW_LOOP` / `ENABLE_ACO_DISPATCH` 同构。关闭时整套逻辑不执行,Manager 契约与完成语义逐字节不变。
- **纯函数、可测**:`evaluate_convergence(run_state)` 不做任何 I/O,不修改 run;调用方(`main.py: compute_convergence_report`)负责持久化。
- **无条件(无开关)**:`refresh_swarm_run_status` 每次终态都计算报告——本仓即 swarm 运行时,无 `ENABLE_*` 门控。
- **不伪造信号**(规则 #9):无质量评分 ≠ 通过;无测试信号 ≠ 失败;无预算 ≠ 充足。缺信号一律退出判定,不补 0、不补 100。
- **解释优先于干预**:本版**只解释**「为什么结束」,不自动改写终态、不自动 merge 冲突产物。
@@ -118,77 +118,12 @@ consensus = 100 × (未被任何冲突牵连的已完成任务数 / 已完成任
模块级、无 WS/Redis/模型。覆盖:两个冲突产物 → 检出 artifact_mismatch + review_disagreement;review_disagreement 可消解、artifact_mismatch 未消解;产出带具体 `termination_reason` 的 `ConvergenceReport`;**断言每个终态都带 termination_reason**;断言至少一个冲突「检出 + 消解」闭环;以及五个终止原因各自的终态用例。全部 PASS。
## 6. 诚实差距(规则 #9)
## 6. 接入现状与诚实差距(规则 #9)
- **未接入**:`evaluate_convergence` 当前**不被 `refresh_swarm_run_status` 调用**,不改变任何 run 的真实终态。现状仍是「任务做完 + 评审通过」,本协议为旁路只读层。
- **质量/预算/风险输入有条件**:`quality` 依赖 Group B fixture 评分(仅 `ENABLE_QUALITY_EVAL` + 绑定 fixture 时存在);`budget`/`usage` 需 run 提供限额与用量;`risks` 需上游显式注入阻断风险。缺失时相应原因不触发,回退 `tasks_completed`。
- **消解为保守首版**:不做自动 merge / 自动选胜,硬冲突一律留待重做或人工。
- **事件未登记**:六个收敛事件尚未进入 Manager 事件注册表(见 §4 警告与下方集成说明)。
**已接入**(`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` 载荷。
## 集成说明(Integration notes)
以下说明**接入时**应如何改 `refresh_swarm_run_status` 与相关契约。**本任务不修改任何既有文件**,仅在此声明落点,供后续 PR 实施。
### 计算落点
在 `orchestrator/main.py:refresh_swarm_run_status` 中,**在已算出 `next_status`(行 ~421)之后、`run.status = next_status`(行 ~433)之前**插入(伪代码):
```python
from .convergence import (
convergence_report_enabled, evaluate_convergence,
event_convergence_started, event_conflict_detected, event_conflict_resolved,
event_consensus_updated, event_convergence_reached, event_convergence_failed,
)
report = None
if convergence_report_enabled():
run_state = {
"tasks": tasks, # 已在函数内拉取
"review_verdict": run.metadata.get("review_verdict"), # 需 maybe_run_review_cycle 顺带存入 metadata
"budget": (run.request_body.get("orchestration_plan") or {}).get("budget") or {},
"usage": run.metadata.get("usage_totals") or {}, # 由 emit_usage_event 累加得到
"quality": run.quality or {}, # Group B fixture 评分
"risks": run.metadata.get("risks") or [],
"review_cycles": int(run.metadata.get("review_cycles", 0) or 0),
"max_review_cycles": review_max_cycles(),
}
report = evaluate_convergence(run_state)
```
`evaluate_convergence` 是纯函数,**不替代** `next_status`;接入策略二选一:
- **影子模式(建议先行)**:只把 `report` 写入 `run.metadata["convergence"]` 并发事件,**不改 `run.status`**。用于在生产观察一段时间,验证收敛判定与现有终态一致后再升级。
- **权威模式(后续)**:让 `report.status` 参与/覆盖 `next_status`(如 `report.status == BLOCKED` 时把 run 置 `blocked`)。此举改变 Manager 面终态语义,**必须先过 `scripts/test-runtime-contract.py` 并按契约评审**。
### 新增事件
在计算 `report` 前后按需发:
```python
if report is not None:
await swarm_runtime.emit_event(run, *event_convergence_started(run_state))
for c in report.conflicts:
await swarm_runtime.emit_event(run, *event_conflict_detected(c))
for c in report.resolved_conflicts:
await swarm_runtime.emit_event(run, *event_conflict_resolved(c))
await swarm_runtime.emit_event(run, *event_consensus_updated(report))
if report.status == ConvergenceStatus.CONVERGED:
await swarm_runtime.emit_event(run, *event_convergence_reached(report))
elif report.is_terminal():
await swarm_runtime.emit_event(run, *event_convergence_failed(report))
```
**前置契约工作**:六个 `event_type`(`convergence.started` / `conflict.detected` / `conflict.resolved` / `consensus.updated` / `convergence.reached` / `convergence.failed`)须先在 Manager 侧 `agent_callback.go` 事件注册表登记必填字段;未登记前 `callback.subscribed_events` 不会投递,且不得在 `docs/integration/` 契约外私自消费。`emit_event` 已对 `timeline.updated`/`budget.alert` 做必填字段补齐,新增事件若有必填字段须比照补齐。
### 在终态附加 termination_reason
接入后,给终态的 `deployment.status_changed` 载荷补一个 `termination_reason`,让 Manager / 客户端能解释「为什么结束」。`status_payload` 不在本任务修改范围内,接入 PR 可在调用处合并:
```python
status_payload = swarm_runtime.status_payload(run, phase=..., deliverable=deliverable)
if report is not None and report.termination_reason is not None:
status_payload["termination_reason"] = report.termination_reason.value
await swarm_runtime.emit_event(run, "deployment.status_changed", payload=status_payload)
```
> 该字段为**新增可选字段**,对旧消费方向后兼容;纳入契约前应在 `docs/integration/` 标注,并在 PR 影响范围声明影响 Manager / agent_swarm / 审计链路。
**诚实差距**:
- **解释性,非权威**:报告**不覆盖** `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`(不伪造)。
- **消解为保守首版**:不做自动 merge / 自动选胜,硬冲突留待重做或人工。
- **收敛事件不进 Manager 流**:六个 `convergence.*`/`conflict.*`/`consensus.*` 事件**构建器已实现但不经 `emit_event` 外发**(未在 Manager `agent_callback.go` 注册;与 `swarm.health` 同策略,避免向订阅全部的回调投递未登记事件)。登记后方可启用 Manager 侧发送。`termination_reason` 以**新增可选字段**附在 `timeline.updated`,对旧消费方向后兼容。
+7 -27
View File
@@ -68,38 +68,18 @@
- **分歧偏置**:当 `disagreement=True` 时,`collaboration` 既 +1 票**又赢平票**——评审者分裂本身就是跨专家一致性缺口的证据;
- 全无信号 → `unknown`(不伪造)。
## 3. 集成说明(Integration notes)
## 3. 接入现状(已无条件接入,取代单评审)
> 本模块**当前未接入在线 finalize 路径**(诚信规则 #9)。`orchestrator/main.py` 的 `maybe_run_review_cycle` 仍是单评审主控门。以下为**预期接入方式**,接入前不改变任何现有行为与 Manager 契约。
> 同伴交叉评审是蜂群**唯一**的评审路径,**无开关**:去中心化重构已**删除**单 critic 主控评审环(`maybe_run_review_cycle` / `review_loop_enabled`)。`main.py: run_cross_review` 在 `refresh_swarm_run_status` 中无条件运行(<2 评审时为 no-op,放行收敛)。
### 3.1 开关(默认关闭)
### 3.1 评审者来源(≥ 2 独立意见)
新增功能开关 **`ENABLE_CROSS_REVIEW`(默认关)**,与现有 `ENABLE_REVIEW_LOOP` 正交:
同伴 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` 比较,平票安全偏向拒绝。
- `ENABLE_CROSS_REVIEW` 未开:完全保持今天的单评审 Supervisor Retry 行为;
- `ENABLE_CROSS_REVIEW=1`(且 `ENABLE_REVIEW_LOOP=1`):`maybe_run_review_cycle` 改为收集 ≥ 2 个 `ReviewDecision`,调用 `aggregate_reviews(...)` 得到 `AggregatedVerdict`,再用 `verdict.rework_targets` 决定 `reopen_task`。
### 3.2 状态与事件
接入需在 `orchestrator/main.py` 增加一个读取开关的 helper(如 `cross_review_enabled()`),与既有 `review_loop_enabled()` 并列;本任务**只新建文件**,故该 helper 的落地留待接入 PR。
### 3.2 评审者来源(≥ 2 独立意见)
当前 `planner.review` 只产出一个意见。接入时的两条候选路径(择一或并用):
1. **多模型 / 多 prompt 评审**:对同一 artifact 用不同 system prompt 或不同 `MASTER_REVIEW_MODEL` 跑 ≥ 2 次,各产出一个 `ReviewDecision`;
2. **同伴评审(peer review)**:复用现有 `build_dispatch_context` 暴露的 `peer_agents` 与 agent 端 `answer_peer_query`(见 `scripts/test-merge-smoke.py::test_agent_peer_shares_summary`),让其它专家就 artifact 给出 `stance/evidence` → 映射为 `ReviewDecision`。
`reviewer_agent_id` 取评审来源标识(模型名或 agent_id),`weight` 可由角色信任度赋值。
### 3.3 状态与事件接线
- **状态**:沿用 `run.metadata`。建议新增 `run.metadata["cross_review"]` 存 `AggregatedVerdict.to_dict()`,`run.metadata["rework_attributions"]` 存 `[ReworkAttribution.to_dict()]`,与既有 `review_cycles` / `review_summary` 并存;持久化走现有 `swarm_runtime.save_run(run)`。
- **事件**:用本模块的 payload builder + 现有 `swarm_runtime.emit_event(run, event_type, payload=...)` 发送。事件类型与现有回调风格一致(`task.*` / `timeline.updated`):
- `review.started`:`review_started_payload(run_id, reviewer_agent_ids, artifact_task_ids, cycle)`;
- `review.decision_made`:`review_decision_made_payload(run_id, verdict, cycle)`;
- `rework.requested`:`rework_requested_payload(run_id, attribution, cycle)`(每个重做目标一条);
- `rework.completed`:`rework_completed_payload(run_id, attribution, cycle, succeeded)`(重做任务到达终态时)。
- **新事件类型须先核对 Manager 回调契约**:HM 的 `agent_callback` 对 `timeline.updated`(需 `title`)、`budget.alert`(需 `threshold_pct`)有必填字段约束(见 `swarm_runtime.emit_event`)。`review.*` / `rework.*` 若要发往 Manager,接入 PR 必须先读 `docs/integration/` 契约确认字段;否则仅作内部事件记录(`_store_event`),不外发回调。
- **替换 vs. augment**:`ENABLE_CROSS_REVIEW=1` 时,`aggregate_reviews` 的 `rework_targets` **替换** `verdict["retry_tasks"]` 作为 `reopen_task` 的输入;其余循环机制(`MAX_REVIEW_CYCLES` 预算、退回 `running`、再 finalize)**复用不变**。关闭时一切照旧。
- **状态**:`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 不变量
+9 -22
View File
@@ -13,13 +13,9 @@
- **接管(takeover)**:另一个 Agent 请求从当前持有者手中接过任务(持有者卡住、或请求者更合适)。
- **仲裁(arbitrate)**:一个**确定性、可审计**的裁决器在竞价者中选出赢家,并记录理由与落败者。
## 2. 开关(默认关闭)
## 2. 接入现状(已无条件接入,无开关)
| 开关 | 默认 | 行为 |
|---|---|---|
| `ENABLE_TASK_COMPETITION` | **关** | 关闭时派发与现有拉取/分配路径逐字节一致,本模块**不被活体循环触达**(未接线)。开关用于未来接线时的守卫 |
`task_competition_enabled()` 读取该开关。当前模块为**纯逻辑 + 数据模型**,无任何副作用(不碰 Redis / WS / 计费 / 审批链)。
竞争协议是蜂群固有行为,**无 `ENABLE_*` 开关**(本仓即 swarm 运行时)。`main.py` 始终生效的 WS 分支 `task_bid` / `task_yield` / `task_takeover_request` → `handle_task_bid`(累积竞价到 `run.metadata["bids"]`)/ `arbitrate_and_assign`(τ 加权确定性仲裁 → `finalize_dispatch` 择优分派)/ `handle_task_yield`(复用 `release_task`)/ `handle_task_takeover`(须 decisive 胜出方可重分派)。仲裁/竞价/让渡审计存 `run.metadata`(**内部遥测,非 Manager 事件**)。`task_competition.py` 模块本身仍是纯逻辑 + 数据模型(不碰 Redis/WS/计费/审批链)。
## 3. 消息 / 数据模型(`orchestrator/task_competition.py`)
@@ -75,21 +71,12 @@ pip install -r orchestrator/requirements.txt
结果:35/35 PASS(`ALL PASSED`)。
## 集成说明(Integration notes)
## 集成现状(已接入)
以下为**尚未接线**的对接方案(本次不改任何现有文件,仅声明):
已落地于 `orchestrator/main.py`(无开关):
1. **新增 WS 入站消息类型**(`orchestrator/main.py` 的消息分发处,紧邻现有 `peer_message`/`handoff_request` 分支):
- `task_bid` → 解析为 `TaskBid`,缓存到 Redis(建议键 `task_bids:{task_id}` 列表),并 `emit_event(run, *bid_submitted_event(bid))`。
- `task_yield` → 解析为 `TaskYield`,调用 `task_queue.release_task(task_id, agent_id)`(已存在,不增 retry_count),`emit_event(run, *yielded_event(msg))`;若有 `recommend_agent`,作为下轮竞价候选优先项。
- `task_takeover_request` → 解析为 `TaskTakeoverRequest`,`emit_event(run, *takeover_requested_event(req))`;将 `req.bid` 与持有者当前 bid 一并送入 `arbitrate`。
2. **新增编排侧事件类型**(需登记进 Manager 回调契约 `docs/integration/event-schema.md` 与 heicode `agent_callback.go` 的允许事件集,**跨端改动须先读契约**,本次未动):`task.bid_submitted`、`task.yielded`、`task.takeover_requested`、`task.arbitrated`。在登记前,这些事件**不得**直接进入 Manager 事件流(与现有 `peer_message` 仅记内部遥测、不进 HM 流的处理一致)。
3. **新增状态**:竞价窗口(bid window)需要一个收集期 —— 建议在 `task_queue` 旁加「竞价开放/截止」时间戳(新键,不改 `Task` 模型),到期后调用 `arbitrate`,对赢家走现有 `assign_task`,对落败者发拒绝通知。
4. **τ 注入**:`historical_success` 应由 `decision_engine.get_tau(agent_role, agent_id)` 逐竞价者取值后传入 `arbitrate`,使竞争与 ACO 共用同一声誉源。
5. **审批 / 计费 / 审计**:`decisive=False`(接近平局)或接管已分配任务时,须走 Manager 审批链而非自动改派;任何改派保留 `usage` 与 `X-Agent/X-Agnet` 归属。以上均为 heicodeDocs 安全规则要求,接线时必须遵循。
6. **开关守卫**:所有新分支必须包在 `if task_competition_enabled():` 之内,默认关闭以保持 Manager 面接口 / 回调 / 审批链 / 计费语义不变。
1. **WS 入站分支**(紧邻 `peer_message`/`handoff_request`):`task_bid` → `handle_task_bid`(按 agent 去重累积到 `run.metadata["bids"]`);`task_yield` → `handle_task_yield`(复用 `task_queue.release_task`,不增 retry_count);`task_takeover_request` → `handle_task_takeover`(请求者 bid 与持有者中性 bid 一并送 `arbitrate`)。
2. **仲裁分派**:`arbitrate_and_assign(run, task_id)` 取 `run.metadata["bids"]` → `arbitrate` → 赢家经 `finalize_dispatch` 分派;审计存 `run.metadata["arbitrations"]/["yields"]`。
3. **τ 注入**:`historical_success` 由 `decision_engine.get_tau(...)` 逐竞价者取值(经 `normalize_tau`)传入 `arbitrate`,与自选/ACO 共用同一声誉源。
4. **事件不进 Manager 流**:`task.bid_submitted/yielded/takeover_requested/arbitrated` 的 builder 已实现但**不经 `emit_event` 外发**(未在 `agent_callback.go` 注册;与 `peer_message`/`swarm.health` 同策略——仅内部遥测)。登记后方可启用 Manager 侧发送。
5. **审批 / 计费 / 审计(后续硬约束)**:`decisive=False`(接近平局)或接管已分配任务时,按 heicodeDocs 应走 Manager 审批链而非自动改派;当前实现仅在 decisive 胜出时改派,平票不改派(记录待 review)。任何改派保留 `usage` 与 `X-Agent/X-Agnet` 归属。专用「竞价窗口」收集期(定时触发 `arbitrate_and_assign`)为后续优化。