"""MetaAgent — Orchestrator agent for VoIdea. Configurable from admin panel: - cron_schedule: str (default "0 9 * * 1,4") - weights: dict[agent_name, float] - sources_count: int (default 10) - auto_create: bool (default True) Runs a subset of agents based on weights and collects reports. """ import json from datetime import datetime, timezone from typing import Any from app.agents.base import AgentResult, AgentStatus, AgentTrigger, BaseAgent def _get_registry(): from app.agents.registry import registry return registry DEFAULT_CONFIG: dict[str, Any] = { "cron_schedule": "0 9 * * 1,4", "weights": {}, "sources_count": 10, "auto_create": True, } class MetaAgent(BaseAgent): """Orchestrator agent that coordinates other agents.""" name = "meta_agent" version = "1.0.0" description = "Orchestrates other agents based on configurable schedule and weights" triggers = [ AgentTrigger.MANUAL, AgentTrigger.CRON, AgentTrigger.API, ] def __init__(self, config: dict[str, Any] | None = None): super().__init__() self._config = {**DEFAULT_CONFIG, **(config or {})} @property def config(self) -> dict[str, Any]: return self._config @config.setter def config(self, value: dict[str, Any]) -> None: self._config = {**DEFAULT_CONFIG, **value} async def run(self, context: dict[str, Any] | None = None) -> AgentResult: """Execute meta-agent orchestration. Context can contain: - action: str (orchestrate | report | status) — default "orchestrate" - target_agents: list[str] — subset of agents to run (default: all) - sources_count: int — override config """ await self.set_running("orchestration") try: action = (context or {}).get("action", "orchestrate") if action == "status": return await self._get_status() elif action == "report": return await self._generate_report() else: return await self._orchestrate(context or {}) except Exception as e: await self.set_error(str(e)) return AgentResult( success=False, message=f"MetaAgent failed: {str(e)}", errors=[str(e)], ) async def health_check(self) -> bool: return True async def _orchestrate(self, context: dict[str, Any]) -> AgentResult: """Run target agents and collect results.""" target_agents: list[str] | None = context.get("target_agents") sources_count = context.get("sources_count", self._config["sources_count"]) weights = self._config.get("weights", {}) all_agents = _get_registry().list_agents() if not all_agents: await self.set_idle() return AgentResult( success=True, message="No agents registered", data={"orchestrated": 0}, ) candidates = [] for a in all_agents: name = a["name"] if name == self.name: continue weight = weights.get(name, 1.0) if weight <= 0: continue candidates.append((name, weight)) if not candidates: candidates = [(a["name"], 1.0) for a in all_agents if a["name"] != self.name] if target_agents: candidates = [c for c in candidates if c[0] in target_agents] max_sources = min(sources_count, len(candidates)) selected = candidates[:max_sources] results: dict[str, Any] = {} for name, _weight in selected: try: result = await _get_registry().run_agent(name, {"trigger": "meta_agent"}) results[name] = { "success": result.success, "message": result.message, "duration_ms": result.duration_ms, } except Exception as e: results[name] = { "success": False, "message": str(e), } success_count = sum(1 for r in results.values() if r["success"]) total_duration = sum(r.get("duration_ms", 0) for r in results.values()) await self.set_idle() return AgentResult( success=True, message=f"Orchestrated {len(results)} agents ({success_count} ok)", data={ "orchestrated": len(results), "success_count": success_count, "agents": results, "total_duration_ms": total_duration, "config": { "cron_schedule": self._config["cron_schedule"], "sources_count": sources_count, "auto_create": self._config["auto_create"], }, }, ) async def _generate_report(self) -> AgentResult: """Generate summary report of all agent statuses.""" all_agents = _get_registry().list_agents() metrics = _get_registry().get_metrics_all() return AgentResult( success=True, message=f"Report: {len(all_agents)} agents", data={ "agents": all_agents, "metrics": metrics, "generated_at": datetime.now(timezone.utc).isoformat(), }, ) async def _get_status(self) -> AgentResult: return AgentResult( success=True, message="Meta-agent operational", data={ "status": self.status.value, "config": self._config, "last_run": self._last_run.isoformat() if self._last_run else None, }, )