Coverage for integrations / agent_engine / self_healing_dispatcher.py: 97.2%
72 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
1"""
2Self-Healing Dispatcher
3========================
5Periodically reviews collected exceptions and creates coding fix goals.
6Runs inside AgentDaemon._tick() on the existing periodic schedule.
8Pattern: group exceptions by (type, module, function) → create goals for
9patterns with >= min_occurrences, deduplicating against active goals.
10"""
11import time
12import logging
13import threading
14from typing import Dict, Optional
15from sqlalchemy.orm import Session
17logger = logging.getLogger('hevolve_social')
20class SelfHealingDispatcher:
21 """Creates coding fix goals from recurring exception patterns."""
23 _instance = None
24 _create_lock = threading.Lock()
26 def __init__(self):
27 self._last_check = 0.0
28 self._check_interval = int(300) # 5 minutes
29 self._min_occurrences = int(3) # require 3+ of same type before creating goal
30 self._lock = threading.RLock()
32 @classmethod
33 def get_instance(cls) -> 'SelfHealingDispatcher':
34 if cls._instance is None:
35 with cls._create_lock:
36 if cls._instance is None:
37 cls._instance = cls()
38 return cls._instance
40 @classmethod
41 def reset_instance(cls):
42 """Reset singleton (for testing)."""
43 with cls._create_lock:
44 cls._instance = None
46 def check_and_dispatch(self, db: Session) -> int:
47 """Check for recurring exception patterns and create fix goals.
49 Returns number of fix goals created.
50 """
51 now = time.time()
52 if now - self._last_check < self._check_interval:
53 return 0
55 with self._lock:
56 self._last_check = now
58 try:
59 from exception_collector import ExceptionCollector
60 collector = ExceptionCollector.get_instance()
61 except ImportError:
62 return 0
64 # Get patterns with >= min_occurrences
65 patterns = collector.get_patterns(
66 since=now - 3600, # look back 1 hour
67 min_count=self._min_occurrences,
68 )
70 if not patterns:
71 return 0
73 goals_created = 0
74 for pattern_key, records in patterns.items():
75 if self._is_already_being_fixed(db, pattern_key):
76 continue
78 goal_result = self._create_fix_goal(db, pattern_key, records)
79 if goal_result and goal_result.get('success'):
80 collector.mark_pattern_resolved(pattern_key)
81 goals_created += 1
82 logger.info(f"Self-heal goal created for pattern: {pattern_key}")
84 return goals_created
86 def _create_fix_goal(self, db: Session, pattern_key: str,
87 records: list) -> Optional[Dict]:
88 """Create a coding goal from an exception pattern."""
89 try:
90 from .goal_manager import GoalManager
91 except ImportError:
92 return None
94 sample = records[0]
95 parts = pattern_key.split('::')
96 exc_type = parts[0] if len(parts) > 0 else 'Unknown'
97 module = parts[1] if len(parts) > 1 else 'unknown'
98 function = parts[2] if len(parts) > 2 else 'unknown'
100 title = f"Fix {exc_type} in {module}.{function}"
101 if len(title) > 200:
102 title = title[:197] + '...'
104 # Collect unique error messages for context
105 unique_messages = list(dict.fromkeys(r.exc_message for r in records[:5]))
106 sample_traceback = records[-1].traceback_str[:2000]
108 description = (
109 f"Recurring exception detected ({len(records)} occurrences in last hour).\n\n"
110 f"Exception: {exc_type}\n"
111 f"Module: {module}\n"
112 f"Function: {function}\n"
113 f"Messages: {'; '.join(unique_messages)}\n\n"
114 f"Sample traceback:\n{sample_traceback}\n\n"
115 f"Fix the root cause. Do not just add try/except — understand why "
116 f"the exception occurs and fix the underlying issue."
117 )
119 config = {
120 'mode': 'self_heal',
121 'pattern_key': pattern_key,
122 'source_module': module,
123 'source_function': function,
124 'exc_type': exc_type,
125 'occurrence_count': len(records),
126 'sample_traceback': sample_traceback,
127 }
129 return GoalManager.create_goal(
130 db,
131 goal_type='self_heal',
132 title=title,
133 description=description,
134 config=config,
135 spark_budget=100,
136 created_by='self_healing_dispatcher',
137 )
139 def _is_already_being_fixed(self, db: Session, pattern_key: str) -> bool:
140 """Check if an active goal already targets this exception pattern."""
141 try:
142 from integrations.social.models import AgentGoal
143 except ImportError:
144 return False
146 active_goals = db.query(AgentGoal).filter(
147 AgentGoal.status == 'active',
148 AgentGoal.goal_type == 'self_heal',
149 ).all()
151 for goal in active_goals:
152 config = goal.config_json or {}
153 if config.get('pattern_key') == pattern_key:
154 return True
156 return False