- 基准标准 v2.1:SwarmMetrics(15 字段)、τ/η/P_decision/reward 公式、对称 G_E,c(修正 C_base=1.0 退化)、Σλ=1.0 校验;新增基线对比与运行记录 schema;指标覆盖缺口分析;参考系数暂留为元数据(待量化)。 - 主控 Agent 实体(分解 / 评审决策 / 汇总);事件契约修正(timeline.title、budget.threshold_pct、handoff 角色、task.released)+ 契约校验脚本。 - 实质性 LLM 对等回复(含降级回退);集成契约(runtime / event / usage / audit / frontend / capability / security);CLIENT_GUIDE 客户端指南;CI 工作流;治理与交付文档。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
"""Master Agent — the decision-making entity of a swarm run.
|
|
|
|
This makes the "master" a first-class entity rather than scattered orchestrator functions.
|
|
The master agent owns the *cognitive* loop:
|
|
- plan() : decompose the objective into specialist subtasks
|
|
- review_and_decide(): judge whether the combined result is good enough, and which tasks to redo
|
|
- synthesize() : compose the specialists' outputs into one user-facing answer
|
|
|
|
It uses an LLM as its brain (via `planner`, with deterministic fallbacks). The orchestrator
|
|
remains the "hands": it executes the master's decisions (dispatch, reopen tasks, persist state,
|
|
emit events). This separation keeps decisions in one named entity while leaving runtime
|
|
mechanics (and the Manager contract) in the orchestrator.
|
|
|
|
NOTE: agent→task dispatch is still capability-matched in the orchestrator loop; `select_agent`
|
|
below is the seam where the master can later own assignment (LLM-driven), without changing the
|
|
loop's contract today.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from .planner import planner
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MasterAgent:
|
|
"""The master agent: decompose → decide → synthesize for a swarm run."""
|
|
|
|
def __init__(self, brain=planner):
|
|
# `brain` is the LLM-backed planner (build_plan / review / synthesize), swappable for tests.
|
|
self.brain = brain
|
|
|
|
async def plan(self, run_id: str, objective: str) -> List[Dict[str, Any]]:
|
|
"""Decompose the user objective into specialist subtasks."""
|
|
subtasks = await self.brain.build_plan(run_id, objective)
|
|
logger.info(f"[master] planned {len(subtasks)} subtask(s) for run {run_id}")
|
|
return subtasks
|
|
|
|
async def review_and_decide(self, objective: str, tasks: List[Dict[str, Any]],
|
|
results: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Decide whether the combined result is good enough.
|
|
|
|
Returns the master's verdict: {accepted: bool, summary: str, retry_tasks: [task_id,...]}.
|
|
The orchestrator acts on it (reopen retry_tasks or finalize).
|
|
"""
|
|
verdict = await self.brain.review(objective, tasks, results)
|
|
logger.info(
|
|
f"[master] review decision: accepted={verdict.get('accepted')} "
|
|
f"retry={verdict.get('retry_tasks')}"
|
|
)
|
|
return verdict
|
|
|
|
async def synthesize(self, objective: str, results: Dict[str, Any]) -> str:
|
|
"""Compose the specialists' outputs into one user-facing answer."""
|
|
return await self.brain.synthesize(objective, results)
|
|
|
|
def select_agent(self, task, candidates: List[Any]) -> Optional[Any]:
|
|
"""Choose which agent runs a task. Seam for future LLM-driven assignment.
|
|
|
|
Default: first capability-eligible candidate (capability matching is enforced upstream
|
|
by task_queue.get_ready_pending_task), preserving current dispatch behavior.
|
|
"""
|
|
return candidates[0] if candidates else None
|
|
|
|
|
|
# Singleton master agent for the runtime.
|
|
master_agent = MasterAgent()
|