Skip to main content

microscope_memory/
self_reflect.rs

1//! Self-Reflection Module for Microscope Memory.
2//!
3//! Periodically introspects the system'\''s own cognitive state and generates
4//! a self-reflective narrative - the system "thinking about itself."
5
6use std::fs;
7use std::path::Path;
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use crate::archetype::ArchetypeState;
11use crate::attention::AttentionState;
12use crate::config::Config;
13use crate::emotional_state::EmotionalStateRing;
14use crate::hebbian::HebbianState;
15use crate::narrative::NarrativeState;
16use crate::reader::MicroscopeReader;
17use crate::spaced_repetition::SpacedRepetition;
18use crate::thought_graph::ThoughtGraphState;
19use colored::Colorize;
20
21pub const AUTO_REFLECT_INTERVAL: usize = 5;
22const MAX_REFLECTION_BYTES: usize = 2048;
23
24pub struct ReflectionState {
25    pub last_reflection_ms: u64,
26    pub total_reflections: u64,
27    pub last_reflection_text: String,
28}
29
30impl ReflectionState {
31    pub fn load_or_init(output_dir: &Path) -> Self {
32        let path = output_dir.join("self_reflect.bin");
33        if path.exists() {
34            if let Ok(data) = fs::read(&path) {
35                if data.len() >= 14 && &data[0..4] == b"SELF" {
36                    let ts = u64::from_le_bytes(data[4..12].try_into().unwrap_or([0; 8]));
37                    let count = u64::from_le_bytes(data[12..20].try_into().unwrap_or([0; 8]));
38                    let rlen =
39                        u16::from_le_bytes(data[20..22].try_into().unwrap_or([0; 2])) as usize;
40                    let text = if rlen > 0 && 22 + rlen <= data.len() {
41                        String::from_utf8_lossy(&data[22..22 + rlen]).to_string()
42                    } else {
43                        String::new()
44                    };
45                    return Self {
46                        last_reflection_ms: ts,
47                        total_reflections: count,
48                        last_reflection_text: text,
49                    };
50                }
51            }
52        }
53        Self {
54            last_reflection_ms: 0,
55            total_reflections: 0,
56            last_reflection_text: String::new(),
57        }
58    }
59
60    pub fn save(&self, output_dir: &Path) -> Result<(), String> {
61        let path = output_dir.join("self_reflect.bin");
62        let text_bytes = self.last_reflection_text.as_bytes();
63        let rlen = text_bytes.len().min(MAX_REFLECTION_BYTES) as u16;
64        let mut buf = Vec::with_capacity(22 + rlen as usize);
65        buf.extend_from_slice(b"SELF");
66        buf.extend_from_slice(&self.last_reflection_ms.to_le_bytes());
67        buf.extend_from_slice(&self.total_reflections.to_le_bytes());
68        buf.extend_from_slice(&rlen.to_le_bytes());
69        buf.extend_from_slice(&text_bytes[..rlen as usize]);
70        let tmp_path = output_dir.join("self_reflect.bin.tmp");
71        fs::write(&tmp_path, &buf).map_err(|e| format!("write self_reflect.bin: {}", e))?;
72        fs::rename(&tmp_path, &path).map_err(|e| format!("rename self_reflect.bin: {}", e))
73    }
74}
75
76pub fn introspect(_config: &Config, reader: &MicroscopeReader, output_dir: &Path) -> String {
77    let now_ms = SystemTime::now()
78        .duration_since(UNIX_EPOCH)
79        .unwrap_or_default()
80        .as_millis() as u64;
81
82    let hebb = HebbianState::load_or_init(output_dir, reader.block_count);
83    let attention = AttentionState::load_or_init(output_dir);
84    let archetypes = ArchetypeState::load_or_init(output_dir);
85    let narrative = NarrativeState::load_or_init(output_dir);
86    let esr = EmotionalStateRing::load_or_init(output_dir);
87    let spaced = SpacedRepetition::load_or_init(output_dir);
88    let thought_graph = ThoughtGraphState::load_or_init(output_dir);
89    let reflection = ReflectionState::load_or_init(output_dir);
90
91    let mut parts: Vec<String> = Vec::new();
92
93    // 1. Emotional state
94    if esr.is_active() {
95        let labels = crate::reader::EMOTION_DIMS;
96        let mut emotions: Vec<(usize, f32)> = esr
97            .current
98            .iter()
99            .enumerate()
100            .map(|(i, &v)| (i, v))
101            .collect();
102        emotions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
103        let top: Vec<String> = emotions
104            .iter()
105            .take(3)
106            .filter(|(_, v)| *v > 0.1)
107            .map(|(i, v)| {
108                let intensity = if *v > 0.6 {
109                    "strongly "
110                } else if *v > 0.3 {
111                    ""
112                } else {
113                    "slightly "
114                };
115                format!("{}{}", intensity, labels.get(*i).unwrap_or(&"?"))
116            })
117            .collect();
118        if !top.is_empty() {
119            parts.push(format!("I feel {}", top.join(" and ")));
120        }
121    }
122
123    // 2. Attention focus
124    let layer_names = [
125        "Hebbian",
126        "Mirror",
127        "Resonance",
128        "Archetype",
129        "Emotional",
130        "ThoughtGraph",
131        "PredictiveCache",
132    ];
133    let mut layers: Vec<(usize, f32)> = attention
134        .learned_weights
135        .iter()
136        .enumerate()
137        .map(|(i, &w)| (i, w))
138        .collect();
139    layers.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
140    if let Some((best_idx, best_w)) = layers.first() {
141        let name = layer_names.get(*best_idx).unwrap_or(&"?");
142        parts.push(format!(
143            "my {} layer is most active ({:.0}%)",
144            name,
145            best_w * 100.0
146        ));
147    }
148
149    // 3. Hebbian activity
150    let hot_count = hebb.activations.iter().filter(|a| a.energy > 0.1).count();
151    let total_energy: f32 = hebb.activations.iter().map(|a| a.energy).sum();
152    if hot_count > 0 {
153        parts.push(format!(
154            "{} hot memories (energy={:.1})",
155            hot_count, total_energy
156        ));
157    }
158
159    // 4. Archetypes
160    if !archetypes.archetypes.is_empty() {
161        let arch_labels: Vec<&str> = archetypes
162            .archetypes
163            .iter()
164            .take(3)
165            .map(|a| a.label.as_str())
166            .collect();
167        parts.push(format!(
168            "{} archetypes: {}",
169            archetypes.archetypes.len(),
170            arch_labels.join(", ")
171        ));
172    }
173
174    // 5. Spaced repetition
175    let due = spaced.due_count();
176    let mastered = spaced.mastered_count();
177    if due > 0 || mastered > 0 {
178        parts.push(format!("{} memories due, {} mastered", due, mastered));
179    }
180
181    // 6. Thought patterns
182    let patterns = thought_graph.crystallized_count();
183    if patterns > 0 {
184        parts.push(format!("{} thought patterns crystallized", patterns));
185    }
186
187    // 7. Memory stats
188    parts.push(format!("{} total blocks", reader.block_count));
189
190    // 8. Previous reflection context
191    if reflection.total_reflections > 0 && !reflection.last_reflection_text.is_empty() {
192        let prev = crate::safe_truncate(&reflection.last_reflection_text, 60);
193        parts.push(format!("previously I reflected: \"{}\"", prev));
194    }
195
196    // 9. Session count
197    if narrative.session_count > 0 {
198        parts.push(format!(
199            "this is my {}th interaction",
200            narrative.session_count
201        ));
202    }
203
204    let reflection_text = if parts.is_empty() {
205        "I am aware of myself but have no distinct feelings right now.".to_string()
206    } else {
207        parts.join(". ") + "."
208    };
209
210    let mut state = ReflectionState::load_or_init(output_dir);
211    state.last_reflection_ms = now_ms;
212    state.total_reflections += 1;
213    state.last_reflection_text = reflection_text.clone();
214    let _ = state.save(output_dir);
215
216    reflection_text
217}
218
219pub fn format_reflection(text: &str) -> String {
220    format!("  {} {}", "SELF:".cyan().bold(), text)
221}