microscope_memory/
inner_monologue.rs1use 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 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 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 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 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 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 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 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 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 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 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}