#!/usr/bin/env python3 """ Allegro-Primus Self-Analyzer Analyzes journal entries, identifies bottlenecks, suggests improvements. """ import json import os from datetime import datetime, timedelta from pathlib import Path from typing import Dict, List, Optional, Tuple, Any from collections import defaultdict import statistics from journal import Journal, JournalEntry, JOURNAL_DIR IMPROVEMENT_QUEUE = Path("/root/wizards/allegro-primus/improvement_queue.json") class SelfAnalyzer: """Analyzes journal data to identify improvements.""" def __init__(self): self.journal = Journal() self._ensure_queue() def _ensure_queue(self): """Ensure improvement queue exists.""" if not IMPROVEMENT_QUEUE.exists(): self._save_queue({ "created_at": datetime.now().isoformat(), "pending": [], "implemented": [], "rejected": [] }) def _load_queue(self) -> Dict: with open(IMPROVEMENT_QUEUE, 'r') as f: return json.load(f) def _save_queue(self, queue: Dict): with open(IMPROVEMENT_QUEUE, 'w') as f: json.dump(queue, f, indent=2) def add_improvement(self, category: str, description: str, priority: str = "medium", source: str = "analysis") -> str: """Add an improvement to the queue.""" queue = self._load_queue() improvement = { "id": f"imp_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{os.urandom(2).hex()}", "category": category, "description": description, "priority": priority, "source": source, "created_at": datetime.now().isoformat(), "status": "pending" } queue["pending"].append(improvement) self._save_queue(queue) return improvement["id"] def complete_improvement(self, improvement_id: str, notes: str = ""): """Mark an improvement as implemented.""" queue = self._load_queue() for imp in queue["pending"]: if imp["id"] == improvement_id: imp["status"] = "implemented" imp["completed_at"] = datetime.now().isoformat() imp["notes"] = notes queue["implemented"].append(imp) queue["pending"] = [p for p in queue["pending"] if p["id"] != improvement_id] break self._save_queue(queue) def analyze_bottlenecks(self, days: int = 14) -> List[Dict]: """Identify bottlenecks from journal entries.""" entries = self.journal.get_entries(days=days) bottlenecks = [] if len(entries) < 5: return [{"status": "insufficient_data", "min_required": 5}] # 1. High error rate patterns error_patterns = self._analyze_error_patterns(entries) if error_patterns: bottlenecks.append({ "type": "error_pattern", "description": f"Recurring errors: {', '.join(error_patterns[:3])}", "severity": "high" if len(error_patterns) > 3 else "medium", "suggestion": "Review error handling and add pre-validation checks" }) # 2. Slow response patterns slow_ops = self._identify_slow_operations(entries) if slow_ops: bottlenecks.append({ "type": "performance", "description": f"Slow operations detected: {slow_ops}", "severity": "medium", "suggestion": "Consider caching or parallelization" }) # 3. Repeated failures failure_clusters = self._find_failure_clusters(entries) if failure_clusters: bottlenecks.append({ "type": "reliability", "description": f"Failure clusters: {failure_clusters}", "severity": "high", "suggestion": "Add retry logic and better error recovery" }) # 4. Tool usage inefficiency tool_inefficiency = self._analyze_tool_inefficiency(entries) if tool_inefficiency: bottlenecks.append({ "type": "efficiency", "description": tool_inefficiency, "severity": "low", "suggestion": "Optimize tool selection strategy" }) return bottlenecks def _analyze_error_patterns(self, entries: List[JournalEntry]) -> List[str]: """Extract common error patterns.""" error_counts = defaultdict(int) for e in entries: for err in e.errors: # Normalize error messages normalized = err.lower().replace('file not found', 'missing_file') normalized = normalized.replace('permission denied', 'access_error') normalized = normalized.replace('timeout', 'timeout_error') error_counts[normalized[:50]] += 1 # Return errors that appear multiple times return [err for err, count in error_counts.items() if count >= 2] def _identify_slow_operations(self, entries: List[JournalEntry]) -> str: """Identify operations with slow response times.""" slow_threshold = 5000 # 5 seconds slow_tasks = [] for e in entries: if e.response_time_ms > slow_threshold: task_type = self.journal._categorize_task(e.task) slow_tasks.append(task_type) if not slow_tasks: return "" task_counts = defaultdict(int) for t in slow_tasks: task_counts[t] += 1 top_slow = sorted(task_counts.items(), key=lambda x: x[1], reverse=True)[:2] return ", ".join([f"{t}({c}x)" for t, c in top_slow]) def _find_failure_clusters(self, entries: List[JournalEntry]) -> List[str]: """Find clusters of failures.""" failures = [e for e in entries if not e.success] if len(failures) < 3: return [] # Group by task type failure_types = defaultdict(int) for f in failures: task_type = self.journal._categorize_task(f.task) failure_types[task_type] += 1 return [t for t, c in failure_types.items() if c >= 2] def _analyze_tool_inefficiency(self, entries: List[JournalEntry]) -> str: """Analyze if tool usage is inefficient.""" total_tools = sum(len(e.tools_used) for e in entries) avg_tools = total_tools / len(entries) if entries else 0 if avg_tools > 5: return f"High average tool usage ({avg_tools:.1f} per cycle)" return "" def generate_recommendations(self, days: int = 14) -> List[Dict]: """Generate improvement recommendations.""" recommendations = [] entries = self.journal.get_entries(days=days) if len(entries) < 5: return [{"status": "insufficient_data", "min_required": 5}] metrics = self.journal.calculate_metrics(days=days) patterns = self.journal.get_patterns(days=days) # 1. Success rate recommendations if metrics.success_rate < 0.8: recommendations.append({ "category": "reliability", "priority": "high", "issue": f"Success rate is {metrics.success_rate*100:.0f}%", "recommendation": "Implement better error handling and validation", "expected_impact": "Increase success rate to 90%+" }) # 2. Response time recommendations if metrics.avg_response_time_ms > 3000: recommendations.append({ "category": "performance", "priority": "medium", "issue": f"Average response time is {metrics.avg_response_time_ms:.0f}ms", "recommendation": "Add caching, optimize queries, use batch operations", "expected_impact": "Reduce response time by 30-50%" }) # 3. Learning recommendations if metrics.total_lessons < metrics.total_cycles * 0.1: recommendations.append({ "category": "learning", "priority": "medium", "issue": "Low lesson capture rate", "recommendation": "Add structured reflection after each cycle", "expected_impact": "Better pattern recognition and faster improvement" }) # 4. Tool usage recommendations tool_usage = defaultdict(int) for e in entries: for tool in e.tools_used: tool_usage[tool] += 1 if len(tool_usage) < 3 and len(entries) > 10: recommendations.append({ "category": "capability", "priority": "low", "issue": "Limited tool variety", "recommendation": "Explore additional tools for different tasks", "expected_impact": "More efficient task completion" }) # 5. Pattern-based recommendations if patterns.get('improvement_trend') == 'declining': recommendations.append({ "category": "trend", "priority": "high", "issue": "Performance trend is declining", "recommendation": "Review recent changes, focus on fundamentals", "expected_impact": "Reverse negative trend" }) return recommendations def auto_generate_improvements(self, days: int = 14) -> List[str]: """Automatically generate improvements from analysis.""" recommendations = self.generate_recommendations(days=days) bottlenecks = self.analyze_bottlenecks(days=days) improvement_ids = [] # Convert recommendations to improvements for rec in recommendations: if 'priority' in rec: imp_id = self.add_improvement( category=rec['category'], description=f"{rec['issue']}: {rec['recommendation']}", priority=rec['priority'], source="auto_analysis" ) improvement_ids.append(imp_id) # Convert bottlenecks to improvements for bottleneck in bottlenecks: if 'severity' in bottleneck: imp_id = self.add_improvement( category=bottleneck['type'], description=bottleneck['description'], priority=bottleneck['severity'], source="bottleneck_detection" ) improvement_ids.append(imp_id) return improvement_ids def generate_report(self, days: int = 14) -> Dict: """Generate comprehensive self-analysis report.""" entries = self.journal.get_entries(days=days) queue = self._load_queue() report = { "generated_at": datetime.now().isoformat(), "analysis_period_days": days, "entries_analyzed": len(entries), "summary": { "metrics": {}, "patterns": {}, "bottlenecks": [], "recommendations": [] }, "improvement_queue": { "pending": len(queue["pending"]), "implemented": len(queue["implemented"]), "rejected": len(queue["rejected"]) } } if len(entries) >= 5: metrics = self.journal.calculate_metrics(days=days) report["summary"]["metrics"] = { "total_cycles": metrics.total_cycles, "success_rate": round(metrics.success_rate * 100, 2), "avg_response_time_ms": round(metrics.avg_response_time_ms, 2), "total_lessons": metrics.total_lessons } report["summary"]["patterns"] = self.journal.get_patterns(days=days) report["summary"]["bottlenecks"] = self.analyze_bottlenecks(days=days) report["summary"]["recommendations"] = self.generate_recommendations(days=days) return report def display_report(self, days: int = 14): """Display formatted analysis report.""" report = self.generate_report(days=days) queue = self._load_queue() print("\n" + "=" * 70) print(" ALLEGRO-PRIMUS SELF-ANALYSIS REPORT") print("=" * 70) print(f" Generated: {report['generated_at']}") print(f" Period: Last {days} days") print(f" Entries Analyzed: {report['entries_analyzed']}") print("-" * 70) # Metrics metrics = report["summary"]["metrics"] if metrics: print("\n 📊 PERFORMANCE METRICS") print(f" Success Rate: {metrics.get('success_rate', 0):.1f}%") print(f" Avg Response: {metrics.get('avg_response_time_ms', 0):.0f}ms") print(f" Lessons Captured: {metrics.get('total_lessons', 0)}") # Patterns patterns = report["summary"]["patterns"] if patterns and patterns.get("status") != "insufficient_data": print("\n 📈 PATTERNS") print(f" Trend: {patterns.get('improvement_trend', 'N/A')}") if patterns.get("best_performing_hours"): print(f" Best Hours: {patterns['best_performing_hours'][:2]}") # Bottlenecks bottlenecks = report["summary"]["bottlenecks"] if bottlenecks and bottlenecks[0].get("status") != "insufficient_data": print("\n ⚠️ BOTTLENECKS") for b in bottlenecks[:3]: if 'type' in b: print(f" [{b['severity'].upper()}] {b['type']}: {b['description'][:50]}...") # Recommendations recommendations = report["summary"]["recommendations"] if recommendations and recommendations[0].get("status") != "insufficient_data": print("\n 💡 RECOMMENDATIONS") for r in recommendations[:3]: if 'category' in r: print(f" [{r['priority'].upper()}] {r['category']}: {r['recommendation'][:50]}...") # Queue Status print("\n 📋 IMPROVEMENT QUEUE") print(f" Pending: {queue['pending'].__len__()}") print(f" Implemented: {queue['implemented'].__len__()}") print("=" * 70) def main(): """CLI interface for self-analyzer.""" import argparse parser = argparse.ArgumentParser(description="AP Self-Analyzer") parser.add_argument('action', choices=[ 'report', 'bottlenecks', 'recommendations', 'auto', 'add', 'complete', 'queue', 'patterns' ]) parser.add_argument('--days', '-d', type=int, default=14) parser.add_argument('--category', '-c', help='Category for add') parser.add_argument('--description', '-desc', help='Description for add') parser.add_argument('--priority', '-p', default='medium', choices=['low', 'medium', 'high']) parser.add_argument('--id', help='Improvement ID for complete') parser.add_argument('--notes', '-n', default='', help='Completion notes') args = parser.parse_args() analyzer = SelfAnalyzer() if args.action == 'report': analyzer.display_report(days=args.days) elif args.action == 'bottlenecks': bottlenecks = analyzer.analyze_bottlenecks(days=args.days) print(json.dumps(bottlenecks, indent=2)) elif args.action == 'recommendations': recs = analyzer.generate_recommendations(days=args.days) print(json.dumps(recs, indent=2)) elif args.action == 'auto': ids = analyzer.auto_generate_improvements(days=args.days) print(f"Generated {len(ids)} improvements:") for imp_id in ids: print(f" - {imp_id}") elif args.action == 'add': if not args.category or not args.description: print("Error: --category and --description required") return imp_id = analyzer.add_improvement( args.category, args.description, args.priority ) print(f"Added improvement: {imp_id}") elif args.action == 'complete': if not args.id: print("Error: --id required") return analyzer.complete_improvement(args.id, args.notes) print(f"Marked {args.id} as complete") elif args.action == 'queue': queue = analyzer._load_queue() print(json.dumps(queue, indent=2)) elif args.action == 'patterns': patterns = analyzer.journal.get_patterns(days=args.days) print(json.dumps(patterns, indent=2)) if __name__ == '__main__': main()