"""EvolutionAgent - Agent self-improvement and versioning system for VoIdea. This agent: - Analyzes agent performance - Generates improvement suggestions - Manages agent capabilities evolution - Handles agent versioning (minor/major bumps) - Coordinates learning """ import hashlib import re from datetime import datetime, timezone from pathlib import Path from typing import Any from app.agents.base import AgentResult, AgentStatus, AgentTrigger, BaseAgent from app.core.config import get_settings settings = get_settings() ALL_AGENTS: list[dict[str, Any]] = [ {"id": "doc_agent", "capabilities": ["documentation", "docstrings", "runbook"]}, {"id": "backlog_agent", "capabilities": ["create", "list", "update", "delete", "suggest"]}, {"id": "spec_agent", "capabilities": ["versioning", "changelog", "project_json"]}, {"id": "audit_agent", "capabilities": ["style_check", "type_check", "docs_check"]}, {"id": "observer_agent", "capabilities": ["collect", "report", "analyze", "metrics"]}, {"id": "security_agent", "capabilities": ["vulnerability_scan", "dependency_check", "compliance"]}, {"id": "qa_tester_agent", "capabilities": ["functional_test", "smoke_test", "regression"]}, {"id": "fix_agent", "capabilities": ["bug_analysis", "patch_generation", "validation"]}, {"id": "ui_test_agent", "capabilities": ["screenshot_test", "layout_check", "accessibility"]}, {"id": "rollout_agent", "capabilities": ["gradual_deploy", "monitor", "rollback"]}, {"id": "evolution_agent", "capabilities": ["analyze", "evolve", "suggest", "status", "version_bump"]}, ] AGENT_IDS = [a["id"] for a in ALL_AGENTS] class EvolutionAgent(BaseAgent): """Agent self-improvement and evolution system.""" name = "evolution_agent" version = "1.0.0" description = "Manages agent self-improvement and capability growth" triggers = [ AgentTrigger.MANUAL, AgentTrigger.CRON, ] def __init__(self): super().__init__() self.project_root = Path(__file__).parent.parent.parent async def run(self, context: dict[str, Any] | None = None) -> AgentResult: """Execute evolution task. Context can contain: - action: str (analyze, evolve, suggest, status, version_check, version_bump) - agent_id: str (specific agent to analyze) - version_type: str (minor, major) — for version_bump - entries: list[str] — changelog entries for version_bump - capabilities: list[str] — new capabilities for evolve """ await self.set_running("evolution") try: action = context.get("action", "status") if context else "status" if action == "analyze": result = await self._analyze_agents(context or {}) elif action == "evolve": result = await self._evolve_agent(context or {}) elif action == "suggest": result = await self._suggest_improvements(context or {}) elif action == "version_check": result = await self._version_check(context or {}) elif action == "version_bump": result = await self._version_bump(context or {}) else: result = await self._get_status() await self.set_idle() return result except Exception as e: await self.set_error(str(e)) return AgentResult( success=False, message=f"EvolutionAgent failed: {str(e)}", errors=[str(e)], ) async def health_check(self) -> bool: """Check if EvolutionAgent is operational.""" return self.project_root.exists() async def _get_status(self) -> AgentResult: """Get current evolution status with versions from changelogs.""" agents = [] for agent_info in ALL_AGENTS: aid = agent_info["id"] version = self._read_agent_version(aid) agents.append({ "id": aid, "version": version or "1.0.0", "capabilities": agent_info["capabilities"], "status": "stable", }) return AgentResult( success=True, message="Evolution status retrieved", data={ "total_agents": len(agents), "agents": agents, "last_evolution": datetime.now(timezone.utc).isoformat(), }, ) def _read_agent_version(self, agent_id: str) -> str | None: """Read latest version from an agent's changelog file.""" changelog_path = Path("CHANGELOG") / "agents" / f"{agent_id}.md" if not changelog_path.exists(): return None content = changelog_path.read_text(encoding="utf-8") matches = re.findall(r"##\s+(\d+\.\d+\.\d+)", content) return matches[-1] if matches else None async def _version_check(self, context: dict[str, Any]) -> AgentResult: """Scan all agents and report their current versions.""" agent_id = context.get("agent_id") agents_to_check = [agent_id] if agent_id else AGENT_IDS versions = {} for aid in agents_to_check: versions[aid] = self._read_agent_version(aid) or "1.0.0" return AgentResult( success=True, message=f"Version check completed for {len(versions)} agents", data={"versions": versions}, ) async def _version_bump(self, context: dict[str, Any]) -> AgentResult: """Bump version of a specific agent (minor or major). Context requires: - agent_id: str - version_type: str (minor or major) - entries: list[str] — changelog entry lines """ agent_id = context.get("agent_id") version_type = context.get("version_type", "minor") entries = context.get("entries", []) if not agent_id: return AgentResult( success=False, message="agent_id required", ) if version_type not in ("minor", "major"): return AgentResult( success=False, message=f"Invalid version_type: {version_type}. Use minor or major.", ) current_version = self._read_agent_version(agent_id) or "1.0.0" major, minor, patch = map(int, current_version.split(".")) if version_type == "major": major += 1 minor = 0 patch = 0 else: minor += 1 patch = 0 new_version = f"{major}.{minor}.{patch}" changelog_path = Path("CHANGELOG") / "agents" / f"{agent_id}.md" changelog_path.parent.mkdir(parents=True, exist_ok=True) today = datetime.now(timezone.utc).strftime("%Y-%m-%d") entry_text = f"\n## {new_version} ({today})\n" for line in entries: entry_text += f"- {line}\n" if changelog_path.exists(): content = changelog_path.read_text(encoding="utf-8") changelog_path.write_text(content + entry_text, encoding="utf-8") else: from app.agents.base import BaseAgent agent_src_path = Path("app") / "agents" / f"{agent_id}.py" if agent_src_path.exists(): checksum = hashlib.sha256(agent_src_path.read_bytes()).hexdigest() else: checksum = self.compute_checksum() changelog_path.write_text( f"# {agent_id} Changelog\n\n{entry_text}", encoding="utf-8", ) return AgentResult( success=True, message=f"{agent_id} bumped to {new_version}", data={ "agent_id": agent_id, "old_version": current_version, "new_version": new_version, "version_type": version_type, "entries": entries, }, ) async def _analyze_agents(self, context: dict[str, Any]) -> AgentResult: """Analyze agent performance and suggest improvements.""" agent_id = context.get("agent_id") analyses = {} targets = [agent_id] if agent_id else AGENT_IDS for aid in targets: analyses[aid] = self._analyze_single_agent(aid) return AgentResult( success=True, message=f"Analyzed {len(analyses)} agents", data={"analyses": analyses}, ) async def _evolve_agent(self, context: dict[str, Any]) -> AgentResult: """Apply evolution to a specific agent with version bump.""" agent_id = context.get("agent_id") new_capabilities = context.get("capabilities", []) if not agent_id: return AgentResult( success=False, message="agent_id required", ) if new_capabilities: entries = [f"Added: {cap}" for cap in new_capabilities] bump_result = await self._version_bump({ "agent_id": agent_id, "version_type": "minor", "entries": entries, }) new_version = bump_result.data.get("new_version", "unknown") else: new_version = self._read_agent_version(agent_id) or "1.0.0" evolution_entry = { "date": datetime.now(timezone.utc).isoformat(), "agent_id": agent_id, "new_capabilities": new_capabilities, "new_version": new_version, "trigger": context.get("trigger", "manual"), } return AgentResult( success=True, message=f"Evolved {agent_id} to v{new_version}", data={ "agent_id": agent_id, "new_capabilities": new_capabilities, "new_version": new_version, "evolution": evolution_entry, }, ) async def _suggest_improvements(self, context: dict[str, Any]) -> AgentResult: """Suggest improvements for agents.""" suggestions = [ { "agent_id": "doc_agent", "suggestion": "Add auto-generation of API docs", "priority": "medium", "impact": "high", }, { "agent_id": "observer_agent", "suggestion": "Add real-time dashboard updates", "priority": "low", "impact": "medium", }, { "agent_id": "audit_agent", "suggestion": "Integrate with CI/CD", "priority": "high", "impact": "high", }, ] return AgentResult( success=True, message="Generated improvement suggestions", data={"suggestions": suggestions}, ) def _analyze_single_agent(self, agent_id: str) -> dict[str, Any]: """Analyze a single agent.""" version = self._read_agent_version(agent_id) or "1.0.0" return { "agent_id": agent_id, "version": version, "health": "good", "performance": "optimal", "capabilities": self._get_agent_capabilities(agent_id), "suggestions": [], "last_check": datetime.now(timezone.utc).isoformat(), } def _get_agent_capabilities(self, agent_id: str) -> list[str]: """Get capabilities for an agent.""" for agent_info in ALL_AGENTS: if agent_info["id"] == agent_id: return agent_info["capabilities"] return [] async def get_metrics(self) -> dict[str, Any]: """Get EvolutionAgent metrics.""" return { "agent_id": self.name, "version": self.version, "status": self.status.value, "last_evolution": self.last_run.isoformat() if self.last_run else None, "capabilities_tracked": len(ALL_AGENTS), }