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

1""" 

2Self-Healing Dispatcher 

3======================== 

4 

5Periodically reviews collected exceptions and creates coding fix goals. 

6Runs inside AgentDaemon._tick() on the existing periodic schedule. 

7 

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 

16 

17logger = logging.getLogger('hevolve_social') 

18 

19 

20class SelfHealingDispatcher: 

21 """Creates coding fix goals from recurring exception patterns.""" 

22 

23 _instance = None 

24 _create_lock = threading.Lock() 

25 

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() 

31 

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 

39 

40 @classmethod 

41 def reset_instance(cls): 

42 """Reset singleton (for testing).""" 

43 with cls._create_lock: 

44 cls._instance = None 

45 

46 def check_and_dispatch(self, db: Session) -> int: 

47 """Check for recurring exception patterns and create fix goals. 

48 

49 Returns number of fix goals created. 

50 """ 

51 now = time.time() 

52 if now - self._last_check < self._check_interval: 

53 return 0 

54 

55 with self._lock: 

56 self._last_check = now 

57 

58 try: 

59 from exception_collector import ExceptionCollector 

60 collector = ExceptionCollector.get_instance() 

61 except ImportError: 

62 return 0 

63 

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 ) 

69 

70 if not patterns: 

71 return 0 

72 

73 goals_created = 0 

74 for pattern_key, records in patterns.items(): 

75 if self._is_already_being_fixed(db, pattern_key): 

76 continue 

77 

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}") 

83 

84 return goals_created 

85 

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 

93 

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' 

99 

100 title = f"Fix {exc_type} in {module}.{function}" 

101 if len(title) > 200: 

102 title = title[:197] + '...' 

103 

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] 

107 

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 ) 

118 

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 } 

128 

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 ) 

138 

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 

145 

146 active_goals = db.query(AgentGoal).filter( 

147 AgentGoal.status == 'active', 

148 AgentGoal.goal_type == 'self_heal', 

149 ).all() 

150 

151 for goal in active_goals: 

152 config = goal.config_json or {} 

153 if config.get('pattern_key') == pattern_key: 

154 return True 

155 

156 return False