"""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 from app.agents.registry import 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 = 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 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 = registry.list_agents() metrics = 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, }, )