1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Configuration for the memory consolidation ("sleep") system.
use serde::{Deserialize, Serialize};
/// Configuration for memory consolidation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsolidationConfig {
/// How often to run consolidation in seconds (default: 3600 = hourly).
pub interval_secs: u64,
/// Maximum concurrent LLM summarization streams.
pub max_concurrent_llm: usize,
/// Maximum episodes to process per consolidation batch.
pub batch_size: usize,
/// Exponential decay half-life in hours (matches Episode::recency_score).
pub decay_half_life_hours: f64,
/// Minimum importance level to retain during consolidation.
pub min_importance: u8,
/// Similarity threshold for deduplication (0.0–1.0).
pub dedup_similarity: f32,
/// Maximum tokens per consolidated summary.
pub summary_max_tokens: usize,
/// LLM endpoint for summarization calls.
pub endpoint: String,
/// Model for summarization.
pub model: String,
/// Temperature for summarization (low for deterministic).
pub temperature: f32,
/// Timeout for each LLM call in seconds.
pub timeout_secs: u64,
/// Maximum age in hours — episodes older than this get consolidated first.
pub max_episode_age_hours: f64,
/// Whether to prune source episodes after consolidation.
pub prune_after_consolidation: bool,
}
impl Default for ConsolidationConfig {
fn default() -> Self {
Self {
interval_secs: 3600,
max_concurrent_llm: 32,
batch_size: 100,
decay_half_life_hours: 24.0,
min_importance: 1, // Importance::Low
dedup_similarity: 0.95,
summary_max_tokens: 1024,
endpoint: "http://127.0.0.1:1234/v1".into(),
model: "qwen3.5-27b".into(),
temperature: 0.1,
timeout_secs: 120,
max_episode_age_hours: 72.0,
prune_after_consolidation: false,
}
}
}
impl ConsolidationConfig {
/// Create a config with a specific endpoint and model.
pub fn new(endpoint: impl Into<String>, model: impl Into<String>) -> Self {
Self {
endpoint: endpoint.into(),
model: model.into(),
..Default::default()
}
}
/// Set concurrency for LLM calls.
pub fn with_concurrency(mut self, n: usize) -> Self {
self.max_concurrent_llm = n.max(1);
self
}
/// Validate configuration.
pub fn validate(&self) -> Result<(), String> {
if self.endpoint.is_empty() {
return Err("endpoint must not be empty".into());
}
if self.model.is_empty() {
return Err("model must not be empty".into());
}
if self.max_concurrent_llm == 0 {
return Err("max_concurrent_llm must be >= 1".into());
}
if self.batch_size == 0 {
return Err("batch_size must be >= 1".into());
}
if self.decay_half_life_hours <= 0.0 {
return Err("decay_half_life_hours must be positive".into());
}
Ok(())
}
}
#[cfg(test)]
#[path = "../../tests/unit/consolidation/config/config_test.rs"]
mod tests;