use chrono::Utc;
use rand::seq::SliceRandom;
use crate::config::MemoryConfig;
use crate::storage::Storage;
use crate::types::{MemoryLayer, MemoryRecord};
pub fn apply_decay(record: &mut MemoryRecord, dt_days: f64, mu1: f64, mu2: f64) {
if record.pinned {
return;
}
record.working_strength *= (-mu1 * dt_days).exp();
record.core_strength *= (-mu2 * dt_days).exp();
}
pub fn consolidate_single(record: &mut MemoryRecord, dt_days: f64, config: &MemoryConfig) {
if record.pinned {
return;
}
let effective_alpha = config.alpha * (0.2 + record.importance.powi(2));
let transfer = effective_alpha * record.working_strength * dt_days;
record.core_strength += transfer;
apply_decay(record, dt_days, config.mu1, config.mu2);
record.consolidation_count += 1;
record.last_consolidated = Some(Utc::now());
}
pub fn run_consolidation_cycle(
storage: &mut Storage,
dt_days: f64,
config: &MemoryConfig,
namespace: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut all_memories = storage.all_in_namespace(namespace)?;
let mut rng = rand::thread_rng();
for record in all_memories.iter_mut().filter(|r| r.layer == MemoryLayer::Working) {
consolidate_single(record, dt_days, config);
}
let mut archive: Vec<_> = all_memories
.iter_mut()
.filter(|r| r.layer == MemoryLayer::Archive)
.collect();
if !archive.is_empty() {
let n_replay = ((archive.len() as f64 * config.interleave_ratio).ceil() as usize).max(1);
archive.shuffle(&mut rng);
for record in archive.iter_mut().take(n_replay) {
record.core_strength += config.replay_boost * (0.5 + record.importance);
record.consolidation_count += 1;
record.last_consolidated = Some(Utc::now());
}
}
for record in all_memories.iter_mut().filter(|r| r.layer == MemoryLayer::Core) {
apply_decay(record, dt_days, 0.0, config.mu2); }
rebalance_layers(&mut all_memories, config);
storage.begin_transaction()?;
let result = (|| -> Result<(), Box<dyn std::error::Error>> {
for record in all_memories {
storage.update(&record)?;
}
Ok(())
})();
match result {
Ok(()) => storage.commit_transaction()?,
Err(e) => {
let _ = storage.rollback_transaction();
eprintln!("[engram] Consolidation failed, attempting FTS rebuild: {}", e);
let _ = storage.rebuild_fts();
return Err(e);
}
}
Ok(())
}
fn rebalance_layers(memories: &mut [MemoryRecord], config: &MemoryConfig) {
for record in memories {
let total = record.working_strength + record.core_strength;
if record.pinned {
record.layer = MemoryLayer::Core;
continue;
}
match record.layer {
MemoryLayer::Working => {
if record.core_strength >= config.promote_threshold {
record.layer = MemoryLayer::Core;
} else if record.working_strength < config.archive_threshold
&& record.core_strength < config.archive_threshold
{
record.layer = MemoryLayer::Archive;
}
}
MemoryLayer::Core => {
if total < config.demote_threshold && !record.pinned {
record.layer = MemoryLayer::Archive;
}
}
MemoryLayer::Archive => {
if record.core_strength >= config.promote_threshold {
record.layer = MemoryLayer::Core;
}
}
}
}
}