terraphim_agent_evolution/
evolution.rs1use chrono::{DateTime, Utc};
4use futures::try_join;
5use serde::{Deserialize, Serialize};
6
7use crate::{AgentId, EvolutionResult, LessonsEvolution, MemoryEvolution, TasksEvolution};
8
9#[derive(Debug, Clone)]
11pub struct AgentEvolutionSystem {
12 pub agent_id: AgentId,
13 pub memory: MemoryEvolution,
14 pub tasks: TasksEvolution,
15 pub lessons: LessonsEvolution,
16}
17
18impl AgentEvolutionSystem {
19 pub fn new(agent_id: AgentId) -> Self {
21 Self {
22 agent_id: agent_id.clone(),
23 memory: MemoryEvolution::new(agent_id.clone()),
24 tasks: TasksEvolution::new(agent_id.clone()),
25 lessons: LessonsEvolution::new(agent_id.clone()),
26 }
27 }
28
29 pub async fn create_snapshot(&self, description: String) -> EvolutionResult<()> {
31 log::info!("Creating snapshot: {}", description);
32 self.save_snapshot().await
33 }
34
35 pub async fn save_snapshot(&self) -> EvolutionResult<()> {
37 let timestamp = Utc::now();
38
39 log::debug!(
40 "Saving evolution snapshot for agent {} at {}",
41 self.agent_id,
42 timestamp
43 );
44
45 let (_, _, _, _) = try_join!(
47 self.memory.save_version(timestamp),
48 self.tasks.save_version(timestamp),
49 self.lessons.save_version(timestamp),
50 self.save_evolution_index(timestamp)
51 )?;
52
53 log::info!(
54 "✅ Saved complete evolution snapshot for agent {}",
55 self.agent_id
56 );
57 Ok(())
58 }
59
60 pub async fn load_snapshot(&self, timestamp: DateTime<Utc>) -> EvolutionResult<AgentSnapshot> {
62 log::debug!(
63 "Loading evolution snapshot for agent {} at {}",
64 self.agent_id,
65 timestamp
66 );
67
68 Ok(AgentSnapshot {
69 agent_id: self.agent_id.clone(),
70 timestamp,
71 memory: self.memory.load_version(timestamp).await?,
72 tasks: self.tasks.load_version(timestamp).await?,
73 lessons: self.lessons.load_version(timestamp).await?,
74 alignment_score: self.calculate_alignment_at(timestamp).await?,
75 })
76 }
77
78 pub async fn get_evolution_summary(
80 &self,
81 start: DateTime<Utc>,
82 end: DateTime<Utc>,
83 ) -> EvolutionResult<EvolutionSummary> {
84 let snapshots = self.get_snapshots_in_range(start, end).await?;
85
86 Ok(EvolutionSummary {
87 agent_id: self.agent_id.clone(),
88 time_range: (start, end),
89 snapshot_count: snapshots.len(),
90 memory_growth: self.calculate_memory_growth(&snapshots),
91 task_completion_rate: self.calculate_task_completion_rate(&snapshots),
92 learning_velocity: self.calculate_learning_velocity(&snapshots),
93 alignment_trend: self.calculate_alignment_trend(&snapshots),
94 })
95 }
96
97 async fn save_evolution_index(&self, timestamp: DateTime<Utc>) -> EvolutionResult<()> {
99 use terraphim_persistence::Persistable;
100
101 let index = EvolutionIndex {
102 agent_id: self.agent_id.clone(),
103 timestamp,
104 memory_snapshot_key: self.memory.get_version_key(timestamp),
105 tasks_snapshot_key: self.tasks.get_version_key(timestamp),
106 lessons_snapshot_key: self.lessons.get_version_key(timestamp),
107 };
108
109 index.save().await?;
110 Ok(())
111 }
112
113 async fn calculate_alignment_at(&self, timestamp: DateTime<Utc>) -> EvolutionResult<f64> {
115 let memory_state = self.memory.load_version(timestamp).await?;
117 let tasks_state = self.tasks.load_version(timestamp).await?;
118
119 let task_alignment = tasks_state.calculate_alignment_score();
121 let memory_alignment = memory_state.calculate_coherence_score();
122
123 Ok(task_alignment * 0.6 + memory_alignment * 0.4)
124 }
125
126 async fn get_snapshots_in_range(
128 &self,
129 _start: DateTime<Utc>,
130 _end: DateTime<Utc>,
131 ) -> EvolutionResult<Vec<AgentSnapshot>> {
132 Ok(vec![])
135 }
136
137 fn calculate_memory_growth(&self, snapshots: &[AgentSnapshot]) -> MemoryGrowthMetrics {
139 if snapshots.is_empty() {
140 return MemoryGrowthMetrics::default();
141 }
142
143 let start_memory_size = snapshots
144 .first()
145 .map(|s| s.memory.total_size())
146 .unwrap_or(0);
147 let end_memory_size = snapshots.last().map(|s| s.memory.total_size()).unwrap_or(0);
148
149 MemoryGrowthMetrics {
150 initial_size: start_memory_size,
151 final_size: end_memory_size,
152 growth_rate: if start_memory_size > 0 {
153 (end_memory_size as f64 - start_memory_size as f64) / start_memory_size as f64
154 } else {
155 0.0
156 },
157 consolidation_events: 0, }
159 }
160
161 fn calculate_task_completion_rate(&self, snapshots: &[AgentSnapshot]) -> f64 {
163 if snapshots.is_empty() {
164 return 0.0;
165 }
166
167 let total_tasks: usize = snapshots.iter().map(|s| s.tasks.total_tasks()).sum();
168 let completed_tasks: usize = snapshots.iter().map(|s| s.tasks.completed_tasks()).sum();
169
170 if total_tasks > 0 {
171 completed_tasks as f64 / total_tasks as f64
172 } else {
173 0.0
174 }
175 }
176
177 fn calculate_learning_velocity(&self, snapshots: &[AgentSnapshot]) -> f64 {
179 if snapshots.len() < 2 {
180 return 0.0;
181 }
182
183 let first_snapshot = snapshots
184 .first()
185 .expect("snapshots should have at least 2 elements");
186 let last_snapshot = snapshots
187 .last()
188 .expect("snapshots should have at least 2 elements");
189 let start_lessons = first_snapshot.lessons.total_lessons();
190 let end_lessons = last_snapshot.lessons.total_lessons();
191 let time_diff = last_snapshot.timestamp - first_snapshot.timestamp;
192
193 if time_diff.num_hours() > 0 {
194 (end_lessons - start_lessons) as f64 / time_diff.num_hours() as f64
195 } else {
196 0.0
197 }
198 }
199
200 fn calculate_alignment_trend(&self, snapshots: &[AgentSnapshot]) -> AlignmentTrend {
202 if snapshots.len() < 2 {
203 return AlignmentTrend::Stable;
204 }
205
206 let first_alignment = snapshots
207 .first()
208 .expect("snapshots should have at least 2 elements")
209 .alignment_score;
210 let last_alignment = snapshots
211 .last()
212 .expect("snapshots should have at least 2 elements")
213 .alignment_score;
214 let diff = last_alignment - first_alignment;
215
216 if diff > 0.1 {
217 AlignmentTrend::Improving
218 } else if diff < -0.1 {
219 AlignmentTrend::Declining
220 } else {
221 AlignmentTrend::Stable
222 }
223 }
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct AgentSnapshot {
229 pub agent_id: AgentId,
230 pub timestamp: DateTime<Utc>,
231 pub memory: crate::MemoryState,
232 pub tasks: crate::TasksState,
233 pub lessons: crate::LessonsState,
234 pub alignment_score: f64,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct EvolutionIndex {
240 pub agent_id: AgentId,
241 pub timestamp: DateTime<Utc>,
242 pub memory_snapshot_key: String,
243 pub tasks_snapshot_key: String,
244 pub lessons_snapshot_key: String,
245}
246
247#[async_trait::async_trait]
248impl terraphim_persistence::Persistable for EvolutionIndex {
249 fn new(key: String) -> Self {
250 Self {
251 agent_id: key,
252 timestamp: Utc::now(),
253 memory_snapshot_key: String::new(),
254 tasks_snapshot_key: String::new(),
255 lessons_snapshot_key: String::new(),
256 }
257 }
258
259 async fn save(&self) -> terraphim_persistence::Result<()> {
260 self.save_to_all().await
261 }
262
263 async fn save_to_one(&self, profile_name: &str) -> terraphim_persistence::Result<()> {
264 self.save_to_profile(profile_name).await
265 }
266
267 async fn load(&mut self) -> terraphim_persistence::Result<Self> {
268 let key = self.get_key();
269 self.load_from_operator(
270 &key,
271 &terraphim_persistence::DeviceStorage::instance()
272 .await?
273 .fastest_op,
274 )
275 .await
276 }
277
278 fn get_key(&self) -> String {
279 format!(
280 "agent_{}/evolution/index/{}",
281 self.agent_id,
282 self.timestamp.timestamp()
283 )
284 }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct EvolutionSummary {
290 pub agent_id: AgentId,
291 pub time_range: (DateTime<Utc>, DateTime<Utc>),
292 pub snapshot_count: usize,
293 pub memory_growth: MemoryGrowthMetrics,
294 pub task_completion_rate: f64,
295 pub learning_velocity: f64,
296 pub alignment_trend: AlignmentTrend,
297}
298
299#[derive(Debug, Clone, Default, Serialize, Deserialize)]
301pub struct MemoryGrowthMetrics {
302 pub initial_size: usize,
303 pub final_size: usize,
304 pub growth_rate: f64,
305 pub consolidation_events: usize,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub enum AlignmentTrend {
311 Improving,
312 Stable,
313 Declining,
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[tokio::test]
321 async fn test_agent_evolution_system_creation() {
322 let agent_id = "test_agent".to_string();
323 let evolution = AgentEvolutionSystem::new(agent_id.clone());
324
325 assert_eq!(evolution.agent_id, agent_id);
326 assert_eq!(evolution.memory.agent_id, agent_id);
327 assert_eq!(evolution.tasks.agent_id, agent_id);
328 assert_eq!(evolution.lessons.agent_id, agent_id);
329 }
330
331 #[tokio::test]
332 async fn test_evolution_summary_calculation() {
333 let agent_id = "test_agent".to_string();
334 let evolution = AgentEvolutionSystem::new(agent_id);
335
336 let now = Utc::now();
337 let earlier = now - chrono::Duration::hours(1);
338
339 let summary = evolution.get_evolution_summary(earlier, now).await.unwrap();
340 assert_eq!(summary.snapshot_count, 0); assert_eq!(summary.task_completion_rate, 0.0);
342 assert_eq!(summary.learning_velocity, 0.0);
343 }
344}