Skip to main content

microscope_memory/
implicit_memory.rs

1//! Implicit memory — procedural & habit learning without conscious recall
2//! Skills, patterns, and conditioned responses from repeated exposure
3
4use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[derive(Clone, Debug)]
9pub struct ImplicitPattern {
10    pub pattern: Vec<u32>, // Sequence of block hashes
11    pub strength: f32,     // 0.0-1.0, reinforcement level
12    pub frequency: u32,    // How many times seen
13    pub last_activation_ms: u64,
14    pub performance_metric: f32, // Success rate for procedural skills
15}
16
17#[derive(Clone, Debug)]
18pub struct SkillNode {
19    pub name: String,
20    pub mastery_level: f32, // 0.0-1.0
21    pub error_rate: f32,    // Recent error frequency
22    pub last_practiced_ms: u64,
23    pub practice_count: u32,
24}
25
26pub struct ImplicitMemory {
27    pub patterns: HashMap<u64, ImplicitPattern>, // Pattern hash -> pattern
28    pub skills: HashMap<String, SkillNode>,
29    pub habits: Vec<(String, f32)>, // (trigger, strength) pairs
30    pub conditioning: HashMap<String, f32>, // stimulus -> response strength
31}
32
33impl Default for ImplicitMemory {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl ImplicitMemory {
40    pub fn new() -> Self {
41        Self {
42            patterns: HashMap::new(),
43            skills: HashMap::new(),
44            habits: Vec::new(),
45            conditioning: HashMap::new(),
46        }
47    }
48
49    /// Learn a procedural pattern from repeated activation
50    pub fn learn_pattern(&mut self, pattern: Vec<u32>, success: bool) {
51        let hash = Self::hash_pattern(&pattern);
52        let strength_delta = if success { 0.1 } else { -0.05 };
53
54        self.patterns
55            .entry(hash)
56            .and_modify(|p| {
57                p.strength = (p.strength + strength_delta).clamp(0.0, 1.0);
58                p.frequency += 1;
59                p.performance_metric =
60                    p.performance_metric * 0.8 + if success { 1.0 } else { 0.0 } * 0.2;
61                p.last_activation_ms = Self::now_ms();
62            })
63            .or_insert_with(|| ImplicitPattern {
64                pattern: pattern.clone(),
65                strength: 0.5,
66                frequency: 1,
67                last_activation_ms: Self::now_ms(),
68                performance_metric: if success { 1.0 } else { 0.0 },
69            });
70    }
71
72    /// Develop or improve a skill
73    pub fn practice_skill(&mut self, skill: &str, error_occurred: bool) {
74        self.skills
75            .entry(skill.to_string())
76            .and_modify(|s| {
77                s.practice_count += 1;
78                let improvement = 0.02;
79                if !error_occurred {
80                    s.mastery_level = (s.mastery_level + improvement).min(1.0);
81                    s.error_rate = (s.error_rate * 0.95).max(0.0);
82                } else {
83                    s.error_rate = (s.error_rate + 0.05).min(1.0);
84                }
85                s.last_practiced_ms = Self::now_ms();
86            })
87            .or_insert_with(|| SkillNode {
88                name: skill.to_string(),
89                mastery_level: if !error_occurred { 0.2 } else { 0.05 },
90                error_rate: if error_occurred { 0.5 } else { 0.1 },
91                last_practiced_ms: Self::now_ms(),
92                practice_count: 1,
93            });
94    }
95
96    /// Form or reinforce a habit
97    pub fn form_habit(&mut self, trigger: &str, strength_delta: f32) {
98        let pos = self.habits.iter().position(|(t, _)| t == trigger);
99        if let Some(idx) = pos {
100            let new_strength = (self.habits[idx].1 + strength_delta).clamp(0.0, 1.0);
101            self.habits[idx].1 = new_strength;
102        } else {
103            self.habits
104                .push((trigger.to_string(), strength_delta.clamp(0.0, 1.0)));
105        }
106    }
107
108    /// Classical conditioning: stimulus -> response
109    pub fn condition_response(&mut self, stimulus: &str, response_strength: f32) {
110        self.conditioning
111            .entry(stimulus.to_string())
112            .and_modify(|s| *s = (*s * 0.7 + response_strength * 0.3).clamp(0.0, 1.0))
113            .or_insert(response_strength.clamp(0.0, 1.0));
114    }
115
116    /// Get strongest patterns
117    pub fn strongest_patterns(&self, k: usize) -> Vec<(u64, ImplicitPattern)> {
118        let mut sorted: Vec<_> = self.patterns.iter().map(|(h, p)| (*h, p.clone())).collect();
119        sorted.sort_by(|a, b| b.1.strength.total_cmp(&a.1.strength));
120        sorted.into_iter().take(k).collect()
121    }
122
123    /// Get most practiced skills
124    pub fn skill_ranking(&self) -> Vec<(String, SkillNode)> {
125        let mut sorted: Vec<_> = self
126            .skills
127            .iter()
128            .map(|(n, s)| (n.clone(), s.clone()))
129            .collect();
130        sorted.sort_by(|a, b| b.1.mastery_level.total_cmp(&a.1.mastery_level));
131        sorted
132    }
133
134    /// Decay: periodic forgetting of weak patterns/skills
135    pub fn decay(&mut self) {
136        let now = Self::now_ms();
137        const DECAY_THRESHOLD_MS: u64 = 604_800_000; // 7 days
138
139        // Decay patterns
140        self.patterns.retain(|_, p| {
141            let age = now.saturating_sub(p.last_activation_ms);
142            if age > DECAY_THRESHOLD_MS {
143                p.strength *= 0.5; // Decay strength
144            }
145            p.strength > 0.05 // Remove near-forgotten patterns
146        });
147
148        // Decay skills
149        self.skills.retain(|_, s| {
150            let age = now.saturating_sub(s.last_practiced_ms);
151            if age > DECAY_THRESHOLD_MS {
152                s.mastery_level *= 0.7;
153            }
154            s.mastery_level > 0.05
155        });
156
157        // Decay habits
158        self.habits.retain(|(_, strength)| *strength > 0.05);
159    }
160
161    fn hash_pattern(pattern: &[u32]) -> u64 {
162        let mut hash: u64 = 0xcbf29ce484222325;
163        for &val in pattern {
164            hash = hash.wrapping_mul(0x100000001b3) ^ (val as u64);
165        }
166        hash
167    }
168
169    fn now_ms() -> u64 {
170        std::time::SystemTime::now()
171            .duration_since(std::time::UNIX_EPOCH)
172            .unwrap_or_default()
173            .as_millis() as u64
174    }
175
176    pub fn save(&self, dir: &Path) -> Result<(), String> {
177        let path = dir.join("implicit_memory.bin");
178        let mut data = Vec::new();
179
180        // Magic + version
181        data.extend_from_slice(b"IMPL");
182        data.push(1);
183
184        // Patterns
185        let pattern_count = self.patterns.len() as u32;
186        data.extend_from_slice(&pattern_count.to_le_bytes());
187        for (hash, pattern) in &self.patterns {
188            data.extend_from_slice(&hash.to_le_bytes());
189            data.extend_from_slice(&pattern.strength.to_le_bytes());
190            data.extend_from_slice(&pattern.frequency.to_le_bytes());
191            data.extend_from_slice(&pattern.last_activation_ms.to_le_bytes());
192            data.extend_from_slice(&pattern.performance_metric.to_le_bytes());
193
194            let seq_len = pattern.pattern.len() as u16;
195            data.extend_from_slice(&seq_len.to_le_bytes());
196            for &val in &pattern.pattern {
197                data.extend_from_slice(&val.to_le_bytes());
198            }
199        }
200
201        // Skills
202        let skill_count = self.skills.len() as u32;
203        data.extend_from_slice(&skill_count.to_le_bytes());
204        for (name, skill) in &self.skills {
205            let name_bytes = name.as_bytes();
206            data.push(name_bytes.len() as u8);
207            data.extend_from_slice(name_bytes);
208            data.extend_from_slice(&skill.mastery_level.to_le_bytes());
209            data.extend_from_slice(&skill.error_rate.to_le_bytes());
210            data.extend_from_slice(&skill.last_practiced_ms.to_le_bytes());
211            data.extend_from_slice(&skill.practice_count.to_le_bytes());
212        }
213
214        let tmp_path = dir.join("implicit_memory.bin.tmp");
215        fs::write(&tmp_path, data).map_err(|e| e.to_string())?;
216        fs::rename(&tmp_path, &path).map_err(|e| e.to_string())
217    }
218
219    pub fn load(dir: &Path) -> Result<Self, String> {
220        let path = dir.join("implicit_memory.bin");
221        if !path.exists() {
222            return Ok(Self::new());
223        }
224
225        let data = fs::read(&path).map_err(|e| e.to_string())?;
226        if data.len() < 9 || &data[0..4] != b"IMPL" {
227            return Ok(Self::new());
228        }
229
230        let mut idx = 5;
231        let mut patterns = HashMap::new();
232        let mut skills = HashMap::new();
233
234        // Read patterns
235        if idx + 4 <= data.len() {
236            let pattern_count =
237                u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
238                    as usize;
239            idx += 4;
240
241            for _ in 0..pattern_count {
242                if idx + 32 > data.len() {
243                    break;
244                }
245
246                let hash = u64::from_le_bytes([
247                    data[idx],
248                    data[idx + 1],
249                    data[idx + 2],
250                    data[idx + 3],
251                    data[idx + 4],
252                    data[idx + 5],
253                    data[idx + 6],
254                    data[idx + 7],
255                ]);
256                idx += 8;
257
258                let strength =
259                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
260                idx += 4;
261
262                let frequency =
263                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
264                idx += 4;
265
266                let last_activation_ms = u64::from_le_bytes([
267                    data[idx],
268                    data[idx + 1],
269                    data[idx + 2],
270                    data[idx + 3],
271                    data[idx + 4],
272                    data[idx + 5],
273                    data[idx + 6],
274                    data[idx + 7],
275                ]);
276                idx += 8;
277
278                let performance_metric =
279                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
280                idx += 4;
281
282                if idx + 2 <= data.len() {
283                    let seq_len = u16::from_le_bytes([data[idx], data[idx + 1]]) as usize;
284                    idx += 2;
285
286                    let mut pattern = Vec::new();
287                    for _ in 0..seq_len {
288                        if idx + 4 > data.len() {
289                            break;
290                        }
291                        let val = u32::from_le_bytes([
292                            data[idx],
293                            data[idx + 1],
294                            data[idx + 2],
295                            data[idx + 3],
296                        ]);
297                        pattern.push(val);
298                        idx += 4;
299                    }
300
301                    patterns.insert(
302                        hash,
303                        ImplicitPattern {
304                            pattern,
305                            strength,
306                            frequency,
307                            last_activation_ms,
308                            performance_metric,
309                        },
310                    );
311                }
312            }
313        }
314
315        // Read skills
316        if idx + 4 <= data.len() {
317            let skill_count =
318                u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
319                    as usize;
320            idx += 4;
321
322            for _ in 0..skill_count {
323                if idx >= data.len() {
324                    break;
325                }
326                let name_len = data[idx] as usize;
327                idx += 1;
328
329                if idx + name_len > data.len() {
330                    break;
331                }
332                let name = String::from_utf8_lossy(&data[idx..idx + name_len]).to_string();
333                idx += name_len;
334
335                if idx + 20 > data.len() {
336                    break;
337                }
338                let mastery_level =
339                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
340                idx += 4;
341
342                let error_rate =
343                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
344                idx += 4;
345
346                let last_practiced_ms = u64::from_le_bytes([
347                    data[idx],
348                    data[idx + 1],
349                    data[idx + 2],
350                    data[idx + 3],
351                    data[idx + 4],
352                    data[idx + 5],
353                    data[idx + 6],
354                    data[idx + 7],
355                ]);
356                idx += 8;
357
358                let practice_count =
359                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
360                idx += 4;
361
362                skills.insert(
363                    name.clone(),
364                    SkillNode {
365                        name,
366                        mastery_level,
367                        error_rate,
368                        last_practiced_ms,
369                        practice_count,
370                    },
371                );
372            }
373        }
374
375        Ok(Self {
376            patterns,
377            skills,
378            habits: Vec::new(),
379            conditioning: HashMap::new(),
380        })
381    }
382
383    pub fn load_or_init(dir: &Path) -> Self {
384        Self::load(dir).unwrap_or_else(|_| Self::new())
385    }
386}