use crate::memory_core::semantic_consolidation::SemanticConsolidationConfig;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct DreamConfig {
pub idle_secs: u64,
pub dedup_threshold: f32,
pub prune_importance: f32,
pub max_cycle_ms: u64,
pub content_prune_enabled: bool,
pub content_prune_min_words: usize,
pub semantic: SemanticConsolidationConfig,
pub openrouter_api_key: String,
pub local_model_enabled: bool,
pub recall_benchmark_enabled: bool,
}
impl Default for DreamConfig {
fn default() -> Self {
Self {
idle_secs: 300,
dedup_threshold: 0.95,
prune_importance: 0.05,
max_cycle_ms: 60_000,
content_prune_enabled: true,
content_prune_min_words: 4,
semantic: SemanticConsolidationConfig::default(),
openrouter_api_key: String::new(),
local_model_enabled: true,
recall_benchmark_enabled: true,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct DreamStats {
pub merged: usize,
pub pruned: usize,
pub closets_updated: usize,
pub compacted: usize,
#[serde(default)]
pub content_pruned: usize,
#[serde(default)]
pub semantically_consolidated: usize,
#[serde(default)]
pub semantic_llm_calls: usize,
#[serde(default)]
pub semantic_cache_hits: usize,
pub duration_ms: u64,
#[serde(default)]
pub drawers_before: u64,
#[serde(default)]
pub drawers_after: u64,
#[serde(default)]
pub compression_ratio: f64,
#[serde(default)]
pub recall_score_before: Option<f64>,
#[serde(default)]
pub recall_score_after: Option<f64>,
}
impl DreamStats {
pub fn update_compression_ratio(&mut self) {
self.compression_ratio = if self.drawers_before == 0 {
0.0
} else {
if self.drawers_after > self.drawers_before {
tracing::warn!(
drawers_before = self.drawers_before,
drawers_after = self.drawers_after,
"dream cycle: net palace growth detected (more drawers after than before); \
compression_ratio clamped to 0.0"
);
}
let eliminated = self.drawers_before.saturating_sub(self.drawers_after);
eliminated as f64 / self.drawers_before as f64
};
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedDreamStats {
pub last_run_at: chrono::DateTime<chrono::Utc>,
#[serde(flatten)]
pub stats: DreamStats,
}
impl PersistedDreamStats {
pub const FILE_NAME: &'static str = "dream_stats.json";
pub fn load(data_dir: &Path) -> Result<Option<Self>> {
let path = data_dir.join(Self::FILE_NAME);
if !path.exists() {
return Ok(None);
}
let raw =
std::fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?;
let parsed: Self =
serde_json::from_str(&raw).with_context(|| format!("parse {}", path.display()))?;
Ok(Some(parsed))
}
pub fn save(&self, data_dir: &Path) -> Result<()> {
let path = data_dir.join(Self::FILE_NAME);
let raw = serde_json::to_string_pretty(self).context("serialize dream stats")?;
std::fs::write(&path, raw).with_context(|| format!("write {}", path.display()))?;
Ok(())
}
}