feat(swarm): 协作聚合收敛取代蜂后选优 + sandbox 用 pytest 验证
CI / tests (push) Failing after 15m3s
CI / guardrails (push) Failing after 15m3s

按 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>
This commit is contained in:
gongzhiyong
2026-06-20 22:03:00 +08:00
co-authored by Claude Opus 4.8
parent 28a6c618ab
commit 1288fd19d7
13 changed files with 1095 additions and 444 deletions
+40 -65
View File
@@ -37,7 +37,7 @@ from . import autonomous_tasks as autonomous_mod
from . import task_competition as competition_mod
from . import cross_review as cross_review_mod
from . import guard as guard_mod
from . import queen as queen_mod
from . import result_aggregator
from . import agent_launcher
# Configure logging
@@ -698,12 +698,29 @@ async def refresh_swarm_run_status(run):
if next_status == "completed":
if await run_cross_review(run, tasks):
return
# Queen quality gate (M3/SC-9): score the candidates and, if the best fails the acceptance
# bar (and the review-cycle cap isn't hit), send work BACK for another round instead of
# declaring success on substandard output. Disabled by default (no threshold) — keeps
# current completion semantics until an operator sets QUEEN_ACCEPTANCE_THRESHOLD.
if await queen_quality_gate(run, tasks):
# 协作聚合质量门(①合并 ②沙箱验证 ③不达标打回):聚合节点合并各 agent 互补产出 → 跑测试验证 →
# 不达标且未到轮次上限则 reopen 打回(run 保持 running,下轮重做),达标/达上限则落 main。
# 阈值未设(AGGREGATE_ACCEPTANCE_THRESHOLD)时不打回,保持原完成语义。
agg_cycles = int(run.metadata.get("review_cycles", 0) or 0)
agg_max = int(os.getenv("MAX_REVIEW_CYCLES", "2") or 2)
agg = await result_aggregator.finalize_run(
run, tasks, run.objective, _accept_threshold(run), agg_cycles, agg_max)
run.metadata["aggregation"] = agg
if agg.get("decision") == "bounce":
from .quality import collect_generated_files
reopened = 0
for t in tasks:
if collect_generated_files([t]).get("impl"):
if await task_queue.reopen_task(t.task_id):
reopened += 1
run.metadata["review_cycles"] = agg_cycles + 1
run.metadata["rework_reopens"] = int(run.metadata.get("rework_reopens", 0) or 0) + reopened
await swarm_runtime.save_run(run)
logger.info("aggregation: bounced run %s — reopened %d for rework (cycle %d, pass_rate=%s)",
run.swarm_id, reopened, agg_cycles + 1,
(agg.get("validation") or {}).get("pass_rate"))
return
await swarm_runtime.save_run(run)
if run.status == next_status:
return
@@ -734,27 +751,15 @@ async def refresh_swarm_run_status(run):
results = {t.task_id: {"result": parse_task_result(t) or {}} for t in completed}
final_summary = await master_agent.synthesize(run.objective, results)
run.metadata["final_summary"] = final_summary
# Queen (agent_swarm#8/#12): aggregate the fan-out agents' artifacts, score each by running
# its impl against the swarm's shared tests, and SELECT the single best (best-of-N). Records
# the verdict on the run and marks the winner on the deliverable so the result is one
# coherent pick, not N scattered branches. Best-effort: never breaks the terminal path.
# The Queen verdict was computed by the quality gate above (run.metadata['queen']); mark the
# selected winner on the deliverable so the result is one coherent pick, not N branches.
queen_summary = run.metadata.get("queen") or {}
winner = queen_summary.get("winner") if isinstance(queen_summary, dict) else None
if isinstance(deliverable, dict) and winner:
deliverable["selected"] = winner
deliverable["candidate_count"] = queen_summary.get("candidate_count")
# SC-7: promote the winning artifact to the repo's main → one coherent deliverable on
# main, not N scattered agent branches. No-op when the run has no git grant.
try:
promo = await queen_mod.promote_to_main(run, tasks, winner.get("task_id"))
run.metadata["queen_promotion"] = promo
if isinstance(deliverable, dict) and promo.get("promoted"):
deliverable["promoted_to_main"] = {
"branch": promo.get("branch"), "commit_sha": promo.get("commit_sha")}
except Exception as exc:
logger.warning("queen promote_to_main failed for run %s: %s", run.swarm_id, exc)
# 聚合产物已由上面的协作聚合质量门产出并落 main(run.metadata['aggregation']);折进 deliverable。
agg = run.metadata.get("aggregation") or {}
if isinstance(deliverable, dict) and agg.get("finalized"):
deliverable["aggregated_files"] = agg.get("files")
deliverable["merged_file_count"] = agg.get("merged_file_count")
promo = agg.get("promotion") or {}
if promo.get("promoted"):
deliverable["promoted_to_main"] = {
"branch": promo.get("branch"), "commit_sha": promo.get("commit_sha")}
await swarm_runtime.save_run(run)
# Benchmark Group B: grade the run's generated code against its held-out fixture tests in
@@ -1175,48 +1180,18 @@ async def run_cross_review(run, tasks) -> bool:
return True
def _queen_threshold(run) -> Optional[float]:
"""Queen acceptance bar (pass_rate 0-100): run.metadata override → QUEEN_ACCEPTANCE_THRESHOLD
env → None (gate disabled). None keeps the current task-completion completion semantics."""
raw = (run.metadata or {}).get("queen_acceptance_threshold")
def _accept_threshold(run) -> Optional[float]:
"""协作聚合验收阈值(pass_rate 0-100):run.metadata 覆盖 → AGGREGATE_ACCEPTANCE_THRESHOLD env →
None(门禁用)。None 保持当前完成语义(不打回);设了才启用质量驱动的"不达标打回迭代"。"""
raw = (run.metadata or {}).get("aggregate_acceptance_threshold")
if raw is None:
raw = os.getenv("QUEEN_ACCEPTANCE_THRESHOLD")
raw = os.getenv("AGGREGATE_ACCEPTANCE_THRESHOLD")
try:
return float(raw) if raw not in (None, "") else None
except (TypeError, ValueError):
return None
async def queen_quality_gate(run, tasks) -> bool:
"""M3/SC-9: the Queen scores the fan-out candidates and, if the best fails the acceptance bar
and the review-cycle cap isn't hit, sends work BACK (reopen impl tasks) for another round
instead of declaring success on substandard output. Stores the verdict on run.metadata['queen']
(reused by the deliverable). Returns True if it reopened (caller keeps the run RUNNING).
Best-effort: never raises, never bounces on a score it couldn't compute (org rule #9)."""
try:
summary = await queen_mod.aggregate_run(run, tasks)
run.metadata["queen"] = summary
cycles = int(run.metadata.get("review_cycles", 0) or 0)
max_cycles = int(os.getenv("MAX_REVIEW_CYCLES", "2") or 2)
if not queen_mod.should_bounce(summary, _queen_threshold(run), cycles, max_cycles):
await swarm_runtime.save_run(run)
return False
reopened = 0
for c in summary.get("candidates", []):
if await task_queue.reopen_task(c["task_id"]):
reopened += 1
run.metadata["review_cycles"] = cycles + 1
# SC-10: tally quality-driven reopens for P_rework (reopen_task doesn't bump retry_count).
run.metadata["rework_reopens"] = int(run.metadata.get("rework_reopens", 0) or 0) + reopened
await swarm_runtime.save_run(run)
logger.info("queen: quality gate bounced run %s — reopened %d for rework (cycle %d)",
run.swarm_id, reopened, cycles + 1)
return reopened > 0
except Exception as exc:
logger.warning("queen quality gate failed for run %s: %s", run.swarm_id, exc)
return False
async def _historical_success_map(agent_role: str, agent_ids) -> Dict[str, float]:
"""τ (decision_engine pheromone) per agent, normalized to [0,1] for arbitration."""
out: Dict[str, float] = {}
@@ -1237,7 +1212,7 @@ async def handle_task_bid(agent_id: str, message: Dict[str, Any]) -> Dict[str, A
if not run or not task_id:
return {"recorded": False, "reason": "no_run"}
if not _agent_belongs_to_run(agent_id, run):
logger.warning("queen: rejected cross-run bid — agent %s ∉ run %s (task %s)",
logger.warning("run-boundary: rejected cross-run bid — agent %s ∉ run %s (task %s)",
agent_id, run.swarm_id, task_id)
return {"recorded": False, "reason": "cross_run_denied"}
bid = competition_mod.TaskBid(
@@ -1296,7 +1271,7 @@ async def handle_task_yield(agent_id: str, message: Dict[str, Any]) -> Dict[str,
if not run or not task_id:
return {"released": False, "reason": "no_run"}
if not _agent_belongs_to_run(agent_id, run):
logger.warning("queen: rejected cross-run yield — agent %s ∉ run %s (task %s)",
logger.warning("run-boundary: rejected cross-run yield — agent %s ∉ run %s (task %s)",
agent_id, run.swarm_id, task_id)
return {"released": False, "reason": "cross_run_denied"}
yield_msg = competition_mod.TaskYield(
@@ -1325,7 +1300,7 @@ async def handle_task_takeover(agent_id: str, message: Dict[str, Any]) -> Dict[s
if not task or not run:
return {"taken_over": False, "reason": "no_task"}
if not _agent_belongs_to_run(agent_id, run):
logger.warning("queen: rejected cross-run takeover — agent %s ∉ run %s (task %s)",
logger.warning("run-boundary: rejected cross-run takeover — agent %s ∉ run %s (task %s)",
agent_id, run.swarm_id, task_id)
return {"taken_over": False, "reason": "cross_run_denied"}
incumbent_id = task.assigned_agent_id