lean_ctx/core/
memory_policy.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, Default)]
4#[serde(default)]
5pub struct MemoryPolicy {
6 pub knowledge: KnowledgePolicy,
7 pub episodic: EpisodicPolicy,
8 pub procedural: ProceduralPolicy,
9 pub lifecycle: LifecyclePolicy,
10 pub embeddings: EmbeddingsPolicy,
11}
12
13impl MemoryPolicy {
14 pub fn apply_env_overrides(&mut self) {
15 self.knowledge.apply_env_overrides();
16 self.episodic.apply_env_overrides();
17 self.procedural.apply_env_overrides();
18 self.lifecycle.apply_env_overrides();
19 self.embeddings.apply_env_overrides();
20 }
21
22 pub fn validate(&self) -> Result<(), String> {
23 self.knowledge.validate()?;
24 self.episodic.validate()?;
25 self.procedural.validate()?;
26 self.lifecycle.validate()?;
27 self.embeddings.validate()?;
28 Ok(())
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(default)]
34pub struct KnowledgePolicy {
35 pub max_facts: usize,
36 pub max_patterns: usize,
37 pub max_history: usize,
38 pub contradiction_threshold: f32,
39}
40
41impl Default for KnowledgePolicy {
42 fn default() -> Self {
43 Self {
44 max_facts: 200,
45 max_patterns: 50,
46 max_history: 100,
47 contradiction_threshold: 0.5,
48 }
49 }
50}
51
52impl KnowledgePolicy {
53 fn apply_env_overrides(&mut self) {
54 if let Ok(v) = std::env::var("LEAN_CTX_KNOWLEDGE_MAX_FACTS") {
55 if let Ok(n) = v.parse() {
56 self.max_facts = n;
57 }
58 }
59 if let Ok(v) = std::env::var("LEAN_CTX_KNOWLEDGE_MAX_PATTERNS") {
60 if let Ok(n) = v.parse() {
61 self.max_patterns = n;
62 }
63 }
64 if let Ok(v) = std::env::var("LEAN_CTX_KNOWLEDGE_MAX_HISTORY") {
65 if let Ok(n) = v.parse() {
66 self.max_history = n;
67 }
68 }
69 if let Ok(v) = std::env::var("LEAN_CTX_KNOWLEDGE_CONTRADICTION_THRESHOLD") {
70 if let Ok(n) = v.parse() {
71 self.contradiction_threshold = n;
72 }
73 }
74 }
75
76 fn validate(&self) -> Result<(), String> {
77 if self.max_facts == 0 {
78 return Err("memory.knowledge.max_facts must be > 0".to_string());
79 }
80 if self.max_patterns == 0 {
81 return Err("memory.knowledge.max_patterns must be > 0".to_string());
82 }
83 if self.max_history == 0 {
84 return Err("memory.knowledge.max_history must be > 0".to_string());
85 }
86 if !(0.0..=1.0).contains(&self.contradiction_threshold) {
87 return Err(
88 "memory.knowledge.contradiction_threshold must be in [0.0, 1.0]".to_string(),
89 );
90 }
91 Ok(())
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(default)]
97pub struct EpisodicPolicy {
98 pub max_episodes: usize,
99 pub max_actions_per_episode: usize,
100 pub summary_max_chars: usize,
101}
102
103impl Default for EpisodicPolicy {
104 fn default() -> Self {
105 Self {
106 max_episodes: 500,
107 max_actions_per_episode: 50,
108 summary_max_chars: 200,
109 }
110 }
111}
112
113impl EpisodicPolicy {
114 fn apply_env_overrides(&mut self) {
115 if let Ok(v) = std::env::var("LEAN_CTX_EPISODIC_MAX_EPISODES") {
116 if let Ok(n) = v.parse() {
117 self.max_episodes = n;
118 }
119 }
120 if let Ok(v) = std::env::var("LEAN_CTX_EPISODIC_MAX_ACTIONS_PER_EPISODE") {
121 if let Ok(n) = v.parse() {
122 self.max_actions_per_episode = n;
123 }
124 }
125 if let Ok(v) = std::env::var("LEAN_CTX_EPISODIC_SUMMARY_MAX_CHARS") {
126 if let Ok(n) = v.parse() {
127 self.summary_max_chars = n;
128 }
129 }
130 }
131
132 fn validate(&self) -> Result<(), String> {
133 if self.max_episodes == 0 {
134 return Err("memory.episodic.max_episodes must be > 0".to_string());
135 }
136 if self.max_actions_per_episode == 0 {
137 return Err("memory.episodic.max_actions_per_episode must be > 0".to_string());
138 }
139 if self.summary_max_chars < 40 {
140 return Err("memory.episodic.summary_max_chars must be >= 40".to_string());
141 }
142 Ok(())
143 }
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147#[serde(default)]
148pub struct ProceduralPolicy {
149 pub min_repetitions: usize,
150 pub min_sequence_len: usize,
151 pub max_procedures: usize,
152 pub max_window_size: usize,
153}
154
155impl Default for ProceduralPolicy {
156 fn default() -> Self {
157 Self {
158 min_repetitions: 3,
159 min_sequence_len: 2,
160 max_procedures: 100,
161 max_window_size: 10,
162 }
163 }
164}
165
166impl ProceduralPolicy {
167 fn apply_env_overrides(&mut self) {
168 if let Ok(v) = std::env::var("LEAN_CTX_PROCEDURAL_MIN_REPETITIONS") {
169 if let Ok(n) = v.parse() {
170 self.min_repetitions = n;
171 }
172 }
173 if let Ok(v) = std::env::var("LEAN_CTX_PROCEDURAL_MIN_SEQUENCE_LEN") {
174 if let Ok(n) = v.parse() {
175 self.min_sequence_len = n;
176 }
177 }
178 if let Ok(v) = std::env::var("LEAN_CTX_PROCEDURAL_MAX_PROCEDURES") {
179 if let Ok(n) = v.parse() {
180 self.max_procedures = n;
181 }
182 }
183 if let Ok(v) = std::env::var("LEAN_CTX_PROCEDURAL_MAX_WINDOW_SIZE") {
184 if let Ok(n) = v.parse() {
185 self.max_window_size = n;
186 }
187 }
188 }
189
190 fn validate(&self) -> Result<(), String> {
191 if self.min_repetitions == 0 {
192 return Err("memory.procedural.min_repetitions must be > 0".to_string());
193 }
194 if self.min_sequence_len < 2 {
195 return Err("memory.procedural.min_sequence_len must be >= 2".to_string());
196 }
197 if self.max_procedures == 0 {
198 return Err("memory.procedural.max_procedures must be > 0".to_string());
199 }
200 if self.max_window_size < self.min_sequence_len {
201 return Err(
202 "memory.procedural.max_window_size must be >= min_sequence_len".to_string(),
203 );
204 }
205 Ok(())
206 }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(default)]
211pub struct LifecyclePolicy {
212 pub decay_rate: f32,
213 pub low_confidence_threshold: f32,
214 pub stale_days: i64,
215 pub similarity_threshold: f32,
216}
217
218impl Default for LifecyclePolicy {
219 fn default() -> Self {
220 Self {
221 decay_rate: 0.01,
222 low_confidence_threshold: 0.3,
223 stale_days: 30,
224 similarity_threshold: 0.85,
225 }
226 }
227}
228
229impl LifecyclePolicy {
230 fn apply_env_overrides(&mut self) {
231 if let Ok(v) = std::env::var("LEAN_CTX_LIFECYCLE_DECAY_RATE") {
232 if let Ok(n) = v.parse() {
233 self.decay_rate = n;
234 }
235 }
236 if let Ok(v) = std::env::var("LEAN_CTX_LIFECYCLE_LOW_CONFIDENCE_THRESHOLD") {
237 if let Ok(n) = v.parse() {
238 self.low_confidence_threshold = n;
239 }
240 }
241 if let Ok(v) = std::env::var("LEAN_CTX_LIFECYCLE_STALE_DAYS") {
242 if let Ok(n) = v.parse() {
243 self.stale_days = n;
244 }
245 }
246 if let Ok(v) = std::env::var("LEAN_CTX_LIFECYCLE_SIMILARITY_THRESHOLD") {
247 if let Ok(n) = v.parse() {
248 self.similarity_threshold = n;
249 }
250 }
251 }
252
253 fn validate(&self) -> Result<(), String> {
254 if !(0.0..=1.0).contains(&self.decay_rate) {
255 return Err("memory.lifecycle.decay_rate must be in [0.0, 1.0]".to_string());
256 }
257 if !(0.0..=1.0).contains(&self.low_confidence_threshold) {
258 return Err(
259 "memory.lifecycle.low_confidence_threshold must be in [0.0, 1.0]".to_string(),
260 );
261 }
262 if self.stale_days < 0 {
263 return Err("memory.lifecycle.stale_days must be >= 0".to_string());
264 }
265 if !(0.0..=1.0).contains(&self.similarity_threshold) {
266 return Err("memory.lifecycle.similarity_threshold must be in [0.0, 1.0]".to_string());
267 }
268 Ok(())
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
273#[serde(default)]
274pub struct EmbeddingsPolicy {
275 pub max_facts: usize,
276}
277
278impl Default for EmbeddingsPolicy {
279 fn default() -> Self {
280 Self { max_facts: 2000 }
281 }
282}
283
284impl EmbeddingsPolicy {
285 fn apply_env_overrides(&mut self) {
286 if let Ok(v) = std::env::var("LEAN_CTX_KNOWLEDGE_EMBEDDINGS_MAX_FACTS") {
287 if let Ok(n) = v.parse() {
288 self.max_facts = n;
289 }
290 }
291 }
292
293 fn validate(&self) -> Result<(), String> {
294 if self.max_facts == 0 {
295 return Err("memory.embeddings.max_facts must be > 0".to_string());
296 }
297 Ok(())
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn restore_env(key: &str, prev: Option<String>) {
306 match prev {
307 Some(v) => std::env::set_var(key, v),
308 None => std::env::remove_var(key),
309 }
310 }
311
312 #[test]
313 fn default_policy_is_valid() {
314 let p = MemoryPolicy::default();
315 p.validate().expect("default policy must be valid");
316 }
317
318 #[test]
319 fn env_overrides_apply() {
320 let _lock = crate::core::data_dir::test_env_lock();
321
322 let prev_facts = std::env::var("LEAN_CTX_KNOWLEDGE_MAX_FACTS").ok();
323 let prev_stale = std::env::var("LEAN_CTX_LIFECYCLE_STALE_DAYS").ok();
324 let prev_rep = std::env::var("LEAN_CTX_PROCEDURAL_MIN_REPETITIONS").ok();
325
326 std::env::set_var("LEAN_CTX_KNOWLEDGE_MAX_FACTS", "123");
327 std::env::set_var("LEAN_CTX_LIFECYCLE_STALE_DAYS", "7");
328 std::env::set_var("LEAN_CTX_PROCEDURAL_MIN_REPETITIONS", "4");
329
330 let mut p = MemoryPolicy::default();
331 p.apply_env_overrides();
332
333 assert_eq!(p.knowledge.max_facts, 123);
334 assert_eq!(p.lifecycle.stale_days, 7);
335 assert_eq!(p.procedural.min_repetitions, 4);
336
337 restore_env("LEAN_CTX_KNOWLEDGE_MAX_FACTS", prev_facts);
338 restore_env("LEAN_CTX_LIFECYCLE_STALE_DAYS", prev_stale);
339 restore_env("LEAN_CTX_PROCEDURAL_MIN_REPETITIONS", prev_rep);
340 }
341
342 #[test]
343 fn validate_rejects_invalid_values() {
344 let mut p = MemoryPolicy::default();
345 p.knowledge.max_facts = 0;
346 assert!(p.validate().is_err());
347
348 let mut p = MemoryPolicy::default();
349 p.lifecycle.decay_rate = 2.0;
350 assert!(p.validate().is_err());
351
352 let mut p = MemoryPolicy::default();
353 p.procedural.min_sequence_len = 1;
354 assert!(p.validate().is_err());
355 }
356}