Files
timmy-config/wizards/allegro-primus/self_analyzer.py
2026-03-31 20:02:01 +00:00

440 lines
17 KiB
Python
Executable File

#!/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()