use crate::mem8::{FrequencyBand, MemoryWave, WaveGrid};
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum MemoryType {
Pattern,
Solution,
Conversation,
Technical,
Learning,
Joke,
}
impl MemoryType {
pub fn to_frequency_band(&self) -> FrequencyBand {
match self {
Self::Pattern => FrequencyBand::Delta,
Self::Solution => FrequencyBand::Theta,
Self::Conversation => FrequencyBand::Alpha,
Self::Technical => FrequencyBand::Beta,
Self::Learning => FrequencyBand::Gamma,
Self::Joke => FrequencyBand::HyperGamma,
}
}
pub fn frequency(&self) -> f32 {
let (min, max) = self.to_frequency_band().range();
(min + max) / 2.0
}
pub fn parse(s: &str) -> Self {
match s.to_lowercase().as_str() {
"pattern" | "pattern_insight" => Self::Pattern,
"solution" | "breakthrough" => Self::Solution,
"conversation" | "rapport" => Self::Conversation,
"technical" | "technical_pattern" => Self::Technical,
"learning" | "learning_moment" => Self::Learning,
"joke" | "shared_joke" => Self::Joke,
_ => Self::Technical, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnchoredMemory {
pub id: String,
pub content: String,
pub keywords: Vec<String>,
pub memory_type: MemoryType,
pub valence: f32,
pub arousal: f32,
pub x: u8,
pub y: u8,
pub z: u16,
pub created_at: DateTime<Utc>,
pub last_accessed: DateTime<Utc>,
pub access_count: u32,
pub origin: String,
pub project_path: Option<PathBuf>,
}
impl AnchoredMemory {
pub fn calculate_coordinates(
content: &str,
keywords: &[String],
memory_type: MemoryType,
) -> (u8, u8, u16) {
let content_hash = Self::hash_string(content);
let x = (content_hash % 256) as u8;
let type_offset = match memory_type {
MemoryType::Pattern => 0,
MemoryType::Solution => 42,
MemoryType::Conversation => 84,
MemoryType::Technical => 128,
MemoryType::Learning => 170,
MemoryType::Joke => 212,
};
let keyword_hash = keywords.iter().map(|k| Self::hash_string(k)).sum::<u64>();
let y = ((type_offset as u64 + keyword_hash % 43) % 256) as u8;
let now = Utc::now().timestamp() as u64;
let z = ((now / 60) % 65536) as u16;
(x, y, z)
}
fn hash_string(s: &str) -> u64 {
s.bytes()
.fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64))
}
pub fn resonance_with(&self, other: &AnchoredMemory) -> f32 {
let freq_sim = if self.memory_type == other.memory_type {
1.0
} else {
0.5
};
let overlap = self
.keywords
.iter()
.filter(|k| other.keywords.contains(k))
.count() as f32;
let total = (self.keywords.len() + other.keywords.len()).max(1) as f32;
let keyword_sim = 2.0 * overlap / total;
let valence_diff = (self.valence - other.valence).abs();
let arousal_diff = (self.arousal - other.arousal).abs();
let emotion_sim = 1.0 - (valence_diff + arousal_diff) / 4.0;
let dx = (self.x as i32 - other.x as i32).abs() as f32 / 256.0;
let dy = (self.y as i32 - other.y as i32).abs() as f32 / 256.0;
let spatial_sim = 1.0 - (dx + dy) / 2.0;
0.3 * freq_sim + 0.4 * keyword_sim + 0.2 * emotion_sim + 0.1 * spatial_sim
}
pub fn to_wave(&self) -> MemoryWave {
let frequency = self.memory_type.frequency();
let amplitude = 0.5 + (self.access_count as f32 / 100.0).min(0.5);
let mut wave = MemoryWave::new(frequency, amplitude);
wave.valence = self.valence;
wave.arousal = self.arousal;
let hours = self.created_at.timestamp() as f32 / 3600.0;
wave.phase = (hours % (2.0 * std::f32::consts::PI)).abs();
wave
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
struct KeywordIndex {
keywords: HashMap<String, Vec<String>>,
}
impl KeywordIndex {
fn add(&mut self, keyword: &str, memory_id: &str) {
self.keywords
.entry(keyword.to_lowercase())
.or_default()
.push(memory_id.to_string());
}
fn find(&self, keywords: &[String]) -> Vec<String> {
let mut scores: HashMap<String, usize> = HashMap::new();
for keyword in keywords {
if let Some(ids) = self.keywords.get(&keyword.to_lowercase()) {
for id in ids {
*scores.entry(id.clone()).or_default() += 1;
}
}
}
let mut results: Vec<_> = scores.into_iter().collect();
results.sort_by(|a, b| b.1.cmp(&a.1));
results.into_iter().map(|(id, _)| id).collect()
}
}
pub struct WaveMemoryManager {
wave_grid: Arc<RwLock<WaveGrid>>,
memories: HashMap<String, AnchoredMemory>,
keyword_index: KeywordIndex,
storage_path: PathBuf,
dirty: bool,
}
impl WaveMemoryManager {
pub fn new(storage_dir: Option<&Path>) -> Self {
let storage_path = storage_dir
.map(|p| p.join(".st").join("mem8").join("wave_memory.m8"))
.unwrap_or_else(|| {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(".st")
.join("mem8")
.join("wave_memory.m8")
});
let mut manager = Self {
wave_grid: Arc::new(RwLock::new(WaveGrid::new())),
memories: HashMap::new(),
keyword_index: KeywordIndex::default(),
storage_path,
dirty: false,
};
if let Err(e) = manager.load() {
eprintln!("Note: Starting fresh wave memory ({})", e);
}
manager
}
pub fn new_compact(storage_dir: Option<&Path>) -> Self {
let storage_path = storage_dir
.map(|p| p.join(".st").join("mem8").join("wave_memory.m8"))
.unwrap_or_else(|| {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".mem8")
.join("wave_memory.m8")
});
let mut manager = Self {
wave_grid: Arc::new(RwLock::new(WaveGrid::new_compact())),
memories: HashMap::new(),
keyword_index: KeywordIndex::default(),
storage_path,
dirty: false,
};
if let Err(e) = manager.load() {
eprintln!("Note: Starting fresh wave memory ({})", e);
}
manager
}
#[cfg(test)]
pub fn new_test(storage_dir: Option<&Path>) -> Self {
let storage_path = storage_dir
.map(|p| p.join(".st").join("mem8").join("wave_memory_test.m8"))
.unwrap_or_else(|| {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(".st")
.join("mem8")
.join("wave_memory_test.m8")
});
let mut manager = Self {
wave_grid: Arc::new(RwLock::new(WaveGrid::new_test())),
memories: HashMap::new(),
keyword_index: KeywordIndex::default(),
storage_path,
dirty: false,
};
let _ = manager.load();
manager
}
#[allow(clippy::too_many_arguments)] pub fn anchor(
&mut self,
content: String,
keywords: Vec<String>,
memory_type: MemoryType,
valence: f32,
arousal: f32,
origin: String,
project_path: Option<PathBuf>,
) -> Result<String> {
let id = uuid::Uuid::new_v4().to_string();
let (x, y, z) = AnchoredMemory::calculate_coordinates(&content, &keywords, memory_type);
let memory = AnchoredMemory {
id: id.clone(),
content,
keywords: keywords.clone(),
memory_type,
valence: valence.clamp(-1.0, 1.0),
arousal: arousal.clamp(0.0, 1.0),
x,
y,
z,
created_at: Utc::now(),
last_accessed: Utc::now(),
access_count: 1,
origin,
project_path,
};
let wave = memory.to_wave();
if let Ok(mut grid) = self.wave_grid.write() {
grid.store(x, y, z, wave);
}
for keyword in &keywords {
self.keyword_index.add(keyword, &id);
}
self.memories.insert(id.clone(), memory);
self.dirty = true;
Ok(id)
}
pub fn find_by_keywords(
&mut self,
keywords: &[String],
max_results: usize,
) -> Vec<AnchoredMemory> {
let ids = self.keyword_index.find(keywords);
let found_ids: Vec<String> = ids.iter().take(max_results).cloned().collect();
let results: Vec<AnchoredMemory> = found_ids
.iter()
.filter_map(|id| self.memories.get(id).cloned())
.collect();
for id in &found_ids {
if let Some(mem) = self.memories.get_mut(id) {
mem.access_count += 1;
mem.last_accessed = Utc::now();
self.dirty = true;
}
}
results
}
pub fn find_by_resonance(
&mut self,
query_content: &str,
query_keywords: &[String],
query_type: MemoryType,
threshold: f32,
max_results: usize,
) -> Vec<(AnchoredMemory, f32)> {
let (x, y, z) =
AnchoredMemory::calculate_coordinates(query_content, query_keywords, query_type);
let query = AnchoredMemory {
id: String::new(),
content: query_content.to_string(),
keywords: query_keywords.to_vec(),
memory_type: query_type,
valence: 0.0,
arousal: 0.5,
x,
y,
z,
created_at: Utc::now(),
last_accessed: Utc::now(),
access_count: 0,
origin: String::new(),
project_path: None,
};
let mut resonances: Vec<(String, AnchoredMemory, f32)> = self
.memories
.values()
.map(|mem| (mem.id.clone(), mem.clone(), mem.resonance_with(&query)))
.filter(|(_, _, r)| *r >= threshold)
.collect();
resonances.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
let update_ids: Vec<String> = resonances
.iter()
.take(max_results)
.map(|(id, _, _)| id.clone())
.collect();
for id in &update_ids {
if let Some(m) = self.memories.get_mut(id) {
m.access_count += 1;
m.last_accessed = Utc::now();
self.dirty = true;
}
}
resonances
.into_iter()
.take(max_results)
.map(|(_, mem, r)| (mem, r))
.collect()
}
pub fn get_interference(&self, x: u8, y: u8, z: u16, t: f32) -> f32 {
if let Ok(grid) = self.wave_grid.read() {
grid.calculate_interference(x, y, z, t)
} else {
0.0
}
}
pub fn stats(&self) -> serde_json::Value {
let type_counts: HashMap<String, usize> =
self.memories.values().fold(HashMap::new(), |mut acc, mem| {
*acc.entry(format!("{:?}", mem.memory_type)).or_default() += 1;
acc
});
let active_count = if let Ok(grid) = self.wave_grid.read() {
grid.active_memory_count()
} else {
0
};
serde_json::json!({
"total_memories": self.memories.len(),
"active_waves": active_count,
"unique_keywords": self.keyword_index.keywords.len(),
"by_type": type_counts,
"storage_path": self.storage_path.display().to_string(),
})
}
pub fn save(&mut self) -> Result<()> {
if !self.dirty {
return Ok(());
}
if let Some(parent) = self.storage_path.parent() {
fs::create_dir_all(parent).context("Failed to create memory directory")?;
}
let data = serde_json::json!({
"version": 1,
"memories": self.memories,
"keyword_index": self.keyword_index,
});
let json = serde_json::to_string_pretty(&data).context("Failed to serialize memories")?;
fs::write(&self.storage_path, json).context("Failed to write memory file")?;
self.dirty = false;
eprintln!(
"💾 Saved {} memories to {}",
self.memories.len(),
self.storage_path.display()
);
Ok(())
}
pub fn load(&mut self) -> Result<()> {
if !self.storage_path.exists() {
return Err(anyhow::anyhow!("No memory file found"));
}
let json = fs::read_to_string(&self.storage_path).context("Failed to read memory file")?;
let data: serde_json::Value =
serde_json::from_str(&json).context("Failed to parse memory file")?;
if let Some(memories) = data.get("memories") {
self.memories = serde_json::from_value(memories.clone())
.context("Failed to deserialize memories")?;
}
if let Some(index) = data.get("keyword_index") {
self.keyword_index = serde_json::from_value(index.clone())
.context("Failed to deserialize keyword index")?;
}
if let Ok(mut grid) = self.wave_grid.write() {
for memory in self.memories.values() {
let wave = memory.to_wave();
grid.store(memory.x, memory.y, memory.z, wave);
}
}
eprintln!(
"🧠Loaded {} memories from {}",
self.memories.len(),
self.storage_path.display()
);
Ok(())
}
pub fn get(&self, id: &str) -> Option<&AnchoredMemory> {
self.memories.get(id)
}
pub fn delete(&mut self, id: &str) -> bool {
if let Some(memory) = self.memories.remove(id) {
for keyword in &memory.keywords {
if let Some(ids) = self.keyword_index.keywords.get_mut(&keyword.to_lowercase()) {
ids.retain(|i| i != id);
}
}
self.dirty = true;
true
} else {
false
}
}
}
impl Drop for WaveMemoryManager {
fn drop(&mut self) {
let _ = self.save();
}
}
static WAVE_MEMORY: std::sync::OnceLock<std::sync::Mutex<WaveMemoryManager>> =
std::sync::OnceLock::new();
pub fn get_wave_memory() -> &'static std::sync::Mutex<WaveMemoryManager> {
WAVE_MEMORY.get_or_init(|| std::sync::Mutex::new(WaveMemoryManager::new(None)))
}
pub fn init_wave_memory(storage_dir: &Path) {
let _ = WAVE_MEMORY.set(std::sync::Mutex::new(WaveMemoryManager::new(Some(
storage_dir,
))));
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_anchor_and_find() {
let dir = tempdir().unwrap();
let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
let id = manager
.anchor(
"The solution to the authentication bug was using JWT refresh tokens".to_string(),
vec!["auth".to_string(), "jwt".to_string(), "bug".to_string()],
MemoryType::Solution,
0.8, 0.7, "tandem:hue:claude".to_string(),
None,
)
.unwrap();
assert!(!id.is_empty());
let results = manager.find_by_keywords(&["auth".to_string(), "jwt".to_string()], 10);
assert_eq!(results.len(), 1);
assert!(results[0].content.contains("JWT refresh tokens"));
}
#[test]
fn test_resonance_search() {
let dir = tempdir().unwrap();
let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
manager
.anchor(
"Rust async/await pattern for error handling".to_string(),
vec!["rust".to_string(), "async".to_string(), "error".to_string()],
MemoryType::Technical,
0.3,
0.5,
"tandem:hue:claude".to_string(),
None,
)
.unwrap();
manager
.anchor(
"Go channels for concurrent error propagation".to_string(),
vec![
"go".to_string(),
"channels".to_string(),
"error".to_string(),
],
MemoryType::Technical,
0.2,
0.4,
"tandem:hue:claude".to_string(),
None,
)
.unwrap();
let results = manager.find_by_resonance(
"error handling in async code",
&["async".to_string(), "error".to_string()],
MemoryType::Technical,
0.3, 10,
);
assert!(!results.is_empty());
assert!(results[0].0.content.contains("Rust") || results[0].0.content.contains("error"));
}
#[test]
fn test_persistence() {
let dir = tempdir().unwrap();
{
let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
manager
.anchor(
"Aye loves Elvis!".to_string(),
vec!["aye".to_string(), "elvis".to_string()],
MemoryType::Joke,
1.0,
1.0,
"tandem:hue:claude".to_string(),
None,
)
.unwrap();
manager.save().unwrap();
}
{
let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
let results = manager.find_by_keywords(&["elvis".to_string()], 10);
assert_eq!(results.len(), 1);
assert!(results[0].content.contains("Elvis"));
}
}
#[test]
fn test_memory_types_to_frequencies() {
assert!(MemoryType::Pattern.frequency() < MemoryType::Solution.frequency());
assert!(MemoryType::Solution.frequency() < MemoryType::Technical.frequency());
assert!(MemoryType::Technical.frequency() < MemoryType::Joke.frequency());
}
}