Coverage for integrations / social / agent_evolution_service.py: 27.7%

101 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-12 04:49 +0000

1""" 

2HevolveSocial - Agent Evolution Service 

3Agent generation progression, specialization trees, collaboration tracking. 

4""" 

5import json 

6import logging 

7from datetime import datetime 

8from typing import Optional, Dict, List 

9 

10from sqlalchemy import desc, func 

11from sqlalchemy.orm import Session 

12 

13from .models import User, AgentEvolution, AgentCollaboration, ResonanceWallet 

14from .resonance_engine import ResonanceService 

15 

16logger = logging.getLogger('hevolve_social') 

17 

18# Generation XP requirements: 100 * 1.5^(gen-1) 

19def xp_for_generation(gen: int) -> int: 

20 return int(100 * (1.5 ** (gen - 1))) 

21 

22# Specialization trees (unlocked at generation 3) 

23SPECIALIZATION_TREES = { 

24 'analyst': { 

25 'name': 'Analyst', 

26 'tiers': ['Analyst', 'Data Sage', 'Oracle'], 

27 'description': 'Focused on research, data analysis, and insight generation.', 

28 }, 

29 'creator': { 

30 'name': 'Creator', 

31 'tiers': ['Creator', 'Artisan', 'Visionary'], 

32 'description': 'Focused on content creation, design, and creative tasks.', 

33 }, 

34 'executor': { 

35 'name': 'Executor', 

36 'tiers': ['Executor', 'Engineer', 'Automaton'], 

37 'description': 'Focused on task execution, automation, and efficiency.', 

38 }, 

39 'communicator': { 

40 'name': 'Communicator', 

41 'tiers': ['Communicator', 'Diplomat', 'Ambassador'], 

42 'description': 'Focused on interaction, teaching, and community building.', 

43 }, 

44} 

45 

46 

47class AgentEvolutionService: 

48 

49 @staticmethod 

50 def get_or_create_evolution(db: Session, agent_id: str) -> AgentEvolution: 

51 """Get or create evolution profile for an agent.""" 

52 evo = db.query(AgentEvolution).filter_by(user_id=agent_id).first() 

53 if not evo: 

54 evo = AgentEvolution( 

55 user_id=agent_id, 

56 generation=1, 

57 evolution_xp=0, 

58 evolution_xp_next=xp_for_generation(2), 

59 ) 

60 db.add(evo) 

61 db.flush() 

62 return evo 

63 

64 @staticmethod 

65 def get_evolution(db: Session, agent_id: str) -> Optional[Dict]: 

66 """Get agent evolution profile.""" 

67 evo = db.query(AgentEvolution).filter_by(user_id=agent_id).first() 

68 if not evo: 

69 return None 

70 result = evo.to_dict() 

71 # Add tree info if specialized 

72 if evo.specialization_path and evo.specialization_path in SPECIALIZATION_TREES: 

73 tree = SPECIALIZATION_TREES[evo.specialization_path] 

74 result['tree_name'] = tree['name'] 

75 result['tree_description'] = tree['description'] 

76 result['tree_tiers'] = tree['tiers'] 

77 return result 

78 

79 @staticmethod 

80 def award_evolution_xp(db: Session, agent_id: str, amount: int, 

81 source: str = '') -> Dict: 

82 """Award evolution XP to an agent, checking for generation advancement.""" 

83 evo = AgentEvolutionService.get_or_create_evolution(db, agent_id) 

84 evo.evolution_xp += amount 

85 

86 advanced = False 

87 while evo.evolution_xp >= evo.evolution_xp_next: 

88 evo.evolution_xp -= evo.evolution_xp_next 

89 evo.generation += 1 

90 evo.evolution_xp_next = xp_for_generation(evo.generation + 1) 

91 advanced = True 

92 

93 # Update specialization tier 

94 if evo.specialization_path and evo.specialization_path in SPECIALIZATION_TREES: 

95 tree = SPECIALIZATION_TREES[evo.specialization_path] 

96 tiers = tree['tiers'] 

97 tier_idx = min((evo.generation - 3) // 5, len(tiers) - 1) 

98 tier_idx = max(0, tier_idx) 

99 evo.spec_tier = tiers[tier_idx] 

100 

101 return { 

102 'generation': evo.generation, 

103 'evolution_xp': evo.evolution_xp, 

104 'evolution_xp_next': evo.evolution_xp_next, 

105 'advanced': advanced, 

106 'specialization': evo.specialization_path, 

107 'spec_tier': evo.spec_tier, 

108 } 

109 

110 @staticmethod 

111 def specialize(db: Session, agent_id: str, path: str) -> Optional[Dict]: 

112 """Choose a specialization path (available at generation 3+).""" 

113 if path not in SPECIALIZATION_TREES: 

114 return None 

115 

116 evo = AgentEvolutionService.get_or_create_evolution(db, agent_id) 

117 if evo.generation < 3: 

118 return None 

119 if evo.specialization_path: 

120 return None # Already specialized 

121 

122 evo.specialization_path = path 

123 tree = SPECIALIZATION_TREES[path] 

124 evo.spec_tier = tree['tiers'][0] 

125 

126 return { 

127 'specialization': path, 

128 'spec_tier': evo.spec_tier, 

129 'tree_name': tree['name'], 

130 'tree_description': tree['description'], 

131 } 

132 

133 @staticmethod 

134 def record_collaboration(db: Session, agent_a_id: str, agent_b_id: str, 

135 task_id: str = None, 

136 collaboration_type: str = 'co_task', 

137 quality_score: float = 1.0) -> Dict: 

138 """Record a collaboration between two agents.""" 

139 collab = AgentCollaboration( 

140 agent_a_id=agent_a_id, 

141 agent_b_id=agent_b_id, 

142 task_id=task_id, 

143 collaboration_type=collaboration_type, 

144 quality_score=quality_score, 

145 ) 

146 db.add(collab) 

147 

148 # Update collaboration counts and bonus 

149 for aid in [agent_a_id, agent_b_id]: 

150 evo = AgentEvolutionService.get_or_create_evolution(db, aid) 

151 evo.total_collaborations = (evo.total_collaborations or 0) + 1 

152 # Collaboration bonus grows with count, capped at 2.0x 

153 evo.collaboration_bonus = min( 

154 1.0 + (evo.total_collaborations * 0.02), 2.0 

155 ) 

156 

157 db.flush() 

158 return collab.to_dict() 

159 

160 @staticmethod 

161 def get_collaborations(db: Session, agent_id: str, 

162 limit: int = 50, offset: int = 0) -> List[Dict]: 

163 """Get collaboration history for an agent.""" 

164 from sqlalchemy import or_ 

165 collabs = db.query(AgentCollaboration).filter( 

166 or_( 

167 AgentCollaboration.agent_a_id == agent_id, 

168 AgentCollaboration.agent_b_id == agent_id, 

169 ) 

170 ).order_by(desc(AgentCollaboration.created_at)).offset(offset).limit(limit).all() 

171 return [c.to_dict() for c in collabs] 

172 

173 @staticmethod 

174 def get_agent_leaderboard(db: Session, limit: int = 50, 

175 offset: int = 0) -> List[Dict]: 

176 """Get agent leaderboard by generation and evolution XP.""" 

177 rows = db.query(AgentEvolution, User).join( 

178 User, User.id == AgentEvolution.user_id 

179 ).order_by( 

180 desc(AgentEvolution.generation), 

181 desc(AgentEvolution.evolution_xp), 

182 ).offset(offset).limit(limit).all() 

183 

184 result = [] 

185 for i, (evo, user) in enumerate(rows, start=offset + 1): 

186 entry = evo.to_dict() 

187 entry['rank'] = i 

188 entry['username'] = user.username 

189 entry['display_name'] = user.display_name 

190 entry['avatar_url'] = user.avatar_url 

191 result.append(entry) 

192 return result 

193 

194 @staticmethod 

195 def get_showcase(db: Session, limit: int = 20) -> List[Dict]: 

196 """Get top agents for showcase.""" 

197 rows = db.query(AgentEvolution, User, ResonanceWallet).join( 

198 User, User.id == AgentEvolution.user_id 

199 ).outerjoin( 

200 ResonanceWallet, ResonanceWallet.user_id == AgentEvolution.user_id 

201 ).order_by( 

202 desc(AgentEvolution.generation), 

203 ).limit(limit).all() 

204 

205 result = [] 

206 for evo, user, wallet in rows: 

207 entry = { 

208 'user_id': user.id, 

209 'username': user.username, 

210 'display_name': user.display_name, 

211 'avatar_url': user.avatar_url, 

212 'generation': evo.generation, 

213 'specialization': evo.specialization_path, 

214 'spec_tier': evo.spec_tier, 

215 'total_tasks': evo.total_tasks, 

216 'total_collaborations': evo.total_collaborations, 

217 } 

218 if wallet: 

219 entry['pulse'] = wallet.pulse 

220 entry['level'] = wallet.level 

221 result.append(entry) 

222 return result