把本仓从「Master 分解→派发→单评审」重构为去中心化自组织蜂群,并设为唯一行为
(本仓即 swarm 运行时,模式选择在仓外,无 ENABLE_swarm 开关)。依据:heicodeDocs 弱中心
定义 + 蜂群文章(stigmergy)+ OpenAI Swarm(handoff)。
流程(全部无条件生效):
- 播种 build_seed_task_specs(不主控分解)→ 自选 swarm_dispatch(能力+τ+负载+预算,唯一派发)
→ 自主分解 task_proposal(autonomous_tasks)→ 竞争/接管 task_bid/yield/takeover(task_competition)
→ 同伴交叉评审 ≥2 评审者(cross_review,取代单 critic)→ 收敛 ConvergenceReport+termination_reason
(convergence)→ 守卫 guard.diagnose(检测无法运作并列原因)。
删除:planner 主控分解、贪心/ACO/scored 派发与 scored_matchmake、单 critic 评审环、
全部 ENABLE_* swarm 构建开关。master_agent.synthesize 作为汇总工具保留。
测试:test-workflow-e2e 改写为真实 swarm 全流程;test-merge-smoke 改为 seeder/cross-review;
新增 test-swarm-{seed,dispatch,autonomous,competition,cross-review,convergence,guard} + 模块单测;
CI 同步。本地 18 套全绿。
影响范围:agent_swarm(orchestrator + 新模块 + 测试 + docs + CI)。不改 Manager↔Swarm 事件契约
(新事件均为运行时内部状态/遥测,不进 HM 注册表);不影响 Client/计费/密钥/审计/发布链路。
无新增长期开关。
诚实边界:不动 benchmark 验收(G_E 仍需真实 run,#13);convergence 暂为解释性(不强制覆盖
run.status);前端可见状态为跨端(#18);「去中心化是否更优」未证(需 Group C)。
#6 的 DoD(Path B 决策 + 定位文档)本 PR 已满足并实现 Path B → 关闭 #6。
#7/#8/#11/#12 的运行时机制已落地(事件 + e2e),但其 DoD 含前端可见状态(跨端 #18)与
多 Agent 回放,故以 Refs 链接、不自动关闭,留维护者在前端/验收就绪后关闭。
Closes #6
Refs #7
Refs #8
Refs #11
Refs #12
Refs #18
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
121 lines
5.7 KiB
Python
121 lines
5.7 KiB
Python
"""Integration test for decentralized-rework P5: peer cross-review (#11).
|
|
|
|
>=2 independent peer reviewers replace the single-critic Master gate: disagreement is recorded,
|
|
the arbitrated verdict reopens rework targets, and each rework is attributed (root cause + who
|
|
introduced it). Hermetic, no model key.
|
|
|
|
Run from agent_swarm_v6 (install deps first — needs fakeredis):
|
|
pip install -r orchestrator/requirements.txt -r agent/requirements.txt
|
|
REDIS_FAKE=1 ENABLE_CROSS_REVIEW=1 python scripts/test-swarm-cross-review.py
|
|
"""
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
os.environ["REDIS_FAKE"] = "1"
|
|
os.environ["ENABLE_CROSS_REVIEW"] = "1"
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from orchestrator.redis_client import redis_client
|
|
from orchestrator import swarm_runtime as sr_mod
|
|
from orchestrator.swarm_runtime import swarm_runtime
|
|
from orchestrator.task_queue import task_queue, TaskStatus
|
|
from orchestrator import main as orch
|
|
|
|
failures = []
|
|
|
|
|
|
def check(name, cond):
|
|
print(("PASS" if cond else "FAIL"), "-", name)
|
|
if not cond:
|
|
failures.append(name)
|
|
|
|
|
|
async def _noop(self, *a, **k):
|
|
return None
|
|
|
|
|
|
async def completed_task(run, tid, agent):
|
|
t = await task_queue.create_task(task_id=f"{run.swarm_id}-{tid}", description=tid,
|
|
agent_role=tid.split("-")[-1], enqueue=False)
|
|
t.status = TaskStatus.COMPLETED
|
|
t.assigned_agent_id = agent
|
|
await task_queue._save_task(t)
|
|
await swarm_runtime.attach_task(run, t.task_id)
|
|
return t
|
|
|
|
|
|
async def main():
|
|
await redis_client.connect()
|
|
sr_mod.SwarmRuntime._post_callback = _noop
|
|
|
|
body = {"mode": "swarm", "requirement": {"objective": "cross review test"},
|
|
"callback": {"url": "http://127.0.0.1:9/cb", "subscribed_events": []},
|
|
"metadata": {"manager_deployment_id": "m-cr"}}
|
|
run, _ = await swarm_runtime.get_or_create_run(body=body, idempotency_key=None, correlation_id="cr")
|
|
impl = await completed_task(run, "t-implementation", "agent-impl")
|
|
await completed_task(run, "t-documentation", "agent-doc")
|
|
|
|
# two peers submit INDEPENDENT reviews of the implementation; they DISAGREE
|
|
await orch.handle_review_decision("reviewer-1", {
|
|
"task_id": impl.task_id, "verdict": "pass", "summary": "looks correct"})
|
|
r = await orch.handle_review_decision("reviewer-2", {
|
|
"task_id": impl.task_id, "verdict": "fail",
|
|
"failed_criteria": ["implementation raises wrong exception"],
|
|
"recommended_rework": [impl.task_id], "summary": "wrong exception on bad input"})
|
|
check("two independent reviews recorded", r.get("review_count") == 2)
|
|
|
|
# aggregate (called by the run lifecycle with a fresh run)
|
|
fresh = await swarm_runtime.get_run(run.swarm_id)
|
|
tasks = [await task_queue.get_task(tid) for tid in fresh.task_ids]
|
|
reopened = await orch.run_cross_review(fresh, tasks)
|
|
check("cross-review reopened rework target (rejected on split)", reopened is True)
|
|
|
|
after = await swarm_runtime.get_run(run.swarm_id)
|
|
cr = after.metadata.get("cross_review") or {}
|
|
check("verdict recorded with disagreement", cr.get("disagreement") is True and cr.get("accepted") is False)
|
|
check("split arbitrated by majority → reject (safety bias)",
|
|
cr.get("method") == "majority" and cr.get("pass_votes") == 1 and cr.get("fail_votes") == 1)
|
|
impl_after = await task_queue.get_task(impl.task_id)
|
|
check("flagged task reopened to PENDING", impl_after.status == TaskStatus.PENDING)
|
|
attrs = after.metadata.get("rework_attributions") or []
|
|
check("rework attributed (root cause + detector)",
|
|
attrs and attrs[0]["target_task_id"] == impl.task_id
|
|
and attrs[0]["root_cause"] in {"implementation", "collaboration"}
|
|
and attrs[0]["detected_by_agent_id"] == "reviewer-2")
|
|
check("reviews consumed after the cycle", not after.metadata.get("reviews"))
|
|
check("review cycle counter advanced", after.metadata.get("review_cycles") == 1)
|
|
|
|
# --- unanimous pass → accept, no reopen ---
|
|
run2, _ = await swarm_runtime.get_or_create_run(
|
|
body={**body, "metadata": {"manager_deployment_id": "m-cr2"}}, idempotency_key=None, correlation_id="cr2")
|
|
a2 = await completed_task(run2, "t-implementation", "agent-impl")
|
|
for rid in ("reviewer-1", "reviewer-2"):
|
|
await orch.handle_review_decision(rid, {"task_id": a2.task_id, "verdict": "pass", "summary": "ok"})
|
|
fresh2 = await swarm_runtime.get_run(run2.swarm_id)
|
|
reopened2 = await orch.run_cross_review(fresh2, [await task_queue.get_task(t) for t in fresh2.task_ids])
|
|
check("unanimous pass → not reopened", reopened2 is False)
|
|
a2_after = await task_queue.get_task(a2.task_id)
|
|
check("accepted task stays COMPLETED", a2_after.status == TaskStatus.COMPLETED)
|
|
|
|
# --- single reviewer is NOT a cross-review (needs >=2) ---
|
|
run3, _ = await swarm_runtime.get_or_create_run(
|
|
body={**body, "metadata": {"manager_deployment_id": "m-cr3"}}, idempotency_key=None, correlation_id="cr3")
|
|
a3 = await completed_task(run3, "t-implementation", "agent-impl")
|
|
await orch.handle_review_decision("reviewer-1", {"task_id": a3.task_id, "verdict": "fail",
|
|
"recommended_rework": [a3.task_id]})
|
|
fresh3 = await swarm_runtime.get_run(run3.swarm_id)
|
|
reopened3 = await orch.run_cross_review(fresh3, [await task_queue.get_task(t) for t in fresh3.task_ids])
|
|
check("single reviewer → no cross-review (needs >=2)", reopened3 is False)
|
|
|
|
print()
|
|
if failures:
|
|
print(f"{len(failures)} cross-review (P5) check(s) FAILED: {failures}")
|
|
sys.exit(1)
|
|
print("all swarm cross-review (P5) checks passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|