Skip to main content

terraphim_agent_evolution/
evolution.rs

1//! Core agent evolution system that coordinates all three tracking components
2
3use chrono::{DateTime, Utc};
4use futures::try_join;
5use serde::{Deserialize, Serialize};
6
7use crate::{AgentId, EvolutionResult, LessonsEvolution, MemoryEvolution, TasksEvolution};
8
9/// Complete agent evolution system that tracks memory, tasks, and lessons
10#[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    /// Create a new agent evolution system
20    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    /// Create a snapshot with a description
30    pub async fn create_snapshot(&self, description: String) -> EvolutionResult<()> {
31        log::info!("Creating snapshot: {}", description);
32        self.save_snapshot().await
33    }
34
35    /// Save a complete snapshot of all three components with atomic versioning
36    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        // Save all three components concurrently with the same timestamp for consistency
46        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    /// Load complete state at any point in time
61    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    /// Get evolution summary for a time range
79    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    /// Save evolution index for efficient querying
98    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    /// Calculate goal alignment at a specific time
114    async fn calculate_alignment_at(&self, timestamp: DateTime<Utc>) -> EvolutionResult<f64> {
115        // Simplified alignment calculation - can be enhanced
116        let memory_state = self.memory.load_version(timestamp).await?;
117        let tasks_state = self.tasks.load_version(timestamp).await?;
118
119        // Calculate alignment based on task completion and memory coherence
120        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    /// Get all snapshots in a time range
127    async fn get_snapshots_in_range(
128        &self,
129        _start: DateTime<Utc>,
130        _end: DateTime<Utc>,
131    ) -> EvolutionResult<Vec<AgentSnapshot>> {
132        // Implementation would query the evolution index and load snapshots
133        // For now, return empty vector
134        Ok(vec![])
135    }
136
137    /// Calculate memory growth metrics
138    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, // Would track memory consolidation
158        }
159    }
160
161    /// Calculate task completion rate
162    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    /// Calculate learning velocity
178    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    /// Calculate alignment trend
201    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/// Complete snapshot of agent state at a specific time
227#[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/// Evolution index for efficient querying
238#[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/// Summary of agent evolution over a time period
288#[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/// Memory growth metrics
300#[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/// Alignment trend over time
309#[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); // No snapshots yet
341        assert_eq!(summary.task_completion_rate, 0.0);
342        assert_eq!(summary.learning_velocity, 0.0);
343    }
344}