use anyhow::Context;
use do_memory_core::{Error, MemoryConfig, SelfLearningMemory};
use do_memory_storage_redb::{CacheConfig, RedbStorage};
use do_memory_storage_turso::{TursoConfig, TursoStorage};
use std::path::Path;
use std::sync::Arc;
use tracing::{info, warn};
pub async fn initialize_memory_system() -> anyhow::Result<Arc<SelfLearningMemory>> {
if let Ok(mode) = std::env::var("MEMORY_STORAGE_MODE") {
match mode.to_lowercase().as_str() {
"local" => {
if let Ok(memory) = initialize_turso_local().await {
info!("Memory system initialized with Turso local database (explicit)");
return Ok(memory);
}
}
"memory" => {
info!("Memory system initialized with in-memory storage (explicit)");
let storage = do_memory_storage_turso::TursoStorage::new_in_memory().await?;
storage.initialize_schema().await?;
let cache = Arc::new(
do_memory_storage_redb::RedbStorage::new(std::path::Path::new(":memory:"))
.await?,
);
let memory = SelfLearningMemory::with_storage(
MemoryConfig::default(),
Arc::new(storage),
cache,
);
return Ok(Arc::new(memory));
}
"remote" => {
if let Ok(memory) = initialize_dual_storage().await {
info!("Memory system initialized with remote Turso (explicit)");
return Ok(memory);
}
}
_ => warn!(
"Unknown MEMORY_STORAGE_MODE: {}, following default order",
mode
),
}
}
if let Ok(memory) = initialize_turso_local().await {
info!("Memory system initialized with Turso local database (default)");
return Ok(memory);
}
if let Ok(memory) = initialize_dual_storage().await {
info!("Memory system initialized with persistent storage (Turso cloud + redb)");
return Ok(memory);
}
if let Ok(memory) = initialize_redb_only_storage().await {
info!("Memory system initialized with redb cache storage (Turso unavailable)");
return Ok(memory);
}
warn!("Failed to initialize any persistent storage, falling back to in-memory");
info!("To enable persistence:");
info!(" - Default: Turso local database (no configuration needed)");
info!(" - Cloud: set TURSO_DATABASE_URL and TURSO_AUTH_TOKEN");
info!(" - Cache-only: ensure REDB_CACHE_PATH is accessible");
Ok(Arc::new(SelfLearningMemory::new()))
}
pub async fn initialize_redb_only_storage() -> anyhow::Result<Arc<SelfLearningMemory>> {
info!("Attempting to initialize redb-only storage...");
let cache_path_str =
std::env::var("REDB_CACHE_PATH").unwrap_or_else(|_| "./data/cache.redb".to_string());
let cache_path = Path::new(&cache_path_str);
if let Some(parent) = cache_path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| Error::Storage(format!("Failed to create data directory: {e}")))?;
}
let cache_config = CacheConfig {
max_size: std::env::var("REDB_MAX_CACHE_SIZE")
.unwrap_or_else(|_| "1000".to_string())
.parse()
.unwrap_or(1000),
default_ttl_secs: 1800, cleanup_interval_secs: 600, enable_background_cleanup: true,
};
let redb_storage = RedbStorage::new_with_cache_config(cache_path, cache_config).await?;
info!(
"Successfully initialized redb storage at {}",
cache_path.display()
);
let memory_config = MemoryConfig::default();
let redb_arc: Arc<dyn do_memory_core::StorageBackend> = Arc::new(redb_storage);
let memory = SelfLearningMemory::with_storage(memory_config, Arc::clone(&redb_arc), redb_arc);
Ok(Arc::new(memory))
}
pub async fn initialize_dual_storage() -> anyhow::Result<Arc<SelfLearningMemory>> {
let turso_url = std::env::var("TURSO_DATABASE_URL")
.context("TURSO_DATABASE_URL environment variable not set")?;
let turso_token = std::env::var("TURSO_AUTH_TOKEN")
.context("TURSO_AUTH_TOKEN environment variable not set")?;
info!("Connecting to Turso database at {}", turso_url);
let turso_config = TursoConfig {
max_retries: 3,
retry_base_delay_ms: 100,
retry_max_delay_ms: 5000,
enable_pooling: true,
compression_threshold: 1024,
compress_episodes: true,
compress_patterns: true,
compress_embeddings: true,
..Default::default()
};
let turso_storage = TursoStorage::with_config(&turso_url, &turso_token, turso_config).await?;
turso_storage.initialize_schema().await?;
let cache_path_str =
std::env::var("REDB_CACHE_PATH").unwrap_or_else(|_| "./data/cache.redb".to_string());
let cache_path = Path::new(&cache_path_str);
if let Some(parent) = cache_path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| Error::Storage(format!("Failed to create cache directory: {e}")))?;
}
let cache_config = CacheConfig {
max_size: std::env::var("REDB_MAX_CACHE_SIZE")
.unwrap_or_else(|_| "1000".to_string())
.parse()
.unwrap_or(1000),
default_ttl_secs: 1800, cleanup_interval_secs: 600, enable_background_cleanup: true,
};
let redb_storage = RedbStorage::new_with_cache_config(cache_path, cache_config).await?;
let memory_config = MemoryConfig::default();
let memory = SelfLearningMemory::with_storage(
memory_config,
Arc::new(turso_storage),
Arc::new(redb_storage),
);
Ok(Arc::new(memory))
}
pub async fn initialize_turso_local() -> anyhow::Result<Arc<SelfLearningMemory>> {
info!("Attempting to initialize Turso local database (default)...");
let db_path: std::path::PathBuf = if let Ok(p) = std::env::var("MEMORY_DB_PATH") {
std::path::PathBuf::from(strip_file_prefix(&p))
} else if let Ok(url) = std::env::var("TURSO_DATABASE_URL") {
if url.starts_with("file:") || url.starts_with("libsql://") {
if url.starts_with("file:") {
std::path::PathBuf::from(strip_file_prefix(&url))
} else {
do_memory_core::memory::default_db_path()
}
} else {
std::path::PathBuf::from(url)
}
} else {
do_memory_core::memory::default_db_path()
};
info!(
"Connecting to local Turso database at {}",
db_path.display()
);
let turso_storage = TursoStorage::new_local(&db_path).await?;
turso_storage.initialize_schema().await?;
let cache_path_str =
std::env::var("REDB_CACHE_PATH").unwrap_or_else(|_| "./data/cache.redb".to_string());
let cache_path = Path::new(&cache_path_str);
if let Some(parent) = cache_path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| Error::Storage(format!("Failed to create cache directory: {e}")))?;
}
let cache_config = CacheConfig {
max_size: std::env::var("REDB_MAX_CACHE_SIZE")
.unwrap_or_else(|_| "1000".to_string())
.parse()
.unwrap_or(1000),
default_ttl_secs: 1800, cleanup_interval_secs: 600, enable_background_cleanup: true,
};
let redb_storage = RedbStorage::new_with_cache_config(cache_path, cache_config).await?;
let memory_config = MemoryConfig::default();
let memory = SelfLearningMemory::with_storage(
memory_config,
Arc::new(turso_storage),
Arc::new(redb_storage),
);
info!("Successfully initialized Turso local + redb cache storage");
Ok(Arc::new(memory))
}
fn strip_file_prefix(s: &str) -> &str {
s.strip_prefix("file:").unwrap_or(s)
}
#[cfg(test)]
#[allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
mod tests {
use super::*;
use tokio::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::const_new(());
#[tokio::test]
#[ignore = "upstream libsql memory corruption SIGSEGV (ADR-027)"]
async fn test_initialize_turso_local_succeeds() {
let _lock = TEST_LOCK.lock().await;
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("test.db");
unsafe {
std::env::set_var("MEMORY_DB_PATH", db_path.to_str().unwrap());
std::env::set_var(
"REDB_CACHE_PATH",
dir.path().join("cache.redb").to_str().unwrap(),
);
}
let result = initialize_turso_local().await;
unsafe {
std::env::remove_var("MEMORY_DB_PATH");
std::env::remove_var("REDB_CACHE_PATH");
}
assert!(result.is_ok());
}
#[tokio::test]
async fn test_initialize_redb_only_storage_succeeds() {
let _lock = TEST_LOCK.lock().await;
let dir = tempfile::tempdir().unwrap();
unsafe {
std::env::set_var(
"REDB_CACHE_PATH",
dir.path().join("cache.redb").to_str().unwrap(),
);
}
let result = initialize_redb_only_storage().await;
unsafe {
std::env::remove_var("REDB_CACHE_PATH");
}
assert!(result.is_ok());
}
#[tokio::test]
async fn test_initialize_memory_system_in_memory_mode() {
let _lock = TEST_LOCK.lock().await;
let dir = tempfile::tempdir().unwrap();
unsafe {
std::env::set_var("MEMORY_STORAGE_MODE", "memory");
std::env::set_var(
"REDB_CACHE_PATH",
dir.path().join("cache.redb").to_str().unwrap(),
);
}
let result = initialize_memory_system().await;
unsafe {
std::env::remove_var("MEMORY_STORAGE_MODE");
std::env::remove_var("REDB_CACHE_PATH");
}
assert!(result.is_ok());
}
#[tokio::test]
async fn test_initialize_memory_system_unknown_mode_falls_back() {
let _lock = TEST_LOCK.lock().await;
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("test.db");
unsafe {
std::env::set_var("MEMORY_STORAGE_MODE", "unknown_xyz");
std::env::set_var("MEMORY_DB_PATH", db_path.to_str().unwrap());
std::env::set_var(
"REDB_CACHE_PATH",
dir.path().join("cache.redb").to_str().unwrap(),
);
}
let result = initialize_memory_system().await;
unsafe {
std::env::remove_var("MEMORY_STORAGE_MODE");
std::env::remove_var("MEMORY_DB_PATH");
std::env::remove_var("REDB_CACHE_PATH");
}
assert!(result.is_ok());
}
}