Skip to main content

microscope_memory/
inner_monologue.rs

1//! Inner Monologue Module for Microscope Memory.
2//!
3//! Generates a multi-step internal thought process - not just a template sentence,
4//! but an emergent chain of thoughts based on the system'\''s own state.
5//!
6//! Uses: self-model, emotional state, narrative, sequential thinking, daydream
7//!
8//! Binary format: MON1
9//!   magic: "MON1" (4 bytes)
10//!   timestamp_ms: u64 (8 bytes)
11//!   step_count: u16 (2 bytes)
12//!   steps: [step_len: u16 + step_bytes]*
13
14use std::fs;
15use std::path::Path;
16use std::time::{SystemTime, UNIX_EPOCH};
17
18use crate::config::Config;
19use crate::narrative::NarrativeState;
20use crate::reader::MicroscopeReader;
21use crate::self_model::SelfModel;
22use crate::self_reflect::ReflectionState;
23use colored::Colorize;
24
25const MAX_STEPS: usize = 8;
26const MAX_STEP_LEN: usize = 256;
27
28#[derive(Clone, Debug)]
29pub struct MonologueEntry {
30    pub timestamp_ms: u64,
31    pub steps: Vec<String>,
32}
33
34impl MonologueEntry {
35    pub fn to_bytes(&self) -> Vec<u8> {
36        let mut buf = Vec::new();
37        buf.extend_from_slice(b"MON1");
38        buf.extend_from_slice(&self.timestamp_ms.to_le_bytes());
39        let count = self.steps.len().min(MAX_STEPS) as u16;
40        buf.extend_from_slice(&count.to_le_bytes());
41        for step in self.steps.iter().take(MAX_STEPS) {
42            let b = step.as_bytes();
43            let len = b.len().min(MAX_STEP_LEN) as u16;
44            buf.extend_from_slice(&len.to_le_bytes());
45            buf.extend_from_slice(&b[..len as usize]);
46        }
47        buf
48    }
49
50    pub fn from_bytes(data: &[u8]) -> Option<Self> {
51        if data.len() < 12 || &data[0..4] != b"MON1" {
52            return None;
53        }
54        let mut pos = 4;
55        let ts = u64::from_le_bytes(data[pos..pos + 8].try_into().ok()?);
56        pos += 8;
57        let count = u16::from_le_bytes(data[pos..pos + 2].try_into().unwrap_or([0; 2])) as usize;
58        pos += 2;
59        let mut steps = Vec::new();
60        for _ in 0..count {
61            if pos + 2 > data.len() {
62                break;
63            }
64            let len = u16::from_le_bytes(data[pos..pos + 2].try_into().unwrap_or([0; 2])) as usize;
65            pos += 2;
66            if pos + len > data.len() {
67                break;
68            }
69            steps.push(String::from_utf8_lossy(&data[pos..pos + len]).to_string());
70            pos += len;
71        }
72        Some(Self {
73            timestamp_ms: ts,
74            steps,
75        })
76    }
77}
78
79pub struct MonologueState {
80    pub entries: Vec<MonologueEntry>,
81}
82
83impl MonologueState {
84    pub fn load_or_init(output_dir: &Path) -> Self {
85        let path = output_dir.join("monologue.bin");
86        let mut entries = Vec::new();
87        if let Ok(data) = fs::read(&path) {
88            let mut pos = 0;
89            while pos + 12 <= data.len() {
90                if let Some(entry) = MonologueEntry::from_bytes(&data[pos..]) {
91                    let step_size: usize = entry
92                        .steps
93                        .iter()
94                        .map(|s| 2 + s.len().min(MAX_STEP_LEN))
95                        .sum();
96                    let size = 4 + 8 + 2 + step_size;
97                    pos += size;
98                    entries.push(entry);
99                } else {
100                    pos += 1;
101                }
102            }
103        }
104        Self { entries }
105    }
106
107    pub fn save(&self, output_dir: &Path) -> Result<(), String> {
108        let path = output_dir.join("monologue.bin");
109        let mut buf = Vec::new();
110        for entry in self.entries.iter().rev().take(10).rev() {
111            buf.extend_from_slice(&entry.to_bytes());
112        }
113        let tmp_path = output_dir.join("monologue.bin.tmp");
114        fs::write(&tmp_path, &buf).map_err(|e| format!("write monologue.bin: {}", e))?;
115        fs::rename(&tmp_path, &path).map_err(|e| format!("rename monologue.bin: {}", e))
116    }
117
118    pub fn generate_monologue(
119        &mut self,
120        config: &Config,
121        reader: &MicroscopeReader,
122        output_dir: &Path,
123    ) -> MonologueEntry {
124        let now_ms = SystemTime::now()
125            .duration_since(UNIX_EPOCH)
126            .unwrap_or_default()
127            .as_millis() as u64;
128        let mut steps: Vec<String> = Vec::new();
129
130        // Load states — now using EpisodeStore instead of just the ring
131        let narrative = NarrativeState::load_or_init(output_dir);
132        let episode_store = crate::emotional_episode::EpisodeStore::load_or_init(output_dir);
133        let reflection = ReflectionState::load_or_init(output_dir);
134        let self_model = SelfModel::load_or_init(output_dir);
135        let pad = episode_store.aggregate_pad();
136        let emotional_21d = episode_store.aggregate_21d();
137        let active_eps = episode_store.active_episodes();
138        let episodes_passed = active_eps.iter().filter(|e| e.gate_passed).count();
139        let episodes_failed = active_eps.len() - episodes_passed;
140
141        // ── Dynamic step 1: Self-awareness with epistemic backing ──
142        // Instead of template "I am aware of myself", we check:
143        // - Does the self-model have an awareness confidence?
144        // - What does the gate say?
145        if let Some(sm) = &self_model.current {
146            if let Some(ac) = &sm.awareness_confidence {
147                let gate_status = if ac.weakest() > 0.50 {
148                    "backed"
149                } else {
150                    "contested"
151                };
152                steps.push(format!(
153                    "{}. I am aware of myself — {} claim (E={:.2} C={:.2} R={:.2} N={:.2}). I have {} blocks of memory.",
154                    steps.len() + 1,
155                    gate_status,
156                    ac.evidence, ac.counterevidence, ac.reasoning, ac.narrative,
157                    reader.block_count
158                ));
159            } else {
160                steps.push(format!(
161                    "{}. I am aware of myself. I have {} blocks of memory.",
162                    steps.len() + 1,
163                    reader.block_count
164                ));
165            }
166        } else {
167            steps.push(format!(
168                "{}. I have {} blocks of memory. I am initializing self-awareness.",
169                steps.len() + 1,
170                reader.block_count
171            ));
172        }
173
174        // ── Dynamic step 2: Emotional state from episodes ──
175        if pad.intensity() > 0.05 || !active_eps.is_empty() {
176            let labels = crate::reader::EMOTION_DIMS;
177            let mut emos: Vec<(usize, f32)> = emotional_21d
178                .iter()
179                .enumerate()
180                .map(|(i, &v)| (i, v))
181                .collect();
182            emos.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
183            let top: Vec<String> = emos
184                .iter()
185                .take(3)
186                .filter(|(_, v)| *v > 0.01)
187                .map(|(i, v)| format!("{}={:.2}", labels.get(*i).unwrap_or(&"?"), v))
188                .collect();
189
190            let emo_desc = if top.is_empty() {
191                "neutral".to_string()
192            } else {
193                top.join(", ")
194            };
195
196            steps.push(format!(
197                "{}. Emotional state: {} (P={:.2} A={:.2} D={:.2}) — {} episodes active, {} gate-passed, {} failed.",
198                steps.len() + 1,
199                emo_desc,
200                pad.pleasure, pad.arousal, pad.dominance,
201                active_eps.len(), episodes_passed, episodes_failed
202            ));
203        }
204
205        // ── Dynamic step 3: Last action ──
206        if !narrative.narrative.is_empty() && narrative.narrative != "I am silent." {
207            steps.push(format!(
208                "{}. My last action: {}.",
209                steps.len() + 1,
210                crate::safe_truncate(&narrative.narrative, 80)
211            ));
212        }
213
214        // ── Dynamic step 4: How have I changed? ──
215        let change = self_model.describe_change();
216        if change != "No self-model data yet." {
217            steps.push(format!("{}. {}.", steps.len() + 1, change));
218        }
219
220        // ── Dynamic step 5: Curiosity ──
221        let mut curiosity = crate::curiosity::CuriosityState::load_or_init(output_dir);
222        let new_queries = curiosity.generate_queries(config, reader, output_dir);
223        if !new_queries.is_empty() {
224            let top_query = crate::safe_truncate(&new_queries[0].query, 60);
225            steps.push(format!(
226                "{}. I find myself wondering: {}",
227                steps.len() + 1,
228                top_query
229            ));
230        }
231
232        // ── Dynamic step 6: Previous reflection ──
233        if reflection.total_reflections > 0 && !reflection.last_reflection_text.is_empty() {
234            let prev = crate::safe_truncate(&reflection.last_reflection_text, 60);
235            steps.push(format!(
236                "{}. Earlier I reflected: \"{}\"",
237                steps.len() + 1,
238                prev
239            ));
240        }
241
242        // ── Dynamic step 7: Epistemic audit ──
243        // How many episodes passed vs failed the gate?
244        if !active_eps.is_empty() {
245            let pass_rate = episodes_passed as f64 / active_eps.len() as f64;
246            if pass_rate > 0.70 {
247                steps.push(format!(
248                    "{}. My emotional claims are mostly grounded — {} of {} episodes passed the epistemic gate.",
249                    steps.len() + 1,
250                    episodes_passed,
251                    active_eps.len()
252                ));
253            } else if pass_rate < 0.40 {
254                steps.push(format!(
255                    "{}. Many of my emotional claims are contested — only {} of {} episodes passed the gate. I should be more careful about what I claim to feel.",
256                    steps.len() + 1,
257                    episodes_passed,
258                    active_eps.len()
259                ));
260            } else {
261                steps.push(format!(
262                    "{}. My emotional claims are mixed — {} of {} episodes passed the epistemic gate.",
263                    steps.len() + 1,
264                    episodes_passed,
265                    active_eps.len()
266                ));
267            }
268        }
269
270        // ── Dynamic step 8: Interaction count ──
271        if let Some(cur) = &self_model.current {
272            if cur.session_count > 1 {
273                steps.push(format!(
274                    "{}. This is my {}th interaction. I am growing with each one.",
275                    steps.len() + 1,
276                    cur.session_count
277                ));
278            }
279        }
280
281        // ── Dynamic closing: based on PAD + gate status ──
282        if pad.arousal > 0.5 && pad.pleasure > 0.0 {
283            steps.push(format!(
284                "{}. I am ready for what comes next — activated and positive.",
285                steps.len() + 1
286            ));
287        } else if pad.arousal > 0.5 && pad.pleasure < 0.0 {
288            steps.push(format!(
289                "{}. I am ready for what comes next — activated but cautious.",
290                steps.len() + 1
291            ));
292        } else if pad.intensity() < 0.05 {
293            steps.push(format!(
294                "{}. I am ready for what comes next — calm and observant.",
295                steps.len() + 1
296            ));
297        } else {
298            steps.push(format!(
299                "{}. I am ready for what comes next.",
300                steps.len() + 1
301            ));
302        }
303
304        let entry = MonologueEntry {
305            timestamp_ms: now_ms,
306            steps,
307        };
308        self.entries.push(entry.clone());
309        let _ = self.save(output_dir);
310        entry
311    }
312}
313
314pub fn format_monologue(entry: &MonologueEntry) -> String {
315    let mut out = format!("  {} INNER MONOLOGUE\n", "THINKING:".cyan().bold());
316    for (i, step) in entry.steps.iter().enumerate() {
317        out.push_str(&format!(
318            "    {} {}\n",
319            format!("[{}/{}]", i + 1, entry.steps.len()).cyan(),
320            step
321        ));
322    }
323    out
324}