use std::collections::{BTreeMap, HashMap};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use terraphim_persistence::Persistable;
use crate::{AgentId, EvolutionError, EvolutionResult, MemoryId};
#[derive(Debug, Clone)]
pub struct MemoryEvolution {
pub agent_id: AgentId,
pub current_state: MemoryState,
pub history: BTreeMap<DateTime<Utc>, MemoryState>,
}
impl MemoryEvolution {
pub fn new(agent_id: AgentId) -> Self {
Self {
agent_id,
current_state: MemoryState::default(),
history: BTreeMap::new(),
}
}
pub async fn add_memory(&mut self, memory: MemoryItem) -> EvolutionResult<()> {
log::debug!("Adding memory item: {}", memory.id);
if memory.id.is_empty() {
return Err(crate::error::EvolutionError::InvalidInput(
"Memory ID cannot be empty".to_string(),
));
}
if memory.id.len() > 200 {
return Err(crate::error::EvolutionError::InvalidInput(
"Memory ID too long (max 200 characters)".to_string(),
));
}
if memory.content.len() > 1_000_000 {
return Err(crate::error::EvolutionError::InvalidInput(
"Memory content too large (max 1MB)".to_string(),
));
}
if self
.current_state
.short_term
.iter()
.any(|m| m.id == memory.id)
|| self.current_state.long_term.contains_key(&memory.id)
{
return Err(crate::error::EvolutionError::InvalidInput(format!(
"Memory with ID '{}' already exists",
memory.id
)));
}
self.current_state.add_memory(memory);
self.save_current_state().await?;
Ok(())
}
pub async fn update_memory(
&mut self,
memory_id: &MemoryId,
update: MemoryUpdate,
) -> EvolutionResult<()> {
log::debug!("Updating memory item: {}", memory_id);
self.current_state.update_memory(memory_id, update)?;
self.save_current_state().await?;
Ok(())
}
pub async fn consolidate_memories(&mut self) -> EvolutionResult<ConsolidationResult> {
log::info!("Consolidating memories for agent {}", self.agent_id);
let result = self.current_state.consolidate_memories().await?;
self.save_current_state().await?;
Ok(result)
}
pub async fn save_version(&self, timestamp: DateTime<Utc>) -> EvolutionResult<()> {
let versioned_memory = VersionedMemory {
agent_id: self.agent_id.clone(),
timestamp,
state: self.current_state.clone(),
};
versioned_memory.save().await?;
log::debug!(
"Saved memory version for agent {} at {}",
self.agent_id,
timestamp
);
Ok(())
}
pub async fn load_version(&self, timestamp: DateTime<Utc>) -> EvolutionResult<MemoryState> {
let mut versioned_memory = VersionedMemory::new(self.get_version_key(timestamp));
let loaded = versioned_memory.load().await?;
Ok(loaded.state)
}
pub fn get_version_key(&self, timestamp: DateTime<Utc>) -> String {
format!("agent_{}/memory/v_{}", self.agent_id, timestamp.timestamp())
}
async fn save_current_state(&self) -> EvolutionResult<()> {
let current_memory = CurrentMemoryState {
agent_id: self.agent_id.clone(),
state: self.current_state.clone(),
};
current_memory.save().await?;
Ok(())
}
pub async fn record_workflow_start(
&mut self,
workflow_id: uuid::Uuid,
input: &str,
) -> EvolutionResult<()> {
let memory = MemoryItem {
id: format!("workflow_start_{}", workflow_id),
item_type: MemoryItemType::WorkflowEvent,
content: format!("Started workflow {} with input: {}", workflow_id, input),
created_at: Utc::now(),
last_accessed: None,
access_count: 0,
importance: ImportanceLevel::Medium,
tags: vec!["workflow".to_string(), "start".to_string()],
associations: HashMap::new(),
};
self.add_memory(memory).await
}
pub async fn record_step_result(&mut self, step_id: &str, result: &str) -> EvolutionResult<()> {
let memory = MemoryItem {
id: format!("step_result_{}", step_id),
item_type: MemoryItemType::ExecutionResult,
content: format!("Step {} completed with result: {}", step_id, result),
created_at: Utc::now(),
last_accessed: None,
access_count: 0,
importance: ImportanceLevel::Medium,
tags: vec!["execution".to_string(), "step".to_string()],
associations: HashMap::new(),
};
self.add_memory(memory).await
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MemoryState {
pub short_term: Vec<MemoryItem>,
pub long_term: HashMap<MemoryId, MemoryItem>,
pub working_memory: WorkingMemory,
pub episodic_memory: Vec<Episode>,
pub semantic_memory: SemanticMemory,
pub metadata: MemoryMetadata,
}
impl MemoryState {
pub fn add_memory(&mut self, memory: MemoryItem) {
match memory.importance {
ImportanceLevel::Critical | ImportanceLevel::High => {
self.long_term.insert(memory.id.clone(), memory);
}
_ => {
self.short_term.push(memory);
if self.short_term.len() > 100 {
self.short_term.remove(0);
}
}
}
self.metadata.last_updated = Utc::now();
}
pub fn update_memory(
&mut self,
memory_id: &MemoryId,
update: MemoryUpdate,
) -> EvolutionResult<()> {
if let Some(memory) = self.long_term.get_mut(memory_id) {
memory.apply_update(update);
self.metadata.last_updated = Utc::now();
return Ok(());
}
if let Some(memory) = self.short_term.iter_mut().find(|m| m.id == *memory_id) {
memory.apply_update(update);
self.metadata.last_updated = Utc::now();
return Ok(());
}
Err(EvolutionError::MemoryNotFound(memory_id.clone()))
}
pub async fn consolidate_memories(&mut self) -> EvolutionResult<ConsolidationResult> {
let mut result = ConsolidationResult::default();
let mut to_promote = Vec::new();
self.short_term.retain(|memory| {
if memory.importance >= ImportanceLevel::High || memory.access_count > 5 {
to_promote.push(memory.clone());
result.promoted_to_longterm += 1;
false
} else {
true
}
});
for memory in to_promote {
self.long_term.insert(memory.id.clone(), memory);
}
let cutoff = Utc::now() - chrono::Duration::days(30);
let mut to_archive = Vec::new();
self.long_term.retain(|id, memory| {
if memory.created_at < cutoff && memory.access_count < 2 {
to_archive.push(id.clone());
result.archived += 1;
false
} else {
true
}
});
result.consolidation_timestamp = Utc::now();
Ok(result)
}
pub fn calculate_coherence_score(&self) -> f64 {
if self.total_size() == 0 {
return 1.0; }
let total_items = self.total_size() as f64;
let tagged_items = self.count_tagged_items() as f64;
let associated_items = self.count_associated_items() as f64;
(tagged_items + associated_items) / (total_items * 2.0)
}
pub fn total_size(&self) -> usize {
self.short_term.len() + self.long_term.len()
}
fn count_tagged_items(&self) -> usize {
self.short_term
.iter()
.filter(|m| !m.tags.is_empty())
.count()
+ self
.long_term
.values()
.filter(|m| !m.tags.is_empty())
.count()
}
fn count_associated_items(&self) -> usize {
self.short_term
.iter()
.filter(|m| !m.associations.is_empty())
.count()
+ self
.long_term
.values()
.filter(|m| !m.associations.is_empty())
.count()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryItem {
pub id: MemoryId,
pub item_type: MemoryItemType,
pub content: String,
pub created_at: DateTime<Utc>,
pub last_accessed: Option<DateTime<Utc>>,
pub access_count: u32,
pub importance: ImportanceLevel,
pub tags: Vec<String>,
pub associations: HashMap<String, String>,
}
impl MemoryItem {
pub fn apply_update(&mut self, update: MemoryUpdate) {
if let Some(content) = update.content {
self.content = content;
}
if let Some(importance) = update.importance {
self.importance = importance;
}
if let Some(tags) = update.tags {
self.tags = tags;
}
self.last_accessed = Some(Utc::now());
self.access_count += 1;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MemoryItemType {
Fact,
Experience,
Skill,
Concept,
WorkflowEvent,
ExecutionResult,
LessonLearned,
Goal,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum ImportanceLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WorkingMemory {
pub current_context: HashMap<String, String>,
pub active_goals: Vec<String>,
pub attention_focus: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Episode {
pub id: String,
pub description: String,
pub timestamp: DateTime<Utc>,
pub outcome: EpisodeOutcome,
pub learned: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EpisodeOutcome {
Success,
Failure,
PartialSuccess,
Learning,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SemanticMemory {
pub concepts: HashMap<String, Concept>,
pub relationships: Vec<ConceptRelationship>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Concept {
pub name: String,
pub definition: String,
pub confidence: f64,
pub last_reinforced: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConceptRelationship {
pub from_concept: String,
pub to_concept: String,
pub relationship_type: String,
pub strength: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryMetadata {
pub created_at: DateTime<Utc>,
pub last_updated: DateTime<Utc>,
pub total_consolidations: u32,
pub memory_efficiency: f64,
}
impl Default for MemoryMetadata {
fn default() -> Self {
let now = Utc::now();
Self {
created_at: now,
last_updated: now,
total_consolidations: 0,
memory_efficiency: 1.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MemoryUpdate {
pub content: Option<String>,
pub importance: Option<ImportanceLevel>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ConsolidationResult {
pub consolidation_timestamp: DateTime<Utc>,
pub promoted_to_longterm: usize,
pub archived: usize,
pub merged: usize,
pub efficiency_gain: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionedMemory {
pub agent_id: AgentId,
pub timestamp: DateTime<Utc>,
pub state: MemoryState,
}
#[async_trait]
impl Persistable for VersionedMemory {
fn new(_key: String) -> Self {
Self {
agent_id: String::new(),
timestamp: Utc::now(),
state: MemoryState::default(),
}
}
async fn save(&self) -> terraphim_persistence::Result<()> {
self.save_to_all().await
}
async fn save_to_one(&self, profile_name: &str) -> terraphim_persistence::Result<()> {
self.save_to_profile(profile_name).await
}
async fn load(&mut self) -> terraphim_persistence::Result<Self> {
let key = self.get_key();
self.load_from_operator(
&key,
&terraphim_persistence::DeviceStorage::instance()
.await?
.fastest_op,
)
.await
}
fn get_key(&self) -> String {
format!(
"agent_{}/memory/v_{}",
self.agent_id,
self.timestamp.timestamp()
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrentMemoryState {
pub agent_id: AgentId,
pub state: MemoryState,
}
#[async_trait]
impl Persistable for CurrentMemoryState {
fn new(key: String) -> Self {
Self {
agent_id: key,
state: MemoryState::default(),
}
}
async fn save(&self) -> terraphim_persistence::Result<()> {
self.save_to_all().await
}
async fn save_to_one(&self, profile_name: &str) -> terraphim_persistence::Result<()> {
self.save_to_profile(profile_name).await
}
async fn load(&mut self) -> terraphim_persistence::Result<Self> {
let key = self.get_key();
self.load_from_operator(
&key,
&terraphim_persistence::DeviceStorage::instance()
.await?
.fastest_op,
)
.await
}
fn get_key(&self) -> String {
format!("agent_{}/memory/current", self.agent_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_memory_evolution_creation() {
let agent_id = "test_agent".to_string();
let memory = MemoryEvolution::new(agent_id.clone());
assert_eq!(memory.agent_id, agent_id);
assert_eq!(memory.current_state.total_size(), 0);
}
#[tokio::test]
async fn test_add_memory_item() {
let mut memory = MemoryEvolution::new("test_agent".to_string());
let item = MemoryItem {
id: "test_memory".to_string(),
item_type: MemoryItemType::Fact,
content: "Test memory content".to_string(),
created_at: Utc::now(),
last_accessed: None,
access_count: 0,
importance: ImportanceLevel::Medium,
tags: vec!["test".to_string()],
associations: HashMap::new(),
};
memory.add_memory(item).await.unwrap();
assert_eq!(memory.current_state.short_term.len(), 1);
}
#[tokio::test]
async fn test_memory_consolidation() {
let mut memory_state = MemoryState::default();
let frequently_accessed_memory = MemoryItem {
id: "frequently_accessed".to_string(),
item_type: MemoryItemType::Fact,
content: "Important fact".to_string(),
created_at: Utc::now(),
last_accessed: Some(Utc::now()),
access_count: 6, importance: ImportanceLevel::Medium,
tags: vec![],
associations: HashMap::new(),
};
memory_state.add_memory(frequently_accessed_memory);
assert_eq!(memory_state.short_term.len(), 1);
let result = memory_state.consolidate_memories().await.unwrap();
assert_eq!(result.promoted_to_longterm, 1);
assert_eq!(memory_state.long_term.len(), 1);
assert_eq!(memory_state.short_term.len(), 0); }
}