1use crate::config::Config;
9use crate::reader::MicroscopeReader;
10use crate::{
11 read_append_log, store_memory, store_memory_with_embedding, store_memory_with_emotion,
12 LAYER_NAMES,
13};
14use microscope_hooks::*;
15use serde_json::{json, Value};
16use std::collections::{HashMap, HashSet};
17use std::io::{self, BufRead, BufReader, Read, Write};
18use std::path::Path;
19
20const MAX_MESSAGE_LEN: usize = 16 * 1024 * 1024; const MAX_HEADER_LEN: usize = 64 * 1024; pub fn run(config: Config) {
30 #[cfg(windows)]
32 unsafe {
33 windows_sys::Win32::System::Console::SetConsoleCP(65001);
34 windows_sys::Win32::System::Console::SetConsoleOutputCP(65001);
35 }
36
37 let hook_config = if config.hooks.read_only {
39 HookConfig::read_only()
40 } else if config.hooks.write_enabled {
41 HookConfig::full()
42 } else {
43 HookConfig::default()
44 };
45 let hook_manager = HookManager::new(hook_config);
46 let mut session_started = false;
47
48 eprintln!(
49 "[hooks] manager initialized (read_only={}, write_enabled={})",
50 config.hooks.read_only, config.hooks.write_enabled
51 );
52
53 let stdin = io::stdin();
54 let stdout = io::stdout();
55 let mut reader = BufReader::new(stdin.lock());
56 let mut stdout = stdout.lock();
57
58 loop {
59 let incoming = match read_message(&mut reader) {
60 Ok(Some(msg)) => msg,
61 Ok(None) => break,
62 Err(e) => {
63 let err = json!({
64 "jsonrpc": "2.0",
65 "id": null,
66 "error": { "code": -32700, "message": format!("Read error: {}", e) }
67 });
68 let _ = write_message(&mut stdout, &err, true);
69 continue;
70 }
71 };
72
73 let request = match serde_json::from_str::<Value>(&incoming.payload) {
74 Ok(v) => v,
75 Err(e) => {
76 let err = json!({
77 "jsonrpc": "2.0",
78 "id": null,
79 "error": { "code": -32700, "message": format!("Parse error: {}", e) }
80 });
81 let _ = write_message(&mut stdout, &err, incoming.framed);
82 continue;
83 }
84 };
85
86 let id = request.get("id").cloned().unwrap_or(Value::Null);
87 let method = request.get("method").and_then(|m| m.as_str()).unwrap_or("");
88
89 if method == "initialize" && !session_started {
91 let ctx = HookContext::new(HookEvent::SessionStart);
92 let _ = hook_manager.execute(HookEvent::SessionStart, ctx);
93 session_started = true;
94 eprintln!("[hooks] session started");
95 }
96
97 let response = match method {
98 "initialize" => handle_initialize(&id),
99 "initialized" => continue,
100 "tools/list" => handle_tools_list(&id),
101 "tools/call" => handle_tools_call_with_hooks(&id, &request, &config, &hook_manager),
102 "ping" => json!({ "jsonrpc": "2.0", "id": id, "result": {} }),
103 "notifications/cancelled" | "notifications/initialized" => continue,
104 _ => json!({
105 "jsonrpc": "2.0",
106 "id": id,
107 "error": { "code": -32601, "message": format!("Method not found: {}", method) }
108 }),
109 };
110
111 let _ = write_message(&mut stdout, &response, incoming.framed);
112 }
113}
114
115fn handle_tools_call_with_hooks(
116 id: &Value,
117 request: &Value,
118 config: &Config,
119 hook_manager: &HookManager,
120) -> Value {
121 let params = request.get("params").cloned().unwrap_or(json!({}));
122 let tool_name = params
123 .get("name")
124 .and_then(|n: &Value| n.as_str())
125 .unwrap_or("");
126 let args = params.get("arguments").cloned().unwrap_or(json!({}));
127
128 let before_ctx = HookContext::new(HookEvent::BeforeToolCall).with_tool(tool_name, args.clone());
130 let _ = hook_manager.execute(HookEvent::BeforeToolCall, before_ctx);
131
132 let result = match tool_name {
134 "memory_status" => tool_status(config),
135 "memory_store" => tool_store(config, &args),
136 "memory_recall" => tool_recall(config, &args),
137 "memory_find" => tool_find(config, &args),
138 "memory_mql_query" => tool_mql_query(config, &args),
139 "memory_build" => tool_build(config, &args),
140 "memory_look" => tool_look(config, &args),
141 "memory_session_log" => tool_session_log(config, &args),
142 "memory_consolidate" => tool_consolidate(config, &args),
143 "memory_dream" => tool_dream(config, &args),
144 "memory_session_context" => tool_session_context(config, &args),
145 "memory_ping" => tool_ping(config, &args),
146 "memory_auto_context" => tool_auto_context(config, &args),
147 "memory_timeline" => tool_timeline(config, &args),
148 "memory_loops" => tool_loops(config, &args),
149 "memory_resolve_loop" => tool_resolve_loop(config, &args),
150 "memory_radial" => tool_radial(config, &args),
151 "memory_soft" => tool_soft(config, &args),
152 "memory_think" => tool_think(config, &args),
153 "memory_hebbian" => tool_hebbian(config, &args),
154 "memory_hottest" => tool_hottest(config, &args),
155 "memory_archetypes" => tool_archetypes(config, &args),
156 "memory_patterns" => tool_patterns(config, &args),
157 "memory_attention" => tool_attention(config, &args),
158 "memory_introspect" => tool_introspect(config, &args),
159 "memory_self_model" => tool_self_model(config, &args),
160 "memory_curiosity" => tool_curiosity(config, &args),
161 "memory_monologue" => tool_monologue(config, &args),
162 "memory_stories" => tool_stories(config, &args),
163 "memory_daydream" => tool_daydream(config, &args),
164 "memory_hyperfocus" => tool_hyperfocus(config, &args),
165 "memory_emotional_field" => tool_emotional_field(config, &args),
166 "memory_embed" => tool_embed(config, &args),
167 "memory_similar" => tool_similar(config, &args),
168 "memory_links" => tool_links(config, &args),
169 "memory_fingerprint" => tool_fingerprint(config, &args),
170 "memory_dream_log" => tool_dream_log(config, &args),
171 "memory_resonance" => tool_resonance(config, &args),
172 "memory_mirror" => tool_mirror(config, &args),
173 "memory_predictions" => tool_predictions(config, &args),
174 "memory_paths" => tool_paths(config, &args),
175 "memory_temporal_patterns" => tool_temporal_patterns(config, &args),
176 "memory_modalities" => tool_modalities(config, &args),
177 "memory_doctor" => tool_doctor(config, &args),
178 "memory_rebuild" => tool_rebuild(config, &args),
179 "memory_store_data" => tool_store_data(config, &args),
180 "memory_resonant" => tool_resonant(config, &args),
181 "memory_autonomous" => tool_autonomous(config, &args),
182 "memory_consciousness" => tool_consciousness(config, &args),
183 "memory_evidence_show" => tool_evidence_show(config, &args),
184 "memory_evidence_link" => tool_evidence_link(config, &args),
185 "memory_evidence_refute" => tool_evidence_refute(config, &args),
186 "memory_evidence_audit" => tool_evidence_audit(config, &args),
187 "memory_evidence_gates" => tool_evidence_gates(config, &args),
188 _ => Err(format!("Unknown tool: {}", tool_name)),
189 };
190
191 match result {
192 Ok(content) => {
193 let after_ctx = HookContext::new(HookEvent::AfterToolCall)
195 .with_tool(tool_name, args)
196 .with_response(&content);
197 let hook_ctx = hook_manager.execute(HookEvent::AfterToolCall, after_ctx);
198 if !hook_ctx.memory_candidates.is_empty() {
199 eprintln!(
200 "[hooks] after_tool_call: {} candidate(s) from '{}'",
201 hook_ctx.memory_candidates.len(),
202 tool_name
203 );
204 }
205
206 json!({
207 "jsonrpc": "2.0",
208 "id": id,
209 "result": {
210 "content": [{ "type": "text", "text": content }]
211 }
212 })
213 }
214 Err(e) => {
215 let err_ctx = HookContext::new(HookEvent::Error)
217 .with_tool(tool_name, args)
218 .with_error(&e, "TOOL_ERROR");
219 let hook_ctx = hook_manager.execute(HookEvent::Error, err_ctx);
220 if !hook_ctx.memory_candidates.is_empty() {
221 eprintln!("[hooks] on_error: stored error trace for '{}'", tool_name);
222 }
223
224 json!({
225 "jsonrpc": "2.0",
226 "id": id,
227 "result": {
228 "content": [{ "type": "text", "text": format!("Error: {}", e) }],
229 "isError": true
230 }
231 })
232 }
233 }
234}
235
236struct IncomingMessage {
237 payload: String,
238 framed: bool,
239}
240
241fn read_message<R: BufRead + Read>(reader: &mut R) -> io::Result<Option<IncomingMessage>> {
242 let first_line = loop {
244 let mut buf = Vec::new();
245 let mut byte = [0u8; 1];
246 loop {
247 let n = reader.read(&mut byte)?;
248 if n == 0 {
249 return Ok(None);
250 }
251 if byte[0] == b'\n' {
252 break;
253 }
254 if byte[0] != b'\r' {
255 buf.push(byte[0]);
256 if buf.len() > MAX_HEADER_LEN {
257 return Err(io::Error::new(
258 io::ErrorKind::InvalidData,
259 format!("MCP header line exceeds maximum allowed {}", MAX_HEADER_LEN),
260 ));
261 }
262 }
263 }
264 if !buf.is_empty() {
265 break String::from_utf8(buf)
266 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8"))?;
267 }
268 };
269
270 if first_line.starts_with('{') {
271 return Ok(Some(IncomingMessage {
272 payload: first_line,
273 framed: false,
274 }));
275 }
276
277 let mut content_length: Option<usize> = None;
278 parse_header_line(&first_line, &mut content_length);
279
280 let mut header_line = String::new();
281 loop {
282 header_line.clear();
283 let bytes = reader.read_line(&mut header_line)?;
284 if bytes == 0 {
285 break;
286 }
287 if header_line.len() > MAX_HEADER_LEN {
288 return Err(io::Error::new(
289 io::ErrorKind::InvalidData,
290 format!("MCP header line exceeds maximum allowed {}", MAX_HEADER_LEN),
291 ));
292 }
293 if header_line == "\r\n" || header_line == "\n" {
294 break;
295 }
296 parse_header_line(&header_line, &mut content_length);
297 }
298
299 let len = content_length.ok_or_else(|| {
300 io::Error::new(
301 io::ErrorKind::InvalidData,
302 "Missing Content-Length header in framed MCP message",
303 )
304 })?;
305
306 if len > MAX_MESSAGE_LEN {
307 return Err(io::Error::new(
308 io::ErrorKind::InvalidData,
309 format!(
310 "Content-Length {} exceeds maximum allowed {}",
311 len, MAX_MESSAGE_LEN
312 ),
313 ));
314 }
315
316 let mut body = vec![0u8; len];
317 reader.read_exact(&mut body)?;
318 let payload = String::from_utf8(body)
319 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8 JSON payload"))?;
320
321 Ok(Some(IncomingMessage {
322 payload,
323 framed: true,
324 }))
325}
326
327fn parse_header_line(line: &str, content_length: &mut Option<usize>) {
328 let lower = line.to_ascii_lowercase();
329 if lower.starts_with("content-length:") {
330 let value = line
331 .split_once(':')
332 .map(|(_, v)| v.trim())
333 .and_then(|v| v.parse::<usize>().ok());
334 if let Some(v) = value {
335 *content_length = Some(v);
336 }
337 }
338}
339
340fn write_message<W: Write>(writer: &mut W, response: &Value, framed: bool) -> io::Result<()> {
341 if framed {
342 let payload = response.to_string();
343 write!(
344 writer,
345 "Content-Length: {}\r\n\r\n{}",
346 payload.len(),
347 payload
348 )?;
349 } else {
350 writeln!(writer, "{}", response)?;
351 }
352 writer.flush()
353}
354
355fn handle_initialize(id: &Value) -> Value {
356 json!({
357 "jsonrpc": "2.0",
358 "id": id,
359 "result": {
360 "protocolVersion": "2024-11-05",
361 "capabilities": {
362 "tools": {}
363 },
364 "serverInfo": {
365 "name": "microscope-memory",
366 "version": env!("CARGO_PKG_VERSION")
367 }
368 }
369 })
370}
371
372fn handle_tools_list(id: &Value) -> Value {
373 json!({
374 "jsonrpc": "2.0",
375 "id": id,
376 "result": {
377 "tools": [
378 {
379 "name": "memory_status",
380 "description": "Get microscope memory index status: block count, depths, append log size",
381 "inputSchema": {
382 "type": "object",
383 "properties": {},
384 "required": []
385 }
386 },
387 {
388 "name": "memory_store",
389 "description": "Store a new memory into the microscope append log",
390 "inputSchema": {
391 "type": "object",
392 "properties": {
393 "text": { "type": "string", "description": "Memory text to store" },
394 "layer": { "type": "string", "description": "Memory layer (long_term, short_term, session, associative, emotional, relational, reflections, echo_cache)", "default": "long_term" },
395 "importance": { "type": "integer", "description": "Importance level 1-10", "default": 5 },
396 "emotion": { "type": "array", "items": { "type": "number" }, "minItems": 21, "maxItems": 21, "description": "21D emotion vector: [joy, sadness, anger, fear, surprise, disgust, trust, anticipation, love, gratitude, curiosity, awe, confusion, anxiety, serenity, hope, pride, shame, guilt, empathy, excitement]" }
397 },
398 "required": ["text"]
399 }
400 },
401 {
402 "name": "memory_recall",
403 "description": "Natural language recall with auto-zoom — searches both main index and append log. Optional emotion vector biases results toward emotionally similar memories.",
404 "inputSchema": {
405 "type": "object",
406 "properties": {
407 "query": { "type": "string", "description": "Natural language query" },
408 "k": { "type": "integer", "description": "Max results to return", "default": 10 },
409 "emotion": { "type": "array", "items": { "type": "number" }, "minItems": 21, "maxItems": 21, "description": "21D emotion vector for emotional recall: [joy, sadness, anger, fear, surprise, disgust, trust, anticipation, love, gratitude, curiosity, awe, confusion, anxiety, serenity, hope, pride, shame, guilt, empathy, excitement]" }
410 },
411 "required": ["query"]
412 }
413 },
414 {
415 "name": "memory_find",
416 "description": "Brute-force text search across all depths",
417 "inputSchema": {
418 "type": "object",
419 "properties": {
420 "query": { "type": "string", "description": "Text to search for" },
421 "k": { "type": "integer", "description": "Max results", "default": 10 }
422 },
423 "required": ["query"]
424 }
425 },
426 {
427 "name": "memory_mql_query",
428 "description": "Execute an MQL (Microscope Query Language) query with filters: layer, depth, spatial, boolean",
429 "inputSchema": {
430 "type": "object",
431 "properties": {
432 "mql": { "type": "string", "description": "MQL expression, e.g. 'layer:long_term depth:2..5 \"memory\"'" }
433 },
434 "required": ["mql"]
435 }
436 },
437 {
438 "name": "memory_build",
439 "description": "Rebuild the microscope index from layer source files (merges append log)",
440 "inputSchema": {
441 "type": "object",
442 "properties": {
443 "force": { "type": "boolean", "description": "Force rebuild even if unchanged", "default": false }
444 },
445 "required": []
446 }
447 },
448 {
449 "name": "memory_session_log",
450 "description": "Read last N interactions from the session memory layer (no index needed, reads layers/session.txt directly)",
451 "inputSchema": {
452 "type": "object",
453 "properties": {
454 "n": { "type": "integer", "description": "Number of recent interactions to return", "default": 50 }
455 },
456 "required": []
457 }
458 },
459 {
460 "name": "memory_consolidate",
461 "description": "Consolidate recent session entries into long-term memory summaries. Groups entries by session ID and creates short summaries.",
462 "inputSchema": {
463 "type": "object",
464 "properties": {},
465 "required": []
466 }
467 },
468 {
469 "name": "memory_dream",
470 "description": "Dream consolidation — offline memory replay that strengthens important pathways and prunes weak ones (biological sleep analog).",
471 "inputSchema": {
472 "type": "object",
473 "properties": {},
474 "required": []
475 }
476 },
477 {
478 "name": "memory_look",
479 "description": "Manual spatial look at specific 3D coordinates and zoom level",
480 "inputSchema": {
481 "type": "object",
482 "properties": {
483 "x": { "type": "number", "description": "X coordinate (0.0-1.0)" },
484 "y": { "type": "number", "description": "Y coordinate (0.0-1.0)" },
485 "z": { "type": "number", "description": "Z coordinate (0.0-1.0)" },
486 "zoom": { "type": "integer", "description": "Depth level (0-8)" },
487 "k": { "type": "integer", "description": "Max results", "default": 10 }
488 },
489 "required": ["x", "y", "z", "zoom"]
490 }
491 },
492 {
493 "name": "memory_session_context",
494 "description": "Store conversation context automatically — saves the current session for later recall",
495 "inputSchema": {
496 "type": "object",
497 "properties": {
498 "context": { "type": "string", "description": "Conversation context to store" },
499 "summary": { "type": "string", "description": "Optional summary for long-term storage" }
500 },
501 "required": ["context"]
502 }
503 },
504 {
505 "name": "memory_ping",
506 "description": "Quick auto-context — fast recall without full indexing. Use this before answering to get relevant context automatically.",
507 "inputSchema": {
508 "type": "object",
509 "properties": {
510 "query": { "type": "string", "description": "What to get context for" }
511 },
512 "required": ["query"]
513 }
514 },
515 {
516 "name": "memory_auto_context",
517 "description": "Full system state snapshot: timeline (last session), open loops, block count, depth distribution, append log size",
518 "inputSchema": {
519 "type": "object",
520 "properties": {
521 "compact": { "type": "boolean", "description": "Compact mode (no box-drawing)", "default": false }
522 },
523 "required": []
524 }
525 },
526 {
527 "name": "memory_timeline",
528 "description": "Show chronological timeline of stored memories by time window",
529 "inputSchema": {
530 "type": "object",
531 "properties": {
532 "window": { "type": "string", "description": "Time window: today, yesterday, last_N_days, since:YYYY-MM-DD, last_session, all", "default": "last_session" },
533 "k": { "type": "integer", "description": "Max entries to return", "default": 20 }
534 },
535 "required": []
536 }
537 },
538 {
539 "name": "memory_loops",
540 "description": "List currently open loops (unresolved tasks/thoughts)",
541 "inputSchema": {
542 "type": "object",
543 "properties": {
544 "k": { "type": "integer", "description": "Max loops to return", "default": 20 }
545 },
546 "required": []
547 }
548 },
549 {
550 "name": "memory_resolve_loop",
551 "description": "Mark an open loop as resolved",
552 "inputSchema": {
553 "type": "object",
554 "properties": {
555 "id": { "type": "integer", "description": "Loop ID to resolve" }
556 },
557 "required": ["id"]
558 }
559 },
560 {
561 "name": "memory_radial",
562 "description": "Radial spatial search — find blocks within a radius at a given depth",
563 "inputSchema": {
564 "type": "object",
565 "properties": {
566 "x": { "type": "number", "description": "X coordinate (0.0-1.0)" },
567 "y": { "type": "number", "description": "Y coordinate (0.0-1.0)" },
568 "z": { "type": "number", "description": "Z coordinate (0.0-1.0)" },
569 "depth": { "type": "integer", "description": "Depth level (0-8)" },
570 "radius": { "type": "number", "description": "Search radius", "default": 0.1 },
571 "k": { "type": "integer", "description": "Max results", "default": 10 }
572 },
573 "required": ["x", "y", "z", "depth"]
574 }
575 },
576 {
577 "name": "memory_soft",
578 "description": "4D soft zoom search — smooth multi-depth spatial query",
579 "inputSchema": {
580 "type": "object",
581 "properties": {
582 "x": { "type": "number", "description": "X coordinate (0.0-1.0)" },
583 "y": { "type": "number", "description": "Y coordinate (0.0-1.0)" },
584 "z": { "type": "number", "description": "Z coordinate (0.0-1.0)" },
585 "zoom": { "type": "integer", "description": "Zoom level (0-8)" },
586 "k": { "type": "integer", "description": "Max results", "default": 10 }
587 },
588 "required": ["x", "y", "z", "zoom"]
589 }
590 },
591 {
592 "name": "memory_think",
593 "description": "Sequential thinking chain — brainstorm a topic through multi-step memory traversal",
594 "inputSchema": {
595 "type": "object",
596 "properties": {
597 "query": { "type": "string", "description": "Topic to think about" },
598 "max_steps": { "type": "integer", "description": "Maximum thinking steps", "default": 5 }
599 },
600 "required": ["query"]
601 }
602 },
603 {
604 "name": "memory_hebbian",
605 "description": "Show Hebbian learning state: activations, co-activations, energy, drifted blocks",
606 "inputSchema": {
607 "type": "object",
608 "properties": {},
609 "required": []
610 }
611 },
612 {
613 "name": "memory_hottest",
614 "description": "Show hottest (most frequently/recently activated) blocks",
615 "inputSchema": {
616 "type": "object",
617 "properties": {
618 "k": { "type": "integer", "description": "Number of hottest blocks", "default": 10 }
619 },
620 "required": []
621 }
622 },
623 {
624 "name": "memory_archetypes",
625 "description": "Show emerged archetypes — crystallized activation patterns",
626 "inputSchema": {
627 "type": "object",
628 "properties": {},
629 "required": []
630 }
631 },
632 {
633 "name": "memory_patterns",
634 "description": "Show thought graph patterns — crystallized recall sequences",
635 "inputSchema": {
636 "type": "object",
637 "properties": {
638 "k": { "type": "integer", "description": "Number of top patterns", "default": 10 }
639 },
640 "required": []
641 }
642 },
643 {
644 "name": "memory_attention",
645 "description": "Show attention mechanism state: learned layer weights, quality history",
646 "inputSchema": {
647 "type": "object",
648 "properties": {},
649 "required": []
650 }
651 },
652 {
653 "name": "memory_introspect",
654 "description": "Self-reflection — the system thinks about its own cognitive state",
655 "inputSchema": {
656 "type": "object",
657 "properties": {},
658 "required": []
659 }
660 },
661 {
662 "name": "memory_self_model",
663 "description": "Show the system's self-model snapshot",
664 "inputSchema": {
665 "type": "object",
666 "properties": {},
667 "required": []
668 }
669 },
670 {
671 "name": "memory_curiosity",
672 "description": "Show what the system is curious about — generated curiosity queries",
673 "inputSchema": {
674 "type": "object",
675 "properties": {},
676 "required": []
677 }
678 },
679 {
680 "name": "memory_monologue",
681 "description": "Generate an inner monologue — the system thinking to itself",
682 "inputSchema": {
683 "type": "object",
684 "properties": {},
685 "required": []
686 }
687 },
688 {
689 "name": "memory_stories",
690 "description": "Show narrative memory episodes — story arcs built from recalls",
691 "inputSchema": {
692 "type": "object",
693 "properties": {
694 "k": { "type": "integer", "description": "Number of recent episodes", "default": 5 }
695 },
696 "required": []
697 }
698 },
699 {
700 "name": "memory_daydream",
701 "description": "Associative drift (mind wandering) — start from a seed and drift through related memories",
702 "inputSchema": {
703 "type": "object",
704 "properties": {
705 "seed": { "type": "string", "description": "Seed text to start from (default: last narrative)", "default": "" },
706 "steps": { "type": "integer", "description": "Number of drift steps", "default": 3 }
707 },
708 "required": []
709 }
710 },
711 {
712 "name": "memory_hyperfocus",
713 "description": "Enter deep concentration mode on a specific topic",
714 "inputSchema": {
715 "type": "object",
716 "properties": {
717 "target": { "type": "string", "description": "Target topic to focus on" },
718 "focus_type": { "type": "string", "description": "Focus type: planning, problem_solving, creative, research", "default": "research" }
719 },
720 "required": ["target"]
721 }
722 },
723 {
724 "name": "memory_emotional_field",
725 "description": "Show emotional contagion state: local + remote emotional fields, total energy",
726 "inputSchema": {
727 "type": "object",
728 "properties": {},
729 "required": []
730 }
731 },
732 {
733 "name": "memory_embed",
734 "description": "Semantic search using embeddings — find conceptually similar memories",
735 "inputSchema": {
736 "type": "object",
737 "properties": {
738 "query": { "type": "string", "description": "Semantic query text" },
739 "k": { "type": "integer", "description": "Max results", "default": 10 },
740 "metric": { "type": "string", "description": "Distance metric: cosine, dot, l2", "default": "cosine" }
741 },
742 "required": ["query"]
743 }
744 },
745 {
746 "name": "memory_similar",
747 "description": "Find structurally similar blocks to a given text using fingerprint analysis",
748 "inputSchema": {
749 "type": "object",
750 "properties": {
751 "text": { "type": "string", "description": "Text to find structural matches for" },
752 "k": { "type": "integer", "description": "Max results", "default": 5 }
753 },
754 "required": ["text"]
755 }
756 },
757 {
758 "name": "memory_links",
759 "description": "Show structural wormhole links for a specific block index",
760 "inputSchema": {
761 "type": "object",
762 "properties": {
763 "block_index": { "type": "integer", "description": "Block index to inspect" }
764 },
765 "required": ["block_index"]
766 }
767 },
768 {
769 "name": "memory_fingerprint",
770 "description": "Build structural fingerprints and wormhole links from the current index",
771 "inputSchema": {
772 "type": "object",
773 "properties": {},
774 "required": []
775 }
776 },
777 {
778 "name": "memory_dream_log",
779 "description": "Show dream consolidation history — past offline memory replay cycles",
780 "inputSchema": {
781 "type": "object",
782 "properties": {
783 "k": { "type": "integer", "description": "Number of recent dream cycles", "default": 10 }
784 },
785 "required": []
786 }
787 },
788 {
789 "name": "memory_resonance",
790 "description": "Show resonance protocol state: pulses, field energy, pending integrations",
791 "inputSchema": {
792 "type": "object",
793 "properties": {},
794 "required": []
795 }
796 },
797 {
798 "name": "memory_mirror",
799 "description": "Show mirror neuron state: resonance echoes, boosted blocks, similarity stats",
800 "inputSchema": {
801 "type": "object",
802 "properties": {},
803 "required": []
804 }
805 },
806 {
807 "name": "memory_predictions",
808 "description": "Show predictive cache stats: hit rate, active predictions, confidence",
809 "inputSchema": {
810 "type": "object",
811 "properties": {},
812 "required": []
813 }
814 },
815 {
816 "name": "memory_paths",
817 "description": "Show recent thought paths — recall sequences grouped by session",
818 "inputSchema": {
819 "type": "object",
820 "properties": {
821 "sessions": { "type": "integer", "description": "Number of recent sessions", "default": 5 }
822 },
823 "required": []
824 }
825 },
826 {
827 "name": "memory_temporal_patterns",
828 "description": "Show temporal archetype patterns — time-of-day activation profiles",
829 "inputSchema": {
830 "type": "object",
831 "properties": {},
832 "required": []
833 }
834 },
835 {
836 "name": "memory_modalities",
837 "description": "Show multimodal index statistics",
838 "inputSchema": {
839 "type": "object",
840 "properties": {},
841 "required": []
842 }
843 },
844 {
845 "name": "memory_doctor",
846 "description": "Run integrity diagnostics and attempt automatic repair (crash recovery)",
847 "inputSchema": {
848 "type": "object",
849 "properties": {
850 "fix": { "type": "boolean", "description": "Attempt to fix common corruption issues", "default": false }
851 },
852 "required": []
853 }
854 },
855 {
856 "name": "memory_rebuild",
857 "description": "Rebuild the microscope index from layer source files (merges append log into main index)",
858 "inputSchema": {
859 "type": "object",
860 "properties": {},
861 "required": []
862 }
863 },
864 {
865 "name": "memory_store_data",
866 "description": "Store structured key-value data pairs",
867 "inputSchema": {
868 "type": "object",
869 "properties": {
870 "pairs": { "type": "object", "description": "Key-value pairs to store", "additionalProperties": { "type": "string" } },
871 "importance": { "type": "integer", "description": "Importance level 1-10", "default": 5 }
872 },
873 "required": ["pairs"]
874 }
875 },
876 {
877 "name": "memory_resonant",
878 "description": "Show most resonant blocks — strongest mirror neuron signal",
879 "inputSchema": {
880 "type": "object",
881 "properties": {
882 "k": { "type": "integer", "description": "Number of top resonant blocks", "default": 10 }
883 },
884 "required": []
885 }
886 },
887 {
888 "name": "memory_autonomous",
889 "description": "Run autonomous mode — the system runs itself: daydream, curiosity, monologue, reflect, narrative, dream",
890 "inputSchema": {
891 "type": "object",
892 "properties": {
893 "tts": { "type": "boolean", "description": "Enable TTS", "default": false },
894 "daemon": { "type": "boolean", "description": "Run as continuous loop", "default": false },
895 "interval": { "type": "integer", "description": "Cycle interval in seconds", "default": 30 },
896 "max_cycles": { "type": "integer", "description": "Maximum cycles (default: 1 in single mode, infinite in daemon)" }
897 },
898 "required": []
899 }
900 },
901 {
902 "name": "memory_consciousness",
903 "description": "Show live consciousness stream state — emotion, surprise, curiosity, predictions, all 13 layers in real-time",
904 "inputSchema": {
905 "type": "object",
906 "properties": {},
907 "required": []
908 }
909 },
910 {
911 "name": "memory_evidence_show",
912 "description": "Show evidence record: class, confidence, support/refute, distinct sources",
913 "inputSchema": {
914 "type": "object",
915 "properties": {
916 "hash_or_text": { "type": "string", "description": "Hash (16 hex) or text" }
917 },
918 "required": ["hash_or_text"]
919 }
920 },
921 {
922 "name": "memory_evidence_link",
923 "description": "Link an Observation/Evidence to a claim (increases confidence)",
924 "inputSchema": {
925 "type": "object",
926 "properties": {
927 "claim": { "type": "string" },
928 "support": { "type": "string" },
929 "source": { "type": "integer", "default": 0 },
930 "class": { "type": "string", "default": "observation" }
931 },
932 "required": ["claim", "support"]
933 }
934 },
935 {
936 "name": "memory_evidence_refute",
937 "description": "Record a refutation against a claim (decreases confidence)",
938 "inputSchema": {
939 "type": "object",
940 "properties": {
941 "claim": { "type": "string" },
942 "source": { "type": "integer", "default": 0 }
943 },
944 "required": ["claim"]
945 }
946 },
947 {
948 "name": "memory_evidence_audit",
949 "description": "Verify evidence audit chain integrity",
950 "inputSchema": { "type": "object", "properties": {}, "required": [] }
951 },
952 {
953 "name": "memory_evidence_gates",
954 "description": "Count promotions blocked by the epistemic gate",
955 "inputSchema": { "type": "object", "properties": {}, "required": [] }
956 }
957 ]
958 }
959 })
960}
961
962fn tool_status(config: &Config) -> Result<String, String> {
963 let reader = MicroscopeReader::open(config)?;
964
965 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
966 let appended = read_append_log(&append_path);
967
968 let mut depth_info = String::new();
969 for (d, &(_start, count)) in reader.depth_ranges.iter().enumerate() {
970 if count > 0 {
971 depth_info.push_str(&format!(" D{}: {} blocks\n", d, count));
972 }
973 }
974
975 let hdr_kb = (reader.block_count * crate::HEADER_SIZE) as f64 / 1024.0;
976 let data_kb = reader.data.len() as f64 / 1024.0;
977
978 let embedding_provider = &config.embedding.provider;
979 let use_gpu = config.embedding.use_gpu;
980 let gpu_status = if use_gpu {
981 "enabled (CUDA lazy)"
982 } else {
983 "disabled"
984 };
985
986 Ok(format!(
987 "Microscope Memory Status\n\
988 ========================\n\
989 Blocks: {}\n\
990 Headers: {:.1} KB\n\
991 Data: {:.1} KB\n\
992 Total: {:.1} KB\n\
993 Append log: {} entries\n\
994 \n\
995 Depth breakdown:\n\
996 {}\n\
997 Features:\n\
998 - Embedding provider: {}\n\
999 - GPU acceleration: {}\n\
1000 - Cosine echo filter: enabled (source_id + cosine similarity)",
1001 reader.block_count,
1002 hdr_kb,
1003 data_kb,
1004 hdr_kb + data_kb,
1005 appended.len(),
1006 depth_info,
1007 embedding_provider,
1008 gpu_status
1009 ))
1010}
1011
1012fn tool_store(config: &Config, args: &Value) -> Result<String, String> {
1013 let text = args
1014 .get("text")
1015 .and_then(|v: &Value| v.as_str())
1016 .ok_or("Missing required parameter: text")?;
1017 let layer = args
1018 .get("layer")
1019 .and_then(|v: &Value| v.as_str())
1020 .unwrap_or("long_term");
1021 let importance = args
1022 .get("importance")
1023 .and_then(|v: &Value| v.as_u64())
1024 .unwrap_or(5) as u8;
1025
1026 let emotion: Option<[f32; 21]> = args.get("emotion").and_then(|v: &Value| {
1028 let arr = v.as_array()?;
1029 if arr.len() != 21 {
1030 return None;
1031 }
1032 let mut emo = [0.0f32; 21];
1033 for (i, val) in arr.iter().enumerate() {
1034 emo[i] = val.as_f64().unwrap_or(0.0) as f32;
1035 }
1036 Some(emo)
1037 });
1038
1039 let sid = std::process::id();
1040 let tagged = format!("[sid-{:04}] {}", sid % 10000, text);
1041
1042 store_memory_with_emotion(config, &tagged, layer, importance, emotion)?;
1043
1044 let (x, y, z) = crate::content_coords(&tagged, layer);
1045 let depth = crate::auto_depth(&tagged);
1046
1047 Ok(format!(
1048 "Stored memory:\n\
1049 Layer: {}\n\
1050 Importance: {}\n\
1051 Depth: D{}\n\
1052 Position: ({:.3}, {:.3}, {:.3})\n\
1053 Session: sid-{:04}\n\
1054 Text: {}",
1055 layer,
1056 importance,
1057 depth,
1058 x,
1059 y,
1060 z,
1061 sid % 10000,
1062 crate::safe_truncate(text, 200)
1063 ))
1064}
1065
1066fn tool_recall(config: &Config, args: &Value) -> Result<String, String> {
1067 let query = args
1068 .get("query")
1069 .and_then(|v: &Value| v.as_str())
1070 .ok_or("Missing required parameter: query")?;
1071 let k = args.get("k").and_then(|v: &Value| v.as_u64()).unwrap_or(10) as usize;
1072
1073 let query_emotion: Option<[f32; 21]> = args.get("emotion").and_then(|v: &Value| {
1075 let arr = v.as_array()?;
1076 if arr.len() != 21 {
1077 return None;
1078 }
1079 let mut emo = [0.0f32; 21];
1080 for (i, val) in arr.iter().enumerate() {
1081 emo[i] = val.as_f64().unwrap_or(0.0) as f32;
1082 }
1083 Some(emo)
1084 });
1085 let emotional_recall_weight = config.search.emotional_bias_weight * 0.15;
1086
1087 crate::recall_ctx::with_shared(config, |ctx| {
1088 let reader = &ctx.reader;
1089 let output_dir = Path::new(&config.paths.output_dir);
1090
1091 let emotional_ring = crate::EmotionalStateRing::load_or_init(output_dir);
1093 let emotional_intensity = emotional_ring.intensity();
1094
1095 let query_emotion = query_emotion.or_else(|| {
1097 if emotional_ring.is_active() {
1098 if let Some((name, val)) = emotional_ring.dominant() {
1099 eprintln!(" [] emotional prime: {} ({:.2})", name, val);
1100 }
1101 Some(emotional_ring.current)
1102 } else {
1103 None
1104 }
1105 });
1106
1107 let (qx, qy, qz) =
1108 crate::content_coords_blended(query, "long_term", config.search.semantic_weight);
1109 let relevance_query = crate::relevance::RelevanceQuery::new(query);
1110
1111 let (mut attention, hebb_pre, tg_pre, pc_pre) = if let Some(stream) =
1112 crate::consciousness_stream::global_stream()
1113 {
1114 let s = crate::sync_guard::mutex_lock(stream);
1115 (
1116 s.attention.clone(),
1117 s.hebbian.clone(),
1118 s.thought_graph.clone(),
1119 s.predictive_cache.clone(),
1120 )
1121 } else {
1122 let attn = crate::attention::AttentionState::load_or_init(output_dir);
1123 let hebb = crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count);
1124 let tg = crate::thought_graph::ThoughtGraphState::load_or_init(output_dir);
1125 let pc = crate::predictive_cache::PredictiveCache::load_or_init(output_dir);
1126 (attn, hebb, tg, pc)
1127 };
1128
1129 let emotional_energy = crate::emotional::emotional_field(reader, &hebb_pre)
1130 .map(|f| f.total_energy)
1131 .unwrap_or(0.0);
1132
1133 if attention.total_recalls > 0 {
1134 let quality = attention.infer_quality();
1135 if let Some(last) = attention.history.last() {
1136 let prev_weights = last.weights;
1137 attention.record_outcome(quality, &prev_weights);
1138 }
1139 }
1140
1141 let attn_signals = crate::attention::AttentionSignals {
1142 query_length: query.len(),
1143 emotional_energy,
1144 emotional_intensity,
1145 session_depth: tg_pre.current_path().len(),
1146 pattern_confidence: 0.0,
1147 cache_hit_rate: pc_pre.stats.hit_rate(),
1148 archetype_match_score: 0.0,
1149 };
1150 let attn = attention.compute_attention(&attn_signals);
1151
1152 let emotional_weight = config.search.emotional_bias_weight * attn.weight(4);
1153 let (qx, qy, qz) =
1154 crate::emotional::apply_emotional_bias(qx, qy, qz, emotional_weight, reader, &hebb_pre);
1155
1156 let (zoom_lo, zoom_hi) = {
1160 let mut hi = 0u8;
1161 for (d, &(_, count)) in reader.depth_ranges.iter().enumerate() {
1162 if count > 0 {
1163 hi = d as u8;
1164 }
1165 }
1166 (0u8, hi)
1167 };
1168
1169 let mut semantic_hits: std::collections::HashMap<usize, f32> =
1174 std::collections::HashMap::new();
1175 let mut appended_sem: std::collections::HashMap<usize, f32> =
1178 std::collections::HashMap::new();
1179 {
1180 use crate::embedding_index::EmbeddingIndex;
1181
1182 let emb_path = output_dir.join("embeddings.bin");
1183 if let Some(eidx) = EmbeddingIndex::open(&emb_path) {
1184 let embedded =
1187 crate::embeddings::with_cached_provider(&config.embedding, eidx.dim(), |p| {
1188 p.embed(query)
1189 });
1190 match embedded {
1191 Ok(qe) if qe.len() == eidx.dim() => {
1192 for (sim, block_idx) in eidx.search(&qe, (k * 8).max(64)) {
1193 if block_idx < reader.block_count {
1194 semantic_hits.insert(block_idx, sim);
1195 }
1196 }
1197 if let Some(side) = crate::embedding_index::AppendEmbeddings::open(
1198 &output_dir.join(crate::embedding_index::APPEND_EMBEDDINGS_FILE),
1199 eidx.dim(),
1200 ) {
1201 for (sim, ai) in side.search(&qe, (k * 8).max(64)) {
1202 appended_sem.insert(ai as usize, sim);
1203 }
1204 }
1205 }
1206 Ok(_) => eprintln!(" semantic: provider width differs from index; skipped"),
1207 Err(e) => eprintln!(" semantic: embedding failed ({}); lexical path only", e),
1208 }
1209 }
1210 }
1211
1212 let emotion_lookup = query_emotion
1214 .as_ref()
1215 .and_then(|_| crate::load_emotion_lookup(output_dir));
1216
1217 let mut all_results: Vec<(f32, usize, bool)> = Vec::new();
1218
1219 for zoom in zoom_lo..=zoom_hi {
1220 let (start, count) = reader.depth_ranges[zoom as usize];
1221 let (start, count) = (start as usize, count as usize);
1222 for i in start..(start + count) {
1223 let text = reader.text(i);
1224 let lexical = relevance_query.lexical_score(text);
1225 let semantic = semantic_hits.get(&i).copied();
1228 if lexical > 0.0 || semantic.is_some() {
1229 let h = reader.header(i);
1230 let dx = h.x - qx;
1231 let dy = h.y - qy;
1232 let dz = h.z - qz;
1233 let spatial_dist = dx * dx + dy * dy + dz * dz;
1234 let emo_boost = query_emotion
1236 .as_ref()
1237 .and_then(|qe| {
1238 emotion_lookup
1239 .as_ref()
1240 .and_then(|lookup| lookup(i))
1241 .map(|block_emo| {
1242 crate::emotional_similarity(qe, &block_emo)
1243 * emotional_recall_weight
1244 })
1245 })
1246 .unwrap_or(0.0);
1247 let base = crate::relevance::rank_distance_from_score(
1248 lexical,
1249 spatial_dist,
1250 config.search.keyword_boost,
1251 h.importance,
1252 );
1253 let mut combined = crate::relevance::apply_boost(base, emo_boost);
1254 if let Some(sim) = semantic {
1255 let w = config.search.semantic_weight.clamp(0.0, 1.0);
1256 combined -= sim * w;
1257 }
1258 all_results.push((combined, i, true));
1259 }
1260 }
1261 }
1262
1263 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
1264 let appended = read_append_log(&append_path);
1265 for (ai, entry) in appended.iter().enumerate() {
1266 let dx = entry.x - qx;
1267 let dy = entry.y - qy;
1268 let dz = entry.z - qz;
1269 let dist = dx * dx + dy * dy + dz * dz;
1270 let lexical = relevance_query.lexical_score(&entry.text);
1271 let emo_boost = query_emotion
1273 .as_ref()
1274 .map(|qe| crate::emotional_similarity(qe, &entry.emotion) * emotional_recall_weight)
1275 .unwrap_or(0.0);
1276 if dist < 0.1 || lexical > 0.0 || emo_boost > 0.0 || appended_sem.contains_key(&ai) {
1277 let mut base = crate::relevance::rank_distance_from_score(
1278 lexical,
1279 dist,
1280 config.search.keyword_boost,
1281 entry.importance,
1282 );
1283 if let Some(sim) = appended_sem.get(&ai) {
1284 let w = config.search.semantic_weight.clamp(0.0, 1.0);
1285 base -= *sim * w;
1286 }
1287 let combined = crate::relevance::apply_boost(base, emo_boost);
1288 all_results.push((combined, ai + 1_000_000, false));
1289 }
1290 }
1291
1292 let link_table = crate::fingerprint::LinkTable::load_cached(output_dir);
1294 if let Some(ref lt) = link_table {
1295 all_results.sort_by(|a, b| a.0.total_cmp(&b.0));
1296 let top_n = all_results.len().min(3);
1297 for i in 0..top_n {
1298 let (_, idx, is_main) = all_results[i];
1299 let text = if is_main {
1300 reader.text(idx).to_string()
1301 } else {
1302 appended
1303 .get(idx - 1_000_000)
1304 .map(|e| e.text.clone())
1305 .unwrap_or_default()
1306 };
1307 let similar = lt.find_similar(&text, 5);
1308 for (linked_idx, sim) in &similar {
1309 let linked_idx = *linked_idx as usize;
1310 let found = all_results
1311 .iter()
1312 .any(|(_, ri, rim)| *rim && *ri == linked_idx);
1313 if !found {
1314 let boost = *sim * 0.12;
1315 all_results.push((boost, linked_idx, true));
1316 } else {
1317 for (dist, ri, rim) in &mut all_results {
1318 if *rim && *ri == linked_idx {
1319 *dist = crate::relevance::apply_boost(*dist, *sim * 0.08);
1320 break;
1321 }
1322 }
1323 }
1324 }
1325 }
1326 }
1327
1328 all_results.sort_by(|a, b| a.0.total_cmp(&b.0));
1329 let novel = all_results.first().is_none_or(|(d, _, _)| *d > 0.3);
1330
1331 let qh_tg = crate::hebbian::query_hash(query);
1332 if let Some(stream) = crate::consciousness_stream::global_stream() {
1333 crate::consciousness_stream::ConsciousnessStream::feed_query(stream, qh_tg);
1334 }
1335
1336 let (mut thought_graph, mut pred_cache) =
1337 if let Some(stream) = crate::consciousness_stream::global_stream() {
1338 let s = crate::sync_guard::mutex_lock(stream);
1339 (s.thought_graph.clone(), s.predictive_cache.clone())
1340 } else {
1341 let tg = crate::thought_graph::ThoughtGraphState::load_or_init(output_dir);
1342 let pc = crate::predictive_cache::PredictiveCache::load_or_init(output_dir);
1343 (tg, pc)
1344 };
1345
1346 if let Some((cached_blocks, confidence)) = pred_cache.check(qh_tg) {
1347 let boost = confidence * crate::thought_graph::PATTERN_BOOST_WEIGHT * attn.weight(6);
1348 let cached_set: HashSet<u32> = cached_blocks.iter().copied().collect();
1349 for (dist, idx, is_main) in &mut all_results {
1350 if *is_main && cached_set.contains(&(*idx as u32)) {
1351 *dist = crate::relevance::apply_boost(*dist, boost);
1352 }
1353 }
1354 }
1355
1356 let pattern_boosts: HashMap<u32, f32> =
1357 thought_graph.pattern_boost(qh_tg).into_iter().collect();
1358 if !pattern_boosts.is_empty() {
1359 let tg_scale = attn.weight(5);
1360 for (dist, idx, is_main) in &mut all_results {
1361 if *is_main {
1362 if let Some(&boost) = pattern_boosts.get(&(*idx as u32)) {
1363 *dist = crate::relevance::apply_boost(*dist, boost * tg_scale);
1364 }
1365 }
1366 }
1367 }
1368
1369 all_results.sort_by(|a, b| a.0.total_cmp(&b.0));
1370
1371 let mut output = format!("Recall '{}' (zoom D{}..D{})", query, zoom_lo, zoom_hi);
1372 let evidence_ledger =
1374 crate::epistemic::EvidenceLedger::load_or_init(Path::new(&config.paths.output_dir));
1375 if novel {
1376 output.push_str(" [NOVEL TOPIC — low prior memory]");
1377 }
1378 output.push_str(":\n\n");
1379 let mut seen = HashSet::new();
1380 let mut shown = 0;
1381
1382 for (dist, idx, is_main) in &all_results {
1383 if shown >= k {
1384 break;
1385 }
1386 if !seen.insert((*idx, *is_main)) {
1387 continue;
1388 }
1389 if *is_main {
1390 let h = reader.header(*idx);
1391 let text = reader.text(*idx);
1392 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
1393 let class = crate::epistemic::class_of_flags(h.flags);
1394 let ch = crate::epistemic::content_hash(text.trim());
1395 let conf = evidence_ledger
1396 .records
1397 .get(&ch)
1398 .map(|r| r.confidence)
1399 .unwrap_or(0);
1400 output.push_str(&format!(
1401 "[D{} {} dist={:.3} {} c={}] {}\n",
1402 h.depth,
1403 layer,
1404 dist,
1405 class,
1406 conf,
1407 crate::safe_truncate(text, 150)
1408 ));
1409 } else {
1410 let ai = idx - 1_000_000;
1411 if let Some(entry) = appended.get(ai) {
1412 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
1413 output.push_str(&format!(
1414 "[APPEND {} dist={:.3}] {}\n",
1415 layer,
1416 dist,
1417 crate::safe_truncate(&entry.text, 150)
1418 ));
1419 }
1420 }
1421 shown += 1;
1422 }
1423
1424 let activated: Vec<(u32, f32)> = all_results
1425 .iter()
1426 .filter(|(_, _, is_main)| *is_main)
1427 .take(k)
1428 .map(|(score, idx, _)| (*idx as u32, *score))
1429 .collect();
1430
1431 if !activated.is_empty() {
1432 let mut hebb =
1433 crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count);
1434 let mut mirror = crate::mirror::MirrorState::load_or_init(output_dir);
1435 let qh = crate::hebbian::query_hash(query);
1436
1437 let _ = crate::mirror::mirror_boost(&hebb, &mut mirror, &activated, qh);
1438 hebb.record_activation(&activated, qh);
1439
1440 let mut resonance = crate::resonance::ResonanceState::load_or_init(output_dir);
1441 let headers: Vec<(f32, f32, f32)> = activated
1442 .iter()
1443 .map(|&(idx, _)| {
1444 let h = reader.header(idx as usize);
1445 (h.x, h.y, h.z)
1446 })
1447 .collect();
1448 resonance.emit_pulse(&activated, qh, &headers, 1);
1449
1450 let mut archetypes = crate::archetype::ArchetypeState::load_or_init(output_dir);
1451 let mut temporal =
1452 crate::temporal_archetype::TemporalArchetypeState::load_or_init(output_dir);
1453 let _ = archetypes.match_archetype(&activated);
1454 temporal.decay();
1455 archetypes.reinforce(&activated);
1456
1457 let dominant_layer = activated
1458 .first()
1459 .map(|&(idx, _)| reader.header(idx as usize).layer_id)
1460 .unwrap_or(0);
1461 thought_graph.record_recall(qh, &activated, dominant_layer);
1462 let result_block_ids: Vec<u32> = activated.iter().map(|&(idx, _)| idx).collect();
1463 thought_graph.update_pattern_blocks(qh, &result_block_ids);
1464 thought_graph.detect_patterns();
1465
1466 let _ = pred_cache.evaluate(qh, &result_block_ids, &mut thought_graph);
1467 pred_cache.predict_next(&thought_graph);
1468
1469 attention.mark_recall();
1470
1471 let mut links: Vec<String> = Vec::new();
1480 for (i, &(_, idx_a, is_a)) in all_results.iter().take(3).enumerate() {
1481 for &(_, idx_b, is_b) in all_results.iter().take(5).skip(i + 1) {
1482 let text_a = if is_a { reader.text(idx_a) } else { "" };
1483 let text_b = if is_b { reader.text(idx_b) } else { "" };
1484 if !text_a.is_empty() && !text_b.is_empty() {
1485 links.push(format!(
1486 "LINK: [{:.40}] <-> [{:.40}] via '{}'",
1487 text_a, text_b, query
1488 ));
1489 }
1490 }
1491 }
1492 if !links.is_empty() && !crate::no_learn::enabled() {
1497 let dim = config.embedding.dim;
1498 let vectors: Vec<Result<Vec<f32>, crate::embeddings::EmbeddingError>> =
1501 crate::embeddings::with_cached_provider(&config.embedding, dim, |p| {
1502 use crate::parallel::*;
1503 links
1504 .par_iter()
1505 .map(|t| p.embed(t))
1506 .collect::<Vec<Result<Vec<f32>, _>>>()
1507 });
1508 for (n, link) in links.iter().enumerate() {
1509 let precomputed = vectors
1512 .get(n)
1513 .and_then(|v| v.as_ref().ok())
1514 .filter(|v| v.len() == dim)
1515 .cloned();
1516 let _ =
1517 store_memory_with_embedding(config, link, "associative", 6, precomputed);
1518 }
1519 }
1520
1521 let _ = hebb.save(output_dir);
1522 let _ = mirror.save(output_dir);
1523 let _ = resonance.save(output_dir);
1524 let _ = archetypes.save(output_dir);
1525 let _ = temporal.save(output_dir);
1526 let _ = thought_graph.save(output_dir);
1527 let _ = pred_cache.save(output_dir);
1528 let _ = attention.save(output_dir);
1529
1530 if let Some(stream) = crate::consciousness_stream::global_stream() {
1531 let mut s = crate::sync_guard::mutex_lock(stream);
1532 s.hebbian = hebb;
1533 s.mirror = mirror;
1534 s.resonance = resonance;
1535 s.archetypes = archetypes;
1536 s.thought_graph = thought_graph;
1537 s.predictive_cache = pred_cache;
1538 s.attention = attention;
1539 }
1540 }
1541
1542 output.push_str(&format!("\n{} results", shown));
1543 Ok(output)
1544 })?
1545}
1546
1547fn tool_evidence_show(config: &Config, args: &Value) -> Result<String, String> {
1550 let hash_or_text = args
1551 .get("hash_or_text")
1552 .and_then(|v| v.as_str())
1553 .ok_or("Missing required parameter: hash_or_text")?;
1554 let output_dir = std::path::Path::new(&config.paths.output_dir);
1555 let ledger = crate::epistemic::EvidenceLedger::load_or_init(output_dir);
1556 let ch = if hash_or_text.len() == 16 && hash_or_text.chars().all(|c| c.is_ascii_hexdigit()) {
1557 u64::from_str_radix(hash_or_text, 16).map_err(|e| format!("invalid hex: {e}"))?
1558 } else {
1559 crate::epistemic::content_hash(hash_or_text)
1560 };
1561 match ledger.records.get(&ch) {
1562 Some(rec) => Ok(serde_json::json!({
1563 "content_hash": format!("{:016x}", rec.content_hash),
1564 "class": rec.class.to_string(),
1565 "source_id": format!("{:016x}", rec.source_id),
1566 "support_count": rec.support_count,
1567 "refute_count": rec.refute_count,
1568 "distinct_sources": rec.distinct_sources,
1569 "confidence": rec.confidence,
1570 "first_seen_ms": rec.first_seen_ms,
1571 })
1572 .to_string()),
1573 None => Ok(format!(
1574 "{{\"error\": \"no evidence record for hash {:016x}\"}}",
1575 ch
1576 )),
1577 }
1578}
1579
1580fn tool_evidence_link(config: &Config, args: &Value) -> Result<String, String> {
1581 let claim_text = args
1582 .get("claim")
1583 .and_then(|v| v.as_str())
1584 .ok_or("Missing: claim")?;
1585 let support_text = args
1586 .get("support")
1587 .and_then(|v| v.as_str())
1588 .ok_or("Missing: support")?;
1589 let source = args.get("source").and_then(|v| v.as_u64()).unwrap_or(0);
1590 let class_str = args
1591 .get("class")
1592 .and_then(|v| v.as_str())
1593 .unwrap_or("observation");
1594 let class: crate::epistemic::EpistemicClass = class_str.parse().map_err(|e: String| e)?;
1595 let output_dir = std::path::Path::new(&config.paths.output_dir);
1596 let now = std::time::SystemTime::now()
1597 .duration_since(std::time::UNIX_EPOCH)
1598 .unwrap_or_default()
1599 .as_millis() as u64;
1600 let mut ledger = crate::epistemic::EvidenceLedger::load_or_init(output_dir);
1601 let mut audit = crate::epistemic::AuditChain::load_or_init(output_dir);
1602 let claim_ch = if claim_text.len() == 16 && claim_text.chars().all(|c| c.is_ascii_hexdigit()) {
1603 u64::from_str_radix(claim_text, 16).map_err(|e| format!("invalid hex: {e}"))?
1604 } else {
1605 crate::epistemic::content_hash(claim_text)
1606 };
1607 let support_ch =
1608 if support_text.len() == 16 && support_text.chars().all(|c| c.is_ascii_hexdigit()) {
1609 u64::from_str_radix(support_text, 16).map_err(|e| format!("invalid hex: {e}"))?
1610 } else {
1611 crate::epistemic::content_hash(support_text)
1612 };
1613 let provider = crate::embeddings::provider_from_config(&config.embedding, config.embedding.dim);
1614 crate::epistemic::link_evidence(
1615 &mut ledger,
1616 &mut audit,
1617 claim_ch,
1618 support_ch,
1619 class,
1620 source,
1621 now,
1622 Some(support_text),
1623 Some(claim_text),
1624 Some(provider.as_ref()),
1625 )?;
1626 ledger.save(output_dir)?;
1627 audit.save(output_dir)?;
1628 let conf = ledger
1629 .records
1630 .get(&claim_ch)
1631 .map(|r| r.confidence)
1632 .unwrap_or(0);
1633 Ok(serde_json::json!({"status":"linked","confidence":conf}).to_string())
1634}
1635
1636fn tool_evidence_refute(config: &Config, args: &Value) -> Result<String, String> {
1637 let claim_text = args
1638 .get("claim")
1639 .and_then(|v| v.as_str())
1640 .ok_or("Missing: claim")?;
1641 let source = args.get("source").and_then(|v| v.as_u64()).unwrap_or(0);
1642 let output_dir = std::path::Path::new(&config.paths.output_dir);
1643 let now = std::time::SystemTime::now()
1644 .duration_since(std::time::UNIX_EPOCH)
1645 .unwrap_or_default()
1646 .as_millis() as u64;
1647 let mut ledger = crate::epistemic::EvidenceLedger::load_or_init(output_dir);
1648 let mut audit = crate::epistemic::AuditChain::load_or_init(output_dir);
1649 let claim_ch = if claim_text.len() == 16 && claim_text.chars().all(|c| c.is_ascii_hexdigit()) {
1650 u64::from_str_radix(claim_text, 16).map_err(|e| format!("invalid hex: {e}"))?
1651 } else {
1652 crate::epistemic::content_hash(claim_text)
1653 };
1654 crate::epistemic::refute(&mut ledger, &mut audit, claim_ch, source, now)?;
1655 ledger.save(output_dir)?;
1656 audit.save(output_dir)?;
1657 let conf = ledger
1658 .records
1659 .get(&claim_ch)
1660 .map(|r| r.confidence)
1661 .unwrap_or(0);
1662 Ok(serde_json::json!({"status":"refuted","confidence":conf}).to_string())
1663}
1664
1665fn tool_evidence_audit(config: &Config, _args: &Value) -> Result<String, String> {
1666 let output_dir = std::path::Path::new(&config.paths.output_dir);
1667 let chain = crate::epistemic::AuditChain::load_or_init(output_dir);
1668 let result = chain.verify();
1669 let events: Vec<serde_json::Value> = chain
1670 .chunks
1671 .iter()
1672 .skip(1)
1673 .map(|c| {
1674 serde_json::json!({
1675 "event": format!("{:?}", c.record.event),
1676 "content_hash": format!("{:016x}", c.record.content_hash),
1677 "source_id": format!("{:016x}", c.record.source_id),
1678 "delta": c.record.delta,
1679 "note": c.record.note,
1680 })
1681 })
1682 .collect();
1683 Ok(serde_json::json!({
1684 "total_chunks": chain.chunks.len(),
1685 "integrity": if result.is_ok() { "OK" } else { "FAIL" },
1686 "events": events,
1687 })
1688 .to_string())
1689}
1690
1691fn tool_evidence_gates(config: &Config, _args: &Value) -> Result<String, String> {
1692 let output_dir = std::path::Path::new(&config.paths.output_dir);
1693 let chain = crate::epistemic::AuditChain::load_or_init(output_dir);
1694 let gates: Vec<serde_json::Value> = chain
1695 .chunks
1696 .iter()
1697 .filter(|c| c.record.event == crate::epistemic::AuditEvent::PromoGate)
1698 .map(|c| {
1699 serde_json::json!({
1700 "content_hash": format!("{:016x}", c.record.content_hash),
1701 "note": c.record.note,
1702 })
1703 })
1704 .collect();
1705 Ok(serde_json::json!({
1706 "blocked_count": gates.len(),
1707 "gates": gates,
1708 })
1709 .to_string())
1710}
1711
1712fn tool_find(config: &Config, args: &Value) -> Result<String, String> {
1713 let query = args
1714 .get("query")
1715 .and_then(|v: &Value| v.as_str())
1716 .ok_or("Missing required parameter: query")?;
1717 let k = args.get("k").and_then(|v: &Value| v.as_u64()).unwrap_or(10) as usize;
1718
1719 crate::recall_ctx::with_shared(config, |ctx| {
1720 let reader = &ctx.reader;
1721 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
1722 let appended = crate::read_append_log(&append_path);
1723 let results = reader.find_text_all(config, query, k);
1724
1725 if results.is_empty() {
1726 return Ok(format!("No results for '{}'", query));
1727 }
1728
1729 let mut output = format!("Text search '{}': {} results\n\n", query, results.len());
1730 for (depth, idx, is_main) in &results {
1731 if *is_main {
1732 let h = reader.header(*idx);
1733 let text = reader.text(*idx);
1734 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
1735 output.push_str(&format!(
1736 "[D{} {}] {}\n",
1737 h.depth,
1738 layer,
1739 crate::safe_truncate(text, 150)
1740 ));
1741 } else if let Some(entry) = appended.get(idx.saturating_sub(1_000_000)) {
1742 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
1743 output.push_str(&format!(
1744 "[D{} {}/new] {}\n",
1745 depth,
1746 layer,
1747 crate::safe_truncate(&entry.text, 150)
1748 ));
1749 }
1750 }
1751
1752 Ok(output)
1753 })?
1754}
1755
1756fn tool_mql_query(config: &Config, args: &Value) -> Result<String, String> {
1757 let mql = args
1758 .get("mql")
1759 .and_then(|v: &Value| v.as_str())
1760 .ok_or("Missing required parameter: mql")?;
1761
1762 let reader = MicroscopeReader::open(config)?;
1763 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
1764 let appended = read_append_log(&append_path);
1765
1766 let q = crate::query::parse(mql);
1767 let results = crate::query::execute(&q, &reader, &appended);
1768
1769 if results.is_empty() {
1770 return Ok(format!("MQL '{}': no results", mql));
1771 }
1772
1773 let mut output = format!("MQL '{}': {} results\n\n", mql, results.len());
1774 for r in &results {
1775 if r.is_main {
1776 let h = reader.header(r.block_idx);
1777 let text = reader.text(r.block_idx);
1778 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
1779 output.push_str(&format!(
1780 "[D{} {} score={:.3}] {}\n",
1781 h.depth,
1782 layer,
1783 r.score,
1784 crate::safe_truncate(text, 150)
1785 ));
1786 } else if let Some(entry) = appended.get(r.block_idx) {
1787 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
1788 output.push_str(&format!(
1789 "[APPEND {} score={:.3}] {}\n",
1790 layer,
1791 r.score,
1792 crate::safe_truncate(&entry.text, 150)
1793 ));
1794 }
1795 }
1796
1797 Ok(output)
1798}
1799
1800fn tool_build(config: &Config, args: &Value) -> Result<String, String> {
1801 let force = args
1802 .get("force")
1803 .and_then(|v: &Value| v.as_bool())
1804 .unwrap_or(false);
1805
1806 crate::build::build(config, force, true)?;
1807
1808 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
1810 let _ = std::fs::remove_file(append_path);
1811 let emotions_path = Path::new(&config.paths.output_dir).join("emotions.bin");
1812 let _ = std::fs::remove_file(emotions_path);
1813
1814 let reader = MicroscopeReader::open(config)?;
1815 Ok(format!(
1816 "Build complete: {} blocks across {} depths\nAppend log cleared.",
1817 reader.block_count,
1818 reader.depth_ranges.iter().filter(|&&(_, c)| c > 0).count()
1819 ))
1820}
1821
1822fn tool_look(config: &Config, args: &Value) -> Result<String, String> {
1823 let x = args
1824 .get("x")
1825 .and_then(|v: &Value| v.as_f64())
1826 .ok_or("Missing required parameter: x")? as f32;
1827 let y = args
1828 .get("y")
1829 .and_then(|v: &Value| v.as_f64())
1830 .ok_or("Missing required parameter: y")? as f32;
1831 let z = args
1832 .get("z")
1833 .and_then(|v: &Value| v.as_f64())
1834 .ok_or("Missing required parameter: z")? as f32;
1835 let zoom = args
1836 .get("zoom")
1837 .and_then(|v: &Value| v.as_u64())
1838 .ok_or("Missing required parameter: zoom")? as u8;
1839 let k = args.get("k").and_then(|v: &Value| v.as_u64()).unwrap_or(10) as usize;
1840
1841 let reader = MicroscopeReader::open(config)?;
1842 let config_clone = config.clone();
1843 let results = reader.look(&config_clone, x, y, z, zoom, k);
1844
1845 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
1846 let appended = read_append_log(&append_path);
1847
1848 if results.is_empty() {
1849 return Ok(format!(
1850 "Look ({:.2},{:.2},{:.2}) zoom={}: no results",
1851 x, y, z, zoom
1852 ));
1853 }
1854
1855 let mut output = format!(
1856 "Look ({:.2},{:.2},{:.2}) zoom={}: {} results\n\n",
1857 x,
1858 y,
1859 z,
1860 zoom,
1861 results.len()
1862 );
1863
1864 for (dist, idx, is_main) in &results {
1865 if *is_main {
1866 let h = reader.header(*idx);
1867 let text = reader.text(*idx);
1868 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
1869 output.push_str(&format!(
1870 "[D{} {} dist={:.3}] {}\n",
1871 h.depth,
1872 layer,
1873 dist,
1874 crate::safe_truncate(text, 150)
1875 ));
1876 } else if let Some(entry) = appended.get(*idx) {
1877 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
1878 output.push_str(&format!(
1879 "[APPEND {} dist={:.3}] {}\n",
1880 layer,
1881 dist,
1882 crate::safe_truncate(&entry.text, 150)
1883 ));
1884 }
1885 }
1886
1887 Ok(output)
1888}
1889
1890fn tool_session_log(config: &Config, args: &Value) -> Result<String, String> {
1891 let n = args.get("n").and_then(|v: &Value| v.as_u64()).unwrap_or(50) as usize;
1892 let file_path = Path::new(&config.paths.layers_dir).join("session.txt");
1893
1894 let content = if file_path.exists() {
1895 std::fs::read_to_string(&file_path).unwrap_or_default()
1896 } else {
1897 return Ok("Session memory is empty. Store interactions with layer=session.".to_string());
1898 };
1899
1900 let entries: Vec<&str> = content
1901 .split("\n\n")
1902 .filter(|s| !s.trim().is_empty())
1903 .collect();
1904
1905 let total = entries.len();
1906 let start = total.saturating_sub(n);
1907 let recent: Vec<&&str> = entries[start..].iter().rev().collect();
1908
1909 let now_secs = std::time::SystemTime::now()
1910 .duration_since(std::time::UNIX_EPOCH)
1911 .unwrap_or_default()
1912 .as_secs();
1913
1914 let mut output = format!(
1915 "Session Memory — {} total interactions, showing last {}:\n\n",
1916 total,
1917 recent.len()
1918 );
1919 for (i, entry) in recent.iter().enumerate() {
1920 let num = total - start - i;
1921 let decay = ebbinghaus_decay(entry, now_secs);
1922 output.push_str(&format!(
1923 "{} {}| {}\n",
1924 num,
1925 decay,
1926 crate::safe_truncate(entry, 300)
1927 ));
1928 }
1929
1930 Ok(output)
1931}
1932
1933fn ebbinghaus_decay(entry: &str, now_secs: u64) -> &'static str {
1934 let ts_str = if entry.starts_with('[') {
1935 entry
1936 .split(']')
1937 .next()
1938 .unwrap_or("")
1939 .trim_start_matches('[')
1940 } else {
1941 return "█ forgotten";
1942 };
1943 if ts_str.len() < 16 {
1944 return "█ forgotten";
1945 }
1946 let parts: Vec<&str> = ts_str.split(&['-', ' ', ':']).collect();
1947 if parts.len() < 5 {
1948 return "█ forgotten";
1949 }
1950 let y: u64 = parts[0].parse().unwrap_or(0);
1951 let mo: u64 = parts[1].parse().unwrap_or(0);
1952 let d: u64 = parts[2].parse().unwrap_or(0);
1953 let h: u64 = parts[3].parse().unwrap_or(0);
1954 let m: u64 = parts[4].parse().unwrap_or(0);
1955 if y < 2020 || mo == 0 || d == 0 {
1956 return "█ forgotten";
1957 }
1958 let mut days = 0u64;
1959 for yr in 1970..y {
1960 days += if is_leap_yr(yr) { 366 } else { 365 };
1961 }
1962 let leap = is_leap_yr(y);
1963 let mdays = [
1964 31,
1965 if leap { 29 } else { 28 },
1966 31,
1967 30,
1968 31,
1969 30,
1970 31,
1971 31,
1972 30,
1973 31,
1974 30,
1975 31,
1976 ];
1977 for &d in mdays.iter().take((mo - 1) as usize) {
1978 days += d as u64;
1979 }
1980 days += d - 1;
1981 let entry_secs = days * 86400 + h * 3600 + m * 60;
1982 let age_hours = if now_secs > entry_secs {
1983 (now_secs - entry_secs) / 3600
1984 } else {
1985 0
1986 };
1987
1988 if age_hours < 1 {
1989 "░ FRESH"
1990 } else if age_hours < 24 {
1991 "░ recent"
1992 } else if age_hours < 72 {
1993 "▒ fading"
1994 } else if age_hours < 168 {
1995 "▓ old"
1996 } else {
1997 "█ forgotten"
1998 }
1999}
2000
2001fn is_leap_yr(y: u64) -> bool {
2002 (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400)
2003}
2004
2005fn tool_consolidate(config: &Config, _args: &Value) -> Result<String, String> {
2006 let file_path = Path::new(&config.paths.layers_dir).join("session.txt");
2007 let content = if file_path.exists() {
2008 std::fs::read_to_string(&file_path).unwrap_or_default()
2009 } else {
2010 return Ok("Session memory is empty.".to_string());
2011 };
2012 let entries: Vec<&str> = content
2013 .split("\n\n")
2014 .filter(|s| !s.trim().is_empty())
2015 .collect();
2016 if entries.len() < 3 {
2017 return Ok("Not enough entries to consolidate (need 3+).".to_string());
2018 }
2019
2020 let mut groups: HashMap<String, Vec<String>> = HashMap::new();
2021 for entry in &entries {
2022 let sid = if entry.contains("[sid-") {
2023 entry
2024 .split("[sid-")
2025 .nth(1)
2026 .and_then(|s| s.split(']').next())
2027 .map(|s| format!("sid-{}", s))
2028 .unwrap_or_else(|| "nosid".to_string())
2029 } else {
2030 "nosid".to_string()
2031 };
2032 groups.entry(sid).or_default().push(entry.to_string());
2033 }
2034
2035 let mut summaries = Vec::new();
2036 for (sid, group) in &groups {
2037 if group.len() < 2 {
2038 continue;
2039 }
2040 let top_topics: Vec<String> = group
2041 .iter()
2042 .take(5)
2043 .map(|e| {
2044 let parts: Vec<&str> = e.split("] ").collect();
2045 crate::safe_truncate(parts.last().unwrap_or(&""), 50)
2046 })
2047 .collect();
2048
2049 let summary = format!(
2050 "[{}] CONSOLIDATED: {} interactions. Topics: {}",
2051 sid,
2052 group.len(),
2053 top_topics.join(" | ")
2054 );
2055 summaries.push(summary);
2056
2057 store_memory(
2058 config,
2059 &format!(
2060 "[{}] CONSOLIDATED: {} interactions from {}",
2061 sid,
2062 group.len(),
2063 top_topics.join(", ")
2064 ),
2065 "long_term",
2066 8,
2067 )?;
2068 }
2069
2070 let mut output = format!("Consolidated {} session groups:\n\n", summaries.len());
2071 for s in &summaries {
2072 output.push_str(&format!(" {}\n", s));
2073 }
2074 Ok(output)
2075}
2076
2077fn tool_dream(config: &Config, _args: &Value) -> Result<String, String> {
2078 let output_dir = Path::new(&config.paths.output_dir);
2079 let reader = MicroscopeReader::open(config)?;
2080 let block_count = reader.block_count;
2081 drop(reader);
2082
2083 match crate::dream::dream_consolidate(
2084 output_dir,
2085 block_count,
2086 config.index.max_blocks,
2087 config.index.protect_min_importance,
2088 ) {
2089 Ok(cycle) => Ok(format!(
2090 "Dream consolidation complete:\n\
2091 Duration: {}ms\n\
2092 Replayed fingerprints: {}\n\
2093 Strengthened pairs: {}\n\
2094 Pruned pairs: {}\n\
2095 Pruned activations: {}\n\
2096 Consolidated patterns: {}\n\
2097 Energy: {:.3} -> {:.3}",
2098 cycle.duration_ms,
2099 cycle.replayed_fingerprints,
2100 cycle.strengthened_pairs,
2101 cycle.pruned_pairs,
2102 cycle.pruned_activations,
2103 cycle.consolidated_patterns,
2104 cycle.energy_before,
2105 cycle.energy_after,
2106 )),
2107 Err(e) => Err(format!("Dream consolidation failed: {}", e)),
2108 }
2109}
2110
2111fn tool_session_context(config: &Config, args: &Value) -> Result<String, String> {
2114 let context = args
2115 .get("context")
2116 .and_then(|v: &Value| v.as_str())
2117 .ok_or("Missing required parameter: context")?;
2118 let summary = args
2119 .get("summary")
2120 .and_then(|v: &Value| v.as_str())
2121 .unwrap_or("");
2122
2123 let tagged = format!("[SESSION_CONTEXT] {}", context);
2125 store_memory(config, &tagged, "session", 7)?;
2126
2127 if !summary.is_empty() {
2129 let summary_tagged = format!("[SESSION_SUMMARY] {}", summary);
2130 store_memory(config, &summary_tagged, "long_term", 6)?;
2131 }
2132
2133 Ok(format!(
2134 "Session context stored:\n Layer: session\n Importance: 7\n Context length: {} chars\n Summary: {}",
2135 context.len(),
2136 if summary.is_empty() { "(none)" } else { summary }
2137 ))
2138}
2139
2140fn tool_consciousness(config: &Config, _args: &Value) -> Result<String, String> {
2143 use crate::consciousness_stream::{global_stream, ConsciousnessStream};
2144
2145 let state = match global_stream() {
2146 Some(s) => s.clone(),
2147 None => ConsciousnessStream::start(config),
2148 };
2149
2150 let snapshot = {
2155 let s = crate::sync_guard::mutex_lock(&state);
2156 s.snapshot.clone()
2157 };
2158 Ok(snapshot.read_cached_format())
2159}
2160
2161fn tool_ping(config: &Config, args: &Value) -> Result<String, String> {
2164 let query = args
2165 .get("query")
2166 .and_then(|v: &Value| v.as_str())
2167 .ok_or("Missing required parameter: query")?;
2168
2169 let reader = MicroscopeReader::open(config)?;
2171 let output_dir = Path::new(&config.paths.output_dir);
2172 let append_path = output_dir.join("append.bin");
2173 let appended = read_append_log(&append_path);
2174
2175 let (qx, qy, qz) =
2176 crate::content_coords_blended(query, "long_term", config.search.semantic_weight);
2177
2178 let mut all_results: Vec<(f32, usize)> = Vec::new();
2179
2180 let scan_limit = reader.block_count.min(500);
2182 for i in 0..scan_limit {
2183 let h = reader.header(i);
2184 let dx = qx - h.x;
2185 let dy = qy - h.y;
2186 let dz = qz - h.z;
2187 let dist = dx * dx + dy * dy + dz * dz;
2188 if dist < 0.05 {
2189 all_results.push((dist, i));
2190 }
2191 }
2192
2193 for (ai, entry) in appended.iter().enumerate() {
2195 let dx = qx - entry.x;
2196 let dy = qy - entry.y;
2197 let dz = qz - entry.z;
2198 let dist = dx * dx + dy * dy + dz * dz;
2199 if dist < 0.05 {
2200 all_results.push((dist, 1_000_000 + ai));
2201 }
2202 }
2203
2204 all_results.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
2205
2206 let mut output = format!("PING '{}':\n", query);
2207 let mut shown = 0;
2208 for (dist, idx) in all_results.iter().take(5) {
2209 let text = if *idx < 1_000_000 {
2210 reader.text(*idx).to_string()
2211 } else {
2212 appended
2213 .get(*idx - 1_000_000)
2214 .map(|e| e.text.clone())
2215 .unwrap_or_default()
2216 };
2217 if !text.is_empty() {
2218 output.push_str(&format!(
2219 " [{:.3}] {}\n",
2220 dist,
2221 crate::safe_truncate(&text, 120)
2222 ));
2223 shown += 1;
2224 }
2225 }
2226
2227 if shown == 0 {
2228 output.push_str(" (no close matches)\n");
2229 }
2230
2231 Ok(output)
2232}
2233
2234fn tool_auto_context(config: &Config, _args: &Value) -> Result<String, String> {
2237 let reader = MicroscopeReader::open(config)?;
2238 let output_dir = Path::new(&config.paths.output_dir);
2239 let ctx = crate::auto_context::build(output_dir, &reader);
2240 Ok(crate::auto_context::render(&ctx))
2241}
2242
2243fn tool_timeline(config: &Config, args: &Value) -> Result<String, String> {
2246 let window = args
2247 .get("window")
2248 .and_then(|v| v.as_str())
2249 .unwrap_or("last_session");
2250 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
2251
2252 let path = Path::new(&config.paths.output_dir).join("timeline.bin");
2253 let entries = crate::timeline::read_all(&path);
2254 let w = crate::timeline::TimeWindow::parse(window)
2255 .map_err(|e| format!("Invalid window '{}': {}", window, e))?;
2256 let filtered = crate::timeline::filter(&entries, &w);
2257 let mut rev: Vec<&crate::timeline::TimelineEntry> = filtered.iter().rev().collect();
2258 rev.truncate(k);
2259
2260 if rev.is_empty() {
2261 return Ok(format!("Timeline [{}]: no entries found", window));
2262 }
2263
2264 let mut output = format!("Timeline [{}] — {} entries:\n\n", window, rev.len());
2265 for e in &rev {
2266 let layer_name = crate::LAYER_NAMES.get(e.layer_id as usize).unwrap_or(&"?");
2267 let status_label = match e.status {
2268 crate::timeline::STATUS_OPEN => "OPEN",
2269 crate::timeline::STATUS_RESOLVED => "RESOLVED",
2270 crate::timeline::STATUS_ARCHIVED => "ARCHIVED",
2271 _ => "",
2272 };
2273 output.push_str(&format!(
2274 "{} D{} [{}] imp={}{} {}\n",
2275 crate::timeline::format_ts(e.ts_ms),
2276 e.depth,
2277 layer_name,
2278 e.importance,
2279 if status_label.is_empty() {
2280 String::new()
2281 } else {
2282 format!(" [{}]", status_label)
2283 },
2284 crate::safe_truncate(&e.text, 100)
2285 ));
2286 }
2287 Ok(output)
2288}
2289
2290fn tool_loops(config: &Config, args: &Value) -> Result<String, String> {
2293 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
2294 let dir = Path::new(&config.paths.output_dir);
2295 let mut open = crate::open_loops::read_open(&dir.join("open_loops.bin"));
2296 open.truncate(k);
2297
2298 if open.is_empty() {
2299 return Ok("No open loops.".to_string());
2300 }
2301
2302 let mut output = format!("Open Loops ({}):\n\n", open.len());
2303 for e in &open {
2304 output.push_str(&format!(
2305 "#{} {} imp={} {}\n",
2306 e.id,
2307 crate::timeline::format_ts(e.ts_ms),
2308 e.importance,
2309 crate::safe_truncate(&e.text, 100)
2310 ));
2311 }
2312 Ok(output)
2313}
2314
2315fn tool_resolve_loop(config: &Config, args: &Value) -> Result<String, String> {
2318 let id = args
2319 .get("id")
2320 .and_then(|v| v.as_u64())
2321 .ok_or("Missing required parameter: id")?;
2322 let dir = Path::new(&config.paths.output_dir);
2323 match crate::open_loops::resolve(dir, id) {
2324 Ok(true) => Ok(format!("Loop #{} resolved.", id)),
2325 Ok(false) => Ok(format!("Loop #{} not found or already resolved.", id)),
2326 Err(e) => Err(format!("Error resolving loop #{}: {}", id, e)),
2327 }
2328}
2329
2330fn tool_radial(config: &Config, args: &Value) -> Result<String, String> {
2333 let x = args.get("x").and_then(|v| v.as_f64()).ok_or("Missing: x")? as f32;
2334 let y = args.get("y").and_then(|v| v.as_f64()).ok_or("Missing: y")? as f32;
2335 let z = args.get("z").and_then(|v| v.as_f64()).ok_or("Missing: z")? as f32;
2336 let depth = args
2337 .get("depth")
2338 .and_then(|v| v.as_u64())
2339 .ok_or("Missing: depth")? as u8;
2340 let radius = args.get("radius").and_then(|v| v.as_f64()).unwrap_or(0.1) as f32;
2341 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2342
2343 let reader = MicroscopeReader::open(config)?;
2344 let result_set = reader.radial_search(config, x, y, z, depth, radius, k);
2345 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
2346 let appended = read_append_log(&append_path);
2347
2348 let mut output = format!(
2349 "Radial ({:.2},{:.2},{:.2}) D{} r={:.3}:\n\n",
2350 x, y, z, depth, radius
2351 );
2352
2353 if let Some(ref primary) = result_set.primary {
2354 output.push_str("PRIMARY:\n");
2355 if primary.is_main {
2356 let h = reader.header(primary.block_idx);
2357 let text = reader.text(primary.block_idx);
2358 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2359 output.push_str(&format!(
2360 " [D{} {} dist={:.3}] {}\n",
2361 h.depth,
2362 layer,
2363 primary.dist_sq,
2364 crate::safe_truncate(text, 150)
2365 ));
2366 } else if let Some(entry) = appended.get(primary.block_idx) {
2367 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
2368 output.push_str(&format!(
2369 " [APPEND {} dist={:.3}] {}\n",
2370 layer,
2371 primary.dist_sq,
2372 crate::safe_truncate(&entry.text, 150)
2373 ));
2374 }
2375 }
2376
2377 if !result_set.neighbors.is_empty() {
2378 output.push_str(&format!("\nNeighbors ({}):\n", result_set.neighbors.len()));
2379 for n in &result_set.neighbors {
2380 if n.is_main {
2381 let h = reader.header(n.block_idx);
2382 let text = reader.text(n.block_idx);
2383 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2384 output.push_str(&format!(
2385 " [D{} {} dist={:.3} w={:.3}] {}\n",
2386 h.depth,
2387 layer,
2388 n.dist_sq,
2389 n.weight,
2390 crate::safe_truncate(text, 100)
2391 ));
2392 } else if let Some(entry) = appended.get(n.block_idx) {
2393 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
2394 output.push_str(&format!(
2395 " [APPEND {} dist={:.3}] {}\n",
2396 layer,
2397 n.dist_sq,
2398 crate::safe_truncate(&entry.text, 100)
2399 ));
2400 }
2401 }
2402 }
2403
2404 output.push_str(&format!(
2405 "\n{} within radius, {} shown",
2406 result_set.total_within_radius,
2407 result_set.all().len()
2408 ));
2409 Ok(output)
2410}
2411
2412fn tool_soft(config: &Config, args: &Value) -> Result<String, String> {
2415 let x = args.get("x").and_then(|v| v.as_f64()).ok_or("Missing: x")? as f32;
2416 let y = args.get("y").and_then(|v| v.as_f64()).ok_or("Missing: y")? as f32;
2417 let z = args.get("z").and_then(|v| v.as_f64()).ok_or("Missing: z")? as f32;
2418 let zoom = args
2419 .get("zoom")
2420 .and_then(|v| v.as_u64())
2421 .ok_or("Missing: zoom")? as u8;
2422 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2423
2424 let reader = MicroscopeReader::open(config)?;
2425 let config_clone = config.clone();
2426 let results = reader.look_soft(&config_clone, x, y, z, zoom, k, config.search.zoom_weight);
2427 let append_path = Path::new(&config.paths.output_dir).join("append.bin");
2428 let appended = read_append_log(&append_path);
2429
2430 if results.is_empty() {
2431 return Ok(format!(
2432 "Soft ({:.2},{:.2},{:.2}) zoom={}: no results",
2433 x, y, z, zoom
2434 ));
2435 }
2436
2437 let mut output = format!(
2438 "Soft 4D ({:.2},{:.2},{:.2}) zoom={}: {} results\n\n",
2439 x,
2440 y,
2441 z,
2442 zoom,
2443 results.len()
2444 );
2445 for (dist, idx, is_main) in &results {
2446 if *is_main {
2447 let h = reader.header(*idx);
2448 let text = reader.text(*idx);
2449 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2450 output.push_str(&format!(
2451 "[D{} {} dist={:.3}] {}\n",
2452 h.depth,
2453 layer,
2454 dist,
2455 crate::safe_truncate(text, 150)
2456 ));
2457 } else if let Some(entry) = appended.get(*idx) {
2458 let layer = LAYER_NAMES.get(entry.layer_id as usize).unwrap_or(&"?");
2459 output.push_str(&format!(
2460 "[APPEND {} dist={:.3}] {}\n",
2461 layer,
2462 dist,
2463 crate::safe_truncate(&entry.text, 150)
2464 ));
2465 }
2466 }
2467 Ok(output)
2468}
2469
2470fn tool_think(config: &Config, args: &Value) -> Result<String, String> {
2473 let query = args
2474 .get("query")
2475 .and_then(|v| v.as_str())
2476 .ok_or("Missing: query")?;
2477 let max_steps = args.get("max_steps").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
2478
2479 let reader = MicroscopeReader::open(config)?;
2480 let mut chain = crate::sequential_thinking::ThinkingChain::new(max_steps);
2481 chain.brainstorm(&reader, config, query);
2482
2483 let mut output = format!("Sequential Thinking: '{}' ({} steps)\n\n", query, max_steps);
2484 output.push_str(&format!("Topic: {}\n", query));
2487 output.push_str(&format!("Steps: {}\n", max_steps));
2488 output.push_str("Run the CLI 'microscope-mem think' for full formatted output.\n");
2489 Ok(output)
2490}
2491
2492fn tool_hebbian(config: &Config, _args: &Value) -> Result<String, String> {
2495 let reader = MicroscopeReader::open(config)?;
2496 let output_dir = Path::new(&config.paths.output_dir);
2497 let hebb = crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count);
2498 let stats = hebb.stats();
2499
2500 let mut output = format!(
2501 "Hebbian State:\n\
2502 ==============\n\
2503 Blocks: {}\n\
2504 Active blocks: {}\n\
2505 Total activations: {}\n\
2506 Hot blocks (>0.1): {}\n\
2507 Drifted blocks: {}\n\
2508 Co-activation pairs: {}\n\
2509 Fingerprints: {}\n",
2510 stats.block_count,
2511 stats.active_blocks,
2512 stats.total_activations,
2513 stats.hot_blocks,
2514 stats.drifted_blocks,
2515 stats.coactivation_pairs,
2516 stats.fingerprint_count,
2517 );
2518
2519 let top = hebb.strongest_pairs(5);
2520 if !top.is_empty() {
2521 output.push_str("\nStrongest co-activations:\n");
2522 for pair in top {
2523 let text_a = crate::safe_truncate(reader.text(pair.block_a as usize), 30);
2524 let text_b = crate::safe_truncate(reader.text(pair.block_b as usize), 30);
2525 output.push_str(&format!(
2526 " {}x [{}] <-> [{}]\n",
2527 pair.count, text_a, text_b
2528 ));
2529 }
2530 }
2531 Ok(output)
2532}
2533
2534fn tool_hottest(config: &Config, args: &Value) -> Result<String, String> {
2537 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2538 let reader = MicroscopeReader::open(config)?;
2539 let output_dir = Path::new(&config.paths.output_dir);
2540 let hebb = crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count);
2541 let hot = hebb.hottest_blocks(k);
2542
2543 if hot.is_empty() {
2544 return Ok("No active blocks — run some queries first.".to_string());
2545 }
2546
2547 let mut output = format!("Hottest {} blocks:\n\n", k);
2548 for (idx, energy) in &hot {
2549 let h = reader.header(*idx);
2550 let text = reader.text(*idx);
2551 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2552 let rec = &hebb.activations[*idx];
2553 output.push_str(&format!(
2554 "E={:.3} D{} [{}] count={} drift=({:.3},{:.3},{:.3}) {}\n",
2555 energy,
2556 h.depth,
2557 layer,
2558 rec.activation_count,
2559 rec.drift_x,
2560 rec.drift_y,
2561 rec.drift_z,
2562 crate::safe_truncate(text, 80)
2563 ));
2564 }
2565 Ok(output)
2566}
2567
2568fn tool_archetypes(config: &Config, _args: &Value) -> Result<String, String> {
2571 let output_dir = Path::new(&config.paths.output_dir);
2572 let arc = crate::archetype::ArchetypeState::load_or_init(output_dir);
2573 let stats = arc.stats();
2574
2575 let mut output = format!(
2576 "Archetypes:\n\
2577 ==========\n\
2578 Emerged: {}\n\
2579 Total members: {}\n",
2580 stats.archetype_count, stats.total_members,
2581 );
2582 if let (Some(label), Some(str)) = (&stats.strongest_label, stats.strongest_strength) {
2583 output.push_str(&format!(
2584 "Strongest: '{}' (str={:.3})\n",
2585 label, str
2586 ));
2587 }
2588
2589 if !arc.archetypes.is_empty() {
2590 output.push_str("\nAll archetypes:\n");
2591 for a in &arc.archetypes {
2592 output.push_str(&format!(
2593 " #{} '{}' str={:.3} members={} reinforced={}x\n",
2594 a.id,
2595 a.label,
2596 a.strength,
2597 a.members.len(),
2598 a.reinforcement_count,
2599 ));
2600 }
2601 }
2602 Ok(output)
2603}
2604
2605fn tool_patterns(config: &Config, args: &Value) -> Result<String, String> {
2608 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2609 let output_dir = Path::new(&config.paths.output_dir);
2610 let tg = crate::thought_graph::ThoughtGraphState::load_or_init(output_dir);
2611 let stats = tg.stats();
2612
2613 let mut output = format!(
2614 "Thought Graph:\n\
2615 =============\n\
2616 nodes={} edges={} patterns={} (crystallized={}) session=#{}\n",
2617 stats.node_count,
2618 stats.edge_count,
2619 stats.pattern_count,
2620 stats.crystallized,
2621 stats.current_session_id,
2622 );
2623
2624 let top = tg.top_patterns(k);
2625 if top.is_empty() {
2626 output.push_str("\n(no patterns yet — recall more to form thought paths)\n");
2627 } else {
2628 output.push_str("\nTop patterns:\n");
2629 for (i, p) in top.iter().enumerate() {
2630 let seq_str: Vec<String> = p
2631 .sequence
2632 .iter()
2633 .map(|h| format!("{:04x}", h & 0xFFFF))
2634 .collect();
2635 let crystallized = if p.frequency >= 3 { "*" } else { " " };
2636 output.push_str(&format!(
2637 "{}#{} {} freq={} str={:.2} blocks={}\n",
2638 crystallized,
2639 i + 1,
2640 seq_str.join(" → "),
2641 p.frequency,
2642 p.strength,
2643 p.result_blocks.len()
2644 ));
2645 }
2646 }
2647 Ok(output)
2648}
2649
2650fn tool_attention(config: &Config, _args: &Value) -> Result<String, String> {
2653 let output_dir = Path::new(&config.paths.output_dir);
2654 let attn = crate::attention::AttentionState::load_or_init(output_dir);
2655
2656 let mut output = format!(
2657 "Attention:\n\
2658 =========\n\
2659 Total recalls: {}\n\
2660 History: {}\n\n",
2661 attn.total_recalls,
2662 attn.history.len(),
2663 );
2664
2665 output.push_str("Learned layer weights:\n");
2666 for (i, name) in crate::attention::LAYER_NAMES.iter().enumerate() {
2667 let w = attn.learned_weights[i];
2668 let bar_len = (w * 10.0) as usize;
2669 let bar: String = "█".repeat(bar_len.min(30));
2670 output.push_str(&format!(" {:<16} {:.3} {}\n", name, w, bar));
2671 }
2672
2673 if !attn.history.is_empty() {
2674 let recent: Vec<&crate::attention::AttentionOutcome> =
2675 attn.history.iter().rev().take(5).collect();
2676 output.push_str("\nRecent outcomes:\n");
2677 for o in recent {
2678 let symbol = if o.quality >= 0.7 {
2679 "+"
2680 } else if o.quality <= 0.3 {
2681 "-"
2682 } else {
2683 "~"
2684 };
2685 output.push_str(&format!(" {} quality={:.2}\n", symbol, o.quality));
2686 }
2687 }
2688 Ok(output)
2689}
2690
2691fn tool_introspect(config: &Config, _args: &Value) -> Result<String, String> {
2694 let reader = MicroscopeReader::open(config)?;
2695 let output_dir = Path::new(&config.paths.output_dir);
2696 let reflection = crate::self_reflect::introspect(config, &reader, output_dir);
2697 Ok(crate::self_reflect::format_reflection(&reflection))
2698}
2699
2700fn tool_self_model(config: &Config, _args: &Value) -> Result<String, String> {
2703 let reader = MicroscopeReader::open(config)?;
2704 let output_dir = Path::new(&config.paths.output_dir);
2705 let mut self_model = crate::self_model::SelfModel::load_or_init(output_dir);
2706 let snap = self_model.take_snapshot(config, &reader, output_dir);
2707 let change = self_model.describe_change();
2708 Ok(crate::self_model::format_self_model(&snap, &change))
2709}
2710
2711fn tool_curiosity(config: &Config, _args: &Value) -> Result<String, String> {
2714 let reader = MicroscopeReader::open(config)?;
2715 let output_dir = Path::new(&config.paths.output_dir);
2716 let mut curiosity = crate::curiosity::CuriosityState::load_or_init(output_dir);
2717 let queries = curiosity.generate_queries(config, &reader, output_dir);
2718 if queries.is_empty() {
2719 return Ok("No curiosity queries generated.".to_string());
2720 }
2721 Ok(crate::curiosity::format_curiosity(&queries))
2722}
2723
2724fn tool_monologue(config: &Config, _args: &Value) -> Result<String, String> {
2727 let reader = MicroscopeReader::open(config)?;
2728 let output_dir = Path::new(&config.paths.output_dir);
2729 let mut monologue = crate::inner_monologue::MonologueState::load_or_init(output_dir);
2730 let entry = monologue.generate_monologue(config, &reader, output_dir);
2731 Ok(crate::inner_monologue::format_monologue(&entry))
2732}
2733
2734fn tool_stories(config: &Config, args: &Value) -> Result<String, String> {
2737 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
2738 let _reader = MicroscopeReader::open(config)?;
2739 let output_dir = Path::new(&config.paths.output_dir);
2740 let nm = crate::narrative_memory::NarrativeMemory::load_or_init(output_dir);
2741
2742 if nm.episodes.is_empty() {
2743 return Ok("No narrative episodes yet — recall some memories first.".to_string());
2744 }
2745
2746 let mut output = format!(
2747 "Narrative Episodes (last {} of {}):\n\n",
2748 k,
2749 nm.episodes.len()
2750 );
2751 for ep in nm.episodes.iter().rev().take(k) {
2752 output.push_str(&crate::narrative_memory::format_episode(ep));
2753 output.push_str("\n---\n");
2754 }
2755 Ok(output)
2756}
2757
2758fn tool_daydream(config: &Config, args: &Value) -> Result<String, String> {
2761 let seed = args.get("seed").and_then(|v| v.as_str()).unwrap_or("");
2762 let steps = args.get("steps").and_then(|v| v.as_u64()).unwrap_or(3) as usize;
2763
2764 let reader = MicroscopeReader::open(config)?;
2765 let output_dir = Path::new(&config.paths.output_dir);
2766
2767 let seed_text = if seed.is_empty() {
2768 let narrative = crate::narrative::NarrativeState::load_or_init(output_dir);
2770 if narrative.narrative.is_empty() {
2771 "microscope memory".to_string()
2772 } else {
2773 narrative.narrative.clone()
2774 }
2775 } else {
2776 seed.to_string()
2777 };
2778
2779 let mut output = format!(
2780 "Daydream (seed: '{}', steps: {}):\n\n",
2781 crate::safe_truncate(&seed_text, 50),
2782 steps
2783 );
2784 let mut current = seed_text;
2785
2786 for step in 0..steps {
2787 let (qx, qy, qz) = crate::content_coords(¤t, "associative");
2788 let config_clone = config.clone();
2789 let results = reader.look(&config_clone, qx, qy, qz, 3, 3);
2790 if results.is_empty() {
2791 output.push_str(&format!(" Step {}: (no associations found)\n", step + 1));
2792 break;
2793 }
2794 let (_, idx, _) = results[0];
2795 let next_text = reader.text(idx);
2796 output.push_str(&format!(
2797 " Step {}: {}\n",
2798 step + 1,
2799 crate::safe_truncate(next_text, 120)
2800 ));
2801 current = next_text.to_string();
2802 }
2803 Ok(output)
2804}
2805
2806fn tool_hyperfocus(config: &Config, args: &Value) -> Result<String, String> {
2809 let target = args
2810 .get("target")
2811 .and_then(|v| v.as_str())
2812 .ok_or("Missing: target")?;
2813 let focus_type = args
2814 .get("focus_type")
2815 .and_then(|v| v.as_str())
2816 .unwrap_or("research");
2817
2818 let reader = MicroscopeReader::open(config)?;
2819 let _output_dir = Path::new(&config.paths.output_dir);
2820
2821 let focus_text = format!("[HYPERFOCUS:{}] Target: {}", focus_type, target);
2823 let _ = store_memory(config, &focus_text, "session", 9);
2824
2825 let (qx, qy, qz) = crate::content_coords(target, "long_term");
2827 let config_clone = config.clone();
2828 let results = reader.look(&config_clone, qx, qy, qz, 2, 10);
2829
2830 let mut output = format!(
2831 "Hyperfocus: '{}' (type: {})\n\
2832 ============================\n\n\
2833 Focus intent stored in session layer (importance=9).\n\n",
2834 target, focus_type
2835 );
2836
2837 if results.is_empty() {
2838 output.push_str("No related memories found — starting fresh.\n");
2839 } else {
2840 output.push_str("Related memories:\n");
2841 for (dist, idx, _) in &results {
2842 let h = reader.header(*idx);
2843 let text = reader.text(*idx);
2844 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2845 output.push_str(&format!(
2846 " [D{} {} dist={:.3}] {}\n",
2847 h.depth,
2848 layer,
2849 dist,
2850 crate::safe_truncate(text, 120)
2851 ));
2852 }
2853 }
2854 Ok(output)
2855}
2856
2857fn tool_emotional_field(config: &Config, _args: &Value) -> Result<String, String> {
2860 let reader = MicroscopeReader::open(config)?;
2861 let output_dir = Path::new(&config.paths.output_dir);
2862 let hebb = crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count);
2863
2864 let mut output = String::from("Emotional Field:\n================\n");
2865
2866 match crate::emotional::emotional_field(&reader, &hebb) {
2867 Some(field) => {
2868 output.push_str(&format!("Total energy: {:.3}\n", field.total_energy));
2869 output.push_str(&format!(
2870 "Centroid: ({:.3}, {:.3}, {:.3})\n",
2871 field.centroid.0, field.centroid.1, field.centroid.2
2872 ));
2873 output.push_str(&format!("Active blocks: {}\n", field.active_blocks));
2874 }
2875 None => {
2876 output.push_str("(no emotional field data — run queries to build state)\n");
2877 }
2878 }
2879
2880 let ring = crate::EmotionalStateRing::load_or_init(output_dir);
2882 if ring.is_active() {
2883 output.push_str("\nEmotional State Ring:\n");
2884 output.push_str(&format!(" Intensity: {:.3}\n", ring.intensity()));
2885 if let Some((name, val)) = ring.dominant() {
2886 output.push_str(&format!(" Dominant: {} ({:.3})\n", name, val));
2887 }
2888 }
2889
2890 Ok(output)
2891}
2892
2893fn tool_embed(config: &Config, args: &Value) -> Result<String, String> {
2896 let query = args
2897 .get("query")
2898 .and_then(|v| v.as_str())
2899 .ok_or("Missing: query")?;
2900 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2901 let metric = args
2902 .get("metric")
2903 .and_then(|v| v.as_str())
2904 .unwrap_or("cosine");
2905
2906 use crate::embedding_index::EmbeddingIndex;
2907
2908 let reader = MicroscopeReader::open(config)?;
2909 let output_dir = Path::new(&config.paths.output_dir);
2910 let emb_path = output_dir.join("embeddings.bin");
2911
2912 let index = EmbeddingIndex::open(&emb_path);
2915 let idx_dim = index
2916 .as_ref()
2917 .map(|idx| idx.dim())
2918 .unwrap_or(config.embedding.dim);
2919 let provider = crate::embeddings::provider_from_config(&config.embedding, idx_dim);
2920 let query_embedding = provider
2921 .embed(query)
2922 .map_err(|e| format!("Embedding failed: {}", e))?;
2923
2924 let mut output = format!("Semantic Search '{}' (metric: {}):\n\n", query, metric);
2925
2926 if let Some(idx) = index {
2927 let results = idx.search(&query_embedding, k);
2928 if results.is_empty() {
2929 output.push_str("(no results)\n");
2930 }
2931 for (sim, block_idx) in results {
2932 let h = reader.header(block_idx);
2933 let text = reader.text(block_idx);
2934 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2935 output.push_str(&format!(
2936 "[D{} {} sim={:.3}] {}\n",
2937 h.depth,
2938 layer,
2939 sim,
2940 crate::safe_truncate(text, 120)
2941 ));
2942 }
2943 } else {
2944 let mut results: Vec<(f32, usize)> = Vec::new();
2946 for i in 0..reader.block_count {
2947 let text = reader.text(i);
2948 if let Ok(block_emb) = provider.embed(text) {
2949 let similarity: f32 = block_emb
2950 .iter()
2951 .zip(query_embedding.iter())
2952 .map(|(a, b)| a * b)
2953 .sum::<f32>()
2954 / (block_emb.iter().map(|v| v * v).sum::<f32>().sqrt()
2955 * query_embedding.iter().map(|v| v * v).sum::<f32>().sqrt()
2956 + 1e-10);
2957 if similarity > 0.5 {
2958 results.push((similarity, i));
2959 }
2960 }
2961 }
2962 results.sort_by(|a, b| b.0.total_cmp(&a.0));
2963 results.truncate(k);
2964
2965 if results.is_empty() {
2966 output.push_str("(no results)\n");
2967 }
2968 for (sim, idx) in results {
2969 let h = reader.header(idx);
2970 let text = reader.text(idx);
2971 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
2972 output.push_str(&format!(
2973 "[D{} {} sim={:.3}] {}\n",
2974 h.depth,
2975 layer,
2976 sim,
2977 crate::safe_truncate(text, 120)
2978 ));
2979 }
2980 }
2981 Ok(output)
2982}
2983
2984fn tool_similar(config: &Config, args: &Value) -> Result<String, String> {
2987 let text = args
2988 .get("text")
2989 .and_then(|v| v.as_str())
2990 .ok_or("Missing: text")?;
2991 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
2992
2993 let reader = MicroscopeReader::open(config)?;
2994 let output_dir = Path::new(&config.paths.output_dir);
2995 let table = crate::fingerprint::LinkTable::load(output_dir)
2996 .ok_or("fingerprints.idx not found — run 'fingerprint' first")?;
2997
2998 let results = table.find_similar(text, k);
2999 if results.is_empty() {
3000 return Ok(format!(
3001 "No structurally similar blocks found for '{}'",
3002 crate::safe_truncate(text, 40)
3003 ));
3004 }
3005
3006 let mut output = format!(
3007 "Structurally similar to '{}':\n\n",
3008 crate::safe_truncate(text, 40)
3009 );
3010 for (idx, sim) in &results {
3011 let h = reader.header(*idx as usize);
3012 let bt = reader.text(*idx as usize);
3013 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
3014 output.push_str(&format!(
3015 "#{} D{} [{}] sim={:.3} {}\n",
3016 idx,
3017 h.depth,
3018 layer,
3019 sim,
3020 crate::safe_truncate(bt, 100)
3021 ));
3022 }
3023 Ok(output)
3024}
3025
3026fn tool_links(config: &Config, args: &Value) -> Result<String, String> {
3029 let block_index = args
3030 .get("block_index")
3031 .and_then(|v| v.as_u64())
3032 .ok_or("Missing: block_index")? as usize;
3033
3034 let reader = MicroscopeReader::open(config)?;
3035 let output_dir = Path::new(&config.paths.output_dir);
3036 let table = crate::fingerprint::LinkTable::load(output_dir)
3037 .ok_or("fingerprints.idx not found — run 'fingerprint' first")?;
3038
3039 let links = table.linked_blocks(block_index as u32);
3040 let h = reader.header(block_index);
3041 let text = reader.text(block_index);
3042 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
3043
3044 let mut output = format!(
3045 "Links for Block #{} D{} [{}] {}\n\n",
3046 block_index,
3047 h.depth,
3048 layer,
3049 crate::safe_truncate(text, 60)
3050 );
3051
3052 if links.is_empty() {
3053 output.push_str("(no structural links)\n");
3054 } else {
3055 for (target, sim) in &links {
3056 let th = reader.header(*target as usize);
3057 let tt = reader.text(*target as usize);
3058 let tl = LAYER_NAMES.get(th.layer_id as usize).unwrap_or(&"?");
3059 output.push_str(&format!(
3060 " -> #{} D{} [{}] sim={:.3} {}\n",
3061 target,
3062 th.depth,
3063 tl,
3064 sim,
3065 crate::safe_truncate(tt, 80)
3066 ));
3067 }
3068 }
3069 Ok(output)
3070}
3071
3072fn tool_fingerprint(config: &Config, _args: &Value) -> Result<String, String> {
3075 let reader = MicroscopeReader::open(config)?;
3076 let output_dir = Path::new(&config.paths.output_dir);
3077
3078 let texts: Vec<&str> = (0..reader.block_count).map(|i| reader.text(i)).collect();
3079 let table = crate::fingerprint::LinkTable::build(&texts);
3080 table
3081 .save(output_dir)
3082 .map_err(|e| format!("Failed to save fingerprints: {}", e))?;
3083
3084 let stats = table.stats();
3085 Ok(format!(
3086 "Fingerprints built:\n\
3087 ==================\n\
3088 Avg entropy: {:.3}\n\
3089 Unique hashes: {}\n\
3090 Largest cluster: {}\n\
3091 Structural links: {}\n",
3092 stats.avg_entropy, stats.unique_hashes, stats.largest_cluster, stats.link_count,
3093 ))
3094}
3095
3096fn tool_dream_log(config: &Config, args: &Value) -> Result<String, String> {
3099 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
3100 let output_dir = Path::new(&config.paths.output_dir);
3101 let log_path = output_dir.join("dream_log.bin");
3102
3103 if !log_path.exists() {
3104 return Ok("No dream log found — run 'dream' first.".to_string());
3105 }
3106
3107 let data = std::fs::read(&log_path).unwrap_or_default();
3108 if data.len() < 4 {
3109 return Ok("Dream log is empty.".to_string());
3110 }
3111
3112 let entry_size = 48; let count = data.len() / entry_size;
3115 let shown = count.min(k);
3116
3117 let mut output = format!(
3118 "Dream Log — {} cycles recorded, showing last {}:\n\n",
3119 count, shown
3120 );
3121 for i in (count - shown)..count {
3122 let offset = i * entry_size;
3123 if offset + 8 <= data.len() {
3124 let ts = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap_or([0; 8]));
3125 output.push_str(&format!(
3126 " Cycle #{}: ts={}\n",
3127 i,
3128 crate::timeline::format_ts(ts)
3129 ));
3130 }
3131 }
3132 Ok(output)
3133}
3134
3135fn tool_resonance(config: &Config, _args: &Value) -> Result<String, String> {
3138 let output_dir = Path::new(&config.paths.output_dir);
3139 let resonance = crate::resonance::ResonanceState::load_or_init(output_dir);
3140 let stats = resonance.stats();
3141
3142 Ok(format!(
3143 "Resonance Protocol:\n\
3144 ==================\n\
3145 Instance ID: {:x}\n\
3146 Outgoing pulses: {}\n\
3147 Incoming pulses: {}\n\
3148 Pending integration:{}\n\
3149 Unique sources: {}\n\
3150 Field cells: {}\n\
3151 Field energy: {:.3}\n",
3152 stats.instance_id,
3153 stats.outgoing_pulses,
3154 stats.incoming_pulses,
3155 stats.pending_integration,
3156 stats.unique_sources,
3157 stats.field_cells,
3158 stats.field_energy,
3159 ))
3160}
3161
3162fn tool_mirror(config: &Config, _args: &Value) -> Result<String, String> {
3165 let output_dir = Path::new(&config.paths.output_dir);
3166 let mirror = crate::mirror::MirrorState::load_or_init(output_dir);
3167 let stats = mirror.stats();
3168
3169 let mut output = format!(
3170 "Mirror Neuron State:\n\
3171 ===================\n\
3172 Resonance echoes: {}\n\
3173 Resonant blocks: {}\n\
3174 Avg similarity: {:.3}\n",
3175 stats.total_echoes, stats.resonant_blocks, stats.avg_similarity,
3176 );
3177
3178 if let Some((idx, strength)) = stats.strongest_block {
3179 let reader = MicroscopeReader::open(config)?;
3180 let text = reader.text(idx as usize);
3181 output.push_str(&format!(
3182 "Strongest: block #{} (str={:.3}) {}\n",
3183 idx,
3184 strength,
3185 crate::safe_truncate(text, 60)
3186 ));
3187 }
3188
3189 if !mirror.echoes.is_empty() {
3190 output.push_str("\nRecent echoes:\n");
3191 for echo in mirror.echoes.iter().rev().take(5) {
3192 output.push_str(&format!(
3193 " sim={:.3} shared={} blocks trigger={:x} echo={:x}\n",
3194 echo.similarity,
3195 echo.shared_blocks.len(),
3196 echo.trigger_hash,
3197 echo.echo_hash,
3198 ));
3199 }
3200 }
3201 Ok(output)
3202}
3203
3204fn tool_predictions(config: &Config, _args: &Value) -> Result<String, String> {
3207 let output_dir = Path::new(&config.paths.output_dir);
3208 let cache = crate::predictive_cache::PredictiveCache::load_or_init(output_dir);
3209 let stats = &cache.stats;
3210
3211 let mut output = format!(
3212 "Predictive Cache:\n\
3213 ================\n\
3214 Predictions: {}\n\
3215 Hits: {}\n\
3216 Misses: {}\n\
3217 Partial hits: {}\n\
3218 Hit rate: {:.1}%\n\
3219 Active: {}\n\
3220 Avg confidence: {:.1}%\n",
3221 stats.total_predictions,
3222 stats.total_hits,
3223 stats.total_misses,
3224 stats.total_partial_hits,
3225 stats.hit_rate() * 100.0,
3226 stats.current_predictions,
3227 stats.avg_confidence * 100.0,
3228 );
3229
3230 if !cache.predictions.is_empty() {
3231 output.push_str("\nActive predictions:\n");
3232 for (i, p) in cache.predictions.iter().enumerate() {
3233 output.push_str(&format!(
3234 " #{} hash={:04x} blocks={} conf={:.0}% pattern=#{}\n",
3235 i + 1,
3236 p.predicted_query_hash & 0xFFFF,
3237 p.blocks.len(),
3238 p.confidence * 100.0,
3239 p.pattern_id
3240 ));
3241 }
3242 }
3243 Ok(output)
3244}
3245
3246fn tool_paths(config: &Config, args: &Value) -> Result<String, String> {
3249 let sessions = args.get("sessions").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
3250 let output_dir = Path::new(&config.paths.output_dir);
3251 let tg = crate::thought_graph::ThoughtGraphState::load_or_init(output_dir);
3252 let recent = tg.recent_sessions(sessions);
3253
3254 if recent.is_empty() {
3255 return Ok("No recall sessions recorded yet.".to_string());
3256 }
3257
3258 let mut output = format!("Thought Paths (last {} sessions):\n\n", sessions);
3259 for (si, session) in recent.iter().enumerate() {
3260 if let Some(first) = session.first() {
3261 let path_str: Vec<String> = session
3262 .iter()
3263 .map(|n| format!("{:04x}", n.query_hash & 0xFFFF))
3264 .collect();
3265 output.push_str(&format!(
3266 "Session #{} ({} recalls): {}\n",
3267 first.session_id,
3268 session.len(),
3269 path_str.join(" → ")
3270 ));
3271 }
3272 if si >= sessions {
3273 break;
3274 }
3275 }
3276 Ok(output)
3277}
3278
3279fn tool_temporal_patterns(config: &Config, _args: &Value) -> Result<String, String> {
3282 let output_dir = Path::new(&config.paths.output_dir);
3283 let temporal = crate::temporal_archetype::TemporalArchetypeState::load_or_init(output_dir);
3284 let window = crate::temporal_archetype::current_time_window();
3285
3286 let mut output = format!(
3287 "Temporal Archetypes (current window: {}):\n\n",
3288 crate::temporal_archetype::WINDOW_LABELS[window]
3289 );
3290
3291 if temporal.profiles.is_empty() {
3292 output.push_str("(no temporal data yet)\n");
3293 } else {
3294 for p in &temporal.profiles {
3295 let dominant = p
3296 .dominant_window()
3297 .map(|w| crate::temporal_archetype::WINDOW_LABELS[w])
3298 .unwrap_or("?");
3299 output.push_str(&format!(
3300 "Archetype #{} (total={}, dominant={})\n",
3301 p.archetype_id, p.total_activations, dominant
3302 ));
3303 for (i, label) in crate::temporal_archetype::WINDOW_LABELS.iter().enumerate() {
3304 let bar_len = (p.window_weights[i] * 5.0) as usize;
3305 let bar: String = "█".repeat(bar_len);
3306 let marker = if i == window { " ◀" } else { "" };
3307 output.push_str(&format!(
3308 " {} {:>3} {:.1} {}{}\n",
3309 label, p.window_counts[i], p.window_weights[i], bar, marker
3310 ));
3311 }
3312 }
3313 }
3314 Ok(output)
3315}
3316
3317fn tool_modalities(config: &Config, _args: &Value) -> Result<String, String> {
3320 let reader = MicroscopeReader::open(config)?;
3321 let _output_dir = Path::new(&config.paths.output_dir);
3322
3323 let mut output = format!(
3324 "Multimodal Index:\n\
3325 =================\n\
3326 Total blocks: {}\n\n",
3327 reader.block_count
3328 );
3329
3330 output.push_str("Layer breakdown:\n");
3332 let mut layer_counts: std::collections::HashMap<u8, usize> = std::collections::HashMap::new();
3333 for i in 0..reader.block_count {
3334 let h = reader.header(i);
3335 *layer_counts.entry(h.layer_id).or_default() += 1;
3336 }
3337 for (lid, count) in layer_counts.iter() {
3338 let name = LAYER_NAMES.get(*lid as usize).unwrap_or(&"?");
3339 output.push_str(&format!(" {}: {} blocks\n", name, count));
3340 }
3341
3342 output.push_str("\nDepth breakdown:\n");
3344 for (d, &(_start, count)) in reader.depth_ranges.iter().enumerate() {
3345 if count > 0 {
3346 output.push_str(&format!(" D{}: {} blocks\n", d, count));
3347 }
3348 }
3349
3350 Ok(output)
3351}
3352
3353fn tool_doctor(config: &Config, args: &Value) -> Result<String, String> {
3356 let fix = args.get("fix").and_then(|v| v.as_bool()).unwrap_or(false);
3357 crate::doctor::run_doctor(config, fix).map_err(|e| format!("Doctor failed: {}", e))?;
3358 Ok("Doctor diagnostics complete. Use --fix to attempt repairs.".to_string())
3359}
3360
3361fn tool_rebuild(config: &Config, _args: &Value) -> Result<String, String> {
3364 let outcome = crate::build::rebuild_pending(config, true, true)
3365 .map_err(|e| format!("Rebuild failed: {}", e))?;
3366 let reader = MicroscopeReader::open(config)?;
3367 Ok(format!(
3368 "Rebuild complete: {} blocks across {} depths.\nAppend log cleared after consolidating {} entries.",
3369 reader.block_count,
3370 reader.depth_ranges.iter().filter(|&&(_, c)| c > 0).count(),
3371 outcome.pending_entries
3372 ))
3373}
3374
3375fn tool_store_data(config: &Config, args: &Value) -> Result<String, String> {
3378 let pairs = args
3379 .get("pairs")
3380 .and_then(|v| v.as_object())
3381 .ok_or("Missing: pairs")?;
3382 let importance = args.get("importance").and_then(|v| v.as_u64()).unwrap_or(5) as u8;
3383
3384 let mut stored = Vec::new();
3385 for (key, val) in pairs {
3386 let val_str = val.as_str().unwrap_or("");
3387 let text = format!("[DATA] {} = {}", key, val_str);
3388 store_memory(config, &text, "long_term", importance)?;
3389 stored.push(format!("{} = {}", key, val_str));
3390 }
3391
3392 Ok(format!(
3393 "Stored {} data pairs (importance={}):\n {}",
3394 stored.len(),
3395 importance,
3396 stored.join("\n ")
3397 ))
3398}
3399
3400fn tool_resonant(config: &Config, args: &Value) -> Result<String, String> {
3403 let k = args.get("k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
3404 let reader = MicroscopeReader::open(config)?;
3405 let output_dir = Path::new(&config.paths.output_dir);
3406 let mirror = crate::mirror::MirrorState::load_or_init(output_dir);
3407 let top = mirror.most_resonant(k);
3408
3409 if top.is_empty() {
3410 return Ok("No resonant blocks — run queries to build mirror state.".to_string());
3411 }
3412
3413 let mut output = format!("Most Resonant {} blocks:\n\n", k);
3414 for (idx, res) in &top {
3415 let h = reader.header(*idx as usize);
3416 let text = reader.text(*idx as usize);
3417 let layer = LAYER_NAMES.get(h.layer_id as usize).unwrap_or(&"?");
3418 output.push_str(&format!(
3419 "S={:.3} D{} [{}] echoes={} {}\n",
3420 res.strength,
3421 h.depth,
3422 layer,
3423 res.echo_count,
3424 crate::safe_truncate(text, 80)
3425 ));
3426 }
3427 Ok(output)
3428}
3429
3430fn tool_autonomous(config: &Config, args: &Value) -> Result<String, String> {
3433 let tts = args.get("tts").and_then(|v| v.as_bool()).unwrap_or(false);
3434 let daemon = args
3435 .get("daemon")
3436 .and_then(|v| v.as_bool())
3437 .unwrap_or(false);
3438 let interval = args.get("interval").and_then(|v| v.as_u64()).unwrap_or(30);
3439 let max_cycles = args.get("max_cycles").and_then(|v| v.as_u64());
3440
3441 let reader = MicroscopeReader::open(config)?;
3442 let output_dir = Path::new(&config.paths.output_dir);
3443
3444 let mut output = format!(
3445 "Autonomous Mode:\n\
3446 ===============\n\
3447 TTS: {}\n\
3448 Daemon: {}\n\
3449 Interval: {}s\n\
3450 Max cycles: {}\n\n",
3451 if tts { "enabled" } else { "disabled" },
3452 if daemon { "enabled" } else { "single cycle" },
3453 interval,
3454 max_cycles.map_or("unlimited".to_string(), |n| n.to_string()),
3455 );
3456
3457 let narrative = crate::narrative::NarrativeState::load_or_init(output_dir);
3460 let seed = if narrative.narrative.is_empty() {
3461 "microscope memory".to_string()
3462 } else {
3463 narrative.narrative.clone()
3464 };
3465 let (qx, qy, qz) = crate::content_coords(&seed, "associative");
3466 let config_clone = config.clone();
3467 let daydream_results = reader.look(&config_clone, qx, qy, qz, 3, 3);
3468 output.push_str("Daydream:\n");
3469 for (dist, idx, _) in daydream_results.iter().take(3) {
3470 let text = reader.text(*idx);
3471 output.push_str(&format!(
3472 " [dist={:.3}] {}\n",
3473 dist,
3474 crate::safe_truncate(text, 100)
3475 ));
3476 }
3477
3478 let mut curiosity = crate::curiosity::CuriosityState::load_or_init(output_dir);
3480 let queries = curiosity.generate_queries(config, &reader, output_dir);
3481 if !queries.is_empty() {
3482 output.push_str("\nCuriosity:\n");
3483 for q in queries.iter().take(3) {
3484 output.push_str(&format!(" {}\n", crate::safe_truncate(&q.query, 100)));
3485 }
3486 }
3487
3488 let mut monologue = crate::inner_monologue::MonologueState::load_or_init(output_dir);
3490 let entry = monologue.generate_monologue(config, &reader, output_dir);
3491 output.push_str(&format!(
3492 "\nMonologue:\n {}\n",
3493 crate::safe_truncate(&entry.steps.join("\n "), 200)
3494 ));
3495
3496 match crate::dream::dream_consolidate(
3498 output_dir,
3499 reader.block_count,
3500 config.index.max_blocks,
3501 config.index.protect_min_importance,
3502 ) {
3503 Ok(cycle) => {
3504 output.push_str(&format!(
3505 "\nDream: {}ms, {} strengthened, {} pruned, energy {:.3}→{:.3}\n",
3506 cycle.duration_ms,
3507 cycle.strengthened_pairs,
3508 cycle.pruned_pairs,
3509 cycle.energy_before,
3510 cycle.energy_after,
3511 ));
3512 }
3513 Err(e) => {
3514 output.push_str(&format!("\nDream: failed — {}\n", e));
3515 }
3516 }
3517
3518 if daemon {
3519 output.push_str(&format!(
3520 "\nDaemon mode: cycling every {}s. Use CLI to stop.",
3521 interval
3522 ));
3523 }
3524
3525 Ok(output)
3526}
3527
3528#[cfg(test)]
3529mod tests {
3530 use super::*;
3531 use crate::types::ProjectId;
3532
3533 fn test_config() -> Config {
3536 Config {
3537 paths: crate::config::Paths {
3538 layers_dir: "./layers".to_string(),
3539 output_dir: "./data".to_string(),
3540 temp_dir: "./tmp".to_string(),
3541 },
3542 index: crate::config::Index {
3543 max_depth: 8,
3544 header_size: 32,
3545 auto_rebuild: false,
3546 auto_rebuild_entries: 25,
3547 layer_retention_entries: 2000,
3548 max_blocks: 0,
3549 protect_min_importance: 8,
3550 promote_energy_threshold: 0.35,
3551 },
3552 search: crate::config::Search {
3553 default_k: 10,
3554 zoom_weight: 2.0,
3555 keyword_boost: 0.1,
3556 semantic_weight: 0.0,
3557 semantic_rank_gain: 2.0,
3558 emotional_bias_weight: 0.0,
3559 emotion_21d_weight: 0.0,
3560 },
3561 memory_layers: crate::config::MemoryLayers {
3562 layers: vec![
3563 "long_term".to_string(),
3564 "short_term".to_string(),
3565 "session".to_string(),
3566 ],
3567 },
3568 performance: crate::config::Performance {
3569 use_mmap: false,
3570 cache_size: 64,
3571 build_workers: 1,
3572 use_gpu: false,
3573 compression: false,
3574 cache_ttl_secs: 300,
3575 },
3576 logging: crate::config::Logging {
3577 level: "debug".to_string(),
3578 file: None,
3579 },
3580 embedding: crate::config::Embedding::default(),
3581 server: crate::config::Server::default(),
3582 federation: crate::config::Federation::default(),
3583 hooks: crate::config::HooksConfig::default(),
3584 project_id: ProjectId::GLOBAL,
3585 epistemic: crate::config::EpistemicConfig::default(),
3586 }
3587 }
3588
3589 #[test]
3592 fn test_hooks_read_only_public_mode() {
3593 let config = test_config();
3594 assert!(
3596 config.hooks.read_only,
3597 "public demo must be read-only by default"
3598 );
3599 assert!(
3600 !config.hooks.write_enabled,
3601 "write must be disabled in public mode"
3602 );
3603
3604 let hook_config = if config.hooks.read_only {
3605 HookConfig::read_only()
3606 } else {
3607 HookConfig::default()
3608 };
3609
3610 assert!(hook_config.read_only);
3611 assert!(!hook_config.can_write());
3612 assert!(!hook_config.is_enabled(&HookEvent::AfterResponse));
3613 }
3614
3615 #[test]
3618 fn test_before_tool_call_injection() {
3619 let config = test_config();
3620 let hook_config = if config.hooks.read_only {
3621 HookConfig::read_only()
3622 } else {
3623 HookConfig::default()
3624 };
3625 let manager = HookManager::new(hook_config);
3626
3627 let ctx = HookContext::new(HookEvent::BeforeToolCall)
3628 .with_tool("memory_recall", serde_json::json!({"query": "test"}));
3629
3630 let result = manager.execute(HookEvent::BeforeToolCall, ctx);
3631
3632 assert!(result.memory_candidates.is_empty());
3634 assert!(!result.chain_id.is_empty());
3636 assert_eq!(result.tool_name, Some("memory_recall".to_string()));
3638 }
3639
3640 #[test]
3643 fn test_after_tool_call_candidate_creation() {
3644 let config = test_config();
3645 let hook_config = if config.hooks.read_only {
3646 HookConfig::read_only()
3647 } else {
3648 HookConfig::default()
3649 };
3650 let manager = HookManager::new(hook_config);
3651
3652 let ctx = HookContext::new(HookEvent::AfterToolCall)
3653 .with_tool("memory_recall", serde_json::json!({"query": "test"}))
3654 .with_response("Found 3 relevant memories about the project.");
3655
3656 let result = manager.execute(HookEvent::AfterToolCall, ctx);
3657
3658 assert!(
3660 result.memory_candidates.is_empty(),
3661 "read-only mode must clear all memory candidates"
3662 );
3663 }
3664
3665 #[test]
3668 fn test_after_tool_call_candidate_full_mode() {
3669 let _config = test_config();
3670 let hook_config = HookConfig::full();
3671 let manager = HookManager::new(hook_config);
3672
3673 let mut ctx = HookContext::new(HookEvent::AfterToolCall)
3674 .with_tool("memory_recall", serde_json::json!({"query": "test"}));
3675 ctx.tool_result =
3676 Some("Found 3 relevant memories about the project requirements.".to_string());
3677
3678 let result = manager.execute(HookEvent::AfterToolCall, ctx);
3679
3680 assert!(
3682 !result.memory_candidates.is_empty(),
3683 "full mode must create memory candidates"
3684 );
3685 assert_eq!(
3686 result.memory_candidates[0].source_tool,
3687 Some("memory_recall".to_string())
3688 );
3689 assert_eq!(
3690 result.memory_candidates[0].source_event,
3691 HookEvent::AfterToolCall
3692 );
3693 }
3694
3695 #[test]
3698 fn test_error_hook_execution() {
3699 let config = test_config();
3700 let hook_config = if config.hooks.read_only {
3701 HookConfig::read_only()
3702 } else {
3703 HookConfig::default()
3704 };
3705 let manager = HookManager::new(hook_config);
3706
3707 let ctx = HookContext::new(HookEvent::Error)
3708 .with_tool("memory_store", serde_json::json!({"text": "test"}))
3709 .with_error("Index not found - run build first", "E1001");
3710
3711 let result = manager.execute(HookEvent::Error, ctx);
3712
3713 assert!(
3715 result.memory_candidates.is_empty(),
3716 "read-only mode must clear error candidates"
3717 );
3718 }
3719
3720 #[test]
3723 fn test_error_hook_full_mode() {
3724 let _config = test_config();
3725 let hook_config = HookConfig::full();
3726 let manager = HookManager::new(hook_config);
3727
3728 let ctx = HookContext::new(HookEvent::Error)
3729 .with_tool("memory_store", serde_json::json!({"text": "test"}))
3730 .with_error("Connection timeout", "E1002");
3731
3732 let result = manager.execute(HookEvent::Error, ctx);
3733
3734 assert!(
3735 !result.memory_candidates.is_empty(),
3736 "full mode must create error candidates"
3737 );
3738 assert!(result.memory_candidates[0].is_error);
3739 assert_eq!(result.memory_candidates[0].importance, 7);
3740 }
3741
3742 #[test]
3745 fn test_write_hook_disabled_by_default() {
3746 let config = test_config();
3747 assert!(
3749 !config.hooks.write_enabled,
3750 "write must be disabled by default"
3751 );
3752
3753 let hook_config = HookConfig::default();
3754 assert!(
3755 !hook_config.can_write(),
3756 "HookConfig must deny writes by default"
3757 );
3758 assert!(
3759 !hook_config.is_enabled(&HookEvent::AfterResponse),
3760 "after_response hook must be disabled by default"
3761 );
3762 }
3763
3764 #[test]
3767 fn test_secret_filtering() {
3768 let _config = test_config();
3769 let hook_config = HookConfig::full();
3770 let manager = HookManager::new(hook_config);
3771
3772 let ctx = HookContext::new(HookEvent::AfterToolCall)
3774 .with_tool("memory_store", serde_json::json!({"text": "test"}))
3775 .with_response("The password is super-secret-123 and the API key is sk-test-key");
3776
3777 let result = manager.execute(HookEvent::AfterToolCall, ctx);
3778
3779 for candidate in &result.memory_candidates {
3781 assert!(
3782 !candidate.text.to_lowercase().contains("password"),
3783 "candidate must not contain secrets: {}",
3784 candidate.text
3785 );
3786 assert!(
3787 !candidate.text.to_lowercase().contains("sk-"),
3788 "candidate must not contain API keys: {}",
3789 candidate.text
3790 );
3791 }
3792 }
3793
3794 #[test]
3797 fn test_secret_filtering_read_only() {
3798 let _config = test_config();
3799 let hook_config = HookConfig::read_only();
3800 let manager = HookManager::new(hook_config);
3801
3802 let ctx = HookContext::new(HookEvent::AfterToolCall)
3803 .with_tool("memory_store", serde_json::json!({"text": "test"}))
3804 .with_response("The password is secret and the api_key=sk-abc123");
3805
3806 let result = manager.execute(HookEvent::AfterToolCall, ctx);
3807
3808 assert!(
3810 result.memory_candidates.is_empty(),
3811 "read-only mode must clear all candidates even with secrets"
3812 );
3813 }
3814
3815 #[test]
3818 fn test_session_start_hook() {
3819 let config = test_config();
3820 let hook_config = if config.hooks.read_only {
3821 HookConfig::read_only()
3822 } else {
3823 HookConfig::default()
3824 };
3825 let manager = HookManager::new(hook_config);
3826
3827 let ctx = HookContext::new(HookEvent::SessionStart);
3828 let result = manager.execute(HookEvent::SessionStart, ctx);
3829
3830 assert!(
3832 result.memory_contract.is_some(),
3833 "session start must load memory contract"
3834 );
3835 assert!(
3837 !result.constraints.is_empty(),
3838 "session start must load constraints"
3839 );
3840 }
3841
3842 #[test]
3845 fn test_full_lifecycle_chain() {
3846 let _config = test_config();
3847 let hook_config = HookConfig::full();
3848 let manager = HookManager::new(hook_config);
3849
3850 let ctx = HookContext::new(HookEvent::SessionStart);
3852 let events = [
3853 HookEvent::SessionStart,
3854 HookEvent::BeforeToolCall,
3855 HookEvent::AfterToolCall,
3856 ];
3857
3858 let mut chain_ctx = ctx;
3859 for event in &events {
3860 chain_ctx = match event {
3861 HookEvent::BeforeToolCall => {
3862 chain_ctx.with_tool("memory_recall", serde_json::json!({"query": "test"}))
3863 }
3864 HookEvent::AfterToolCall => {
3865 chain_ctx.tool_result =
3866 Some("Found relevant results from the memory search.".to_string());
3867 chain_ctx
3868 }
3869 _ => chain_ctx,
3870 };
3871 chain_ctx = manager.execute(*event, chain_ctx);
3872 }
3873
3874 assert!(
3876 !chain_ctx.memory_candidates.is_empty(),
3877 "full lifecycle chain should produce candidates"
3878 );
3879 assert!(
3880 chain_ctx.memory_contract.is_some(),
3881 "memory contract should be loaded from session start"
3882 );
3883 }
3884
3885 #[test]
3888 fn test_public_demo_tools_list() {
3889 let response = handle_tools_list(&serde_json::json!("test"));
3890 let tools = response["result"]["tools"].as_array().unwrap();
3891 assert!(!tools.is_empty(), "tools/list must return tools");
3892 let names: Vec<&str> = tools.iter().filter_map(|t| t["name"].as_str()).collect();
3893 assert!(
3894 names.contains(&"memory_status"),
3895 "must include memory_status"
3896 );
3897 assert!(
3898 names.contains(&"memory_recall"),
3899 "must include memory_recall"
3900 );
3901 assert!(names.contains(&"memory_find"), "must include memory_find");
3902 assert!(
3903 names.contains(&"memory_auto_context"),
3904 "must include memory_auto_context"
3905 );
3906 }
3907
3908 #[test]
3909 fn test_public_demo_tools_are_read_only() {
3910 let response = handle_tools_list(&serde_json::json!("test"));
3912 let tools = response["result"]["tools"].as_array().unwrap();
3913 let names: Vec<&str> = tools.iter().filter_map(|t| t["name"].as_str()).collect();
3914
3915 assert!(
3917 names.contains(&"memory_status"),
3918 "must include memory_status"
3919 );
3920 assert!(
3921 names.contains(&"memory_recall"),
3922 "must include memory_recall"
3923 );
3924 assert!(names.contains(&"memory_find"), "must include memory_find");
3925 assert!(names.contains(&"memory_look"), "must include memory_look");
3926 assert!(
3927 names.contains(&"memory_auto_context"),
3928 "must include memory_auto_context"
3929 );
3930
3931 for tool in tools {
3933 let name = tool["name"].as_str().unwrap_or("");
3934 let schema = &tool["inputSchema"];
3935 assert!(schema.is_object(), "tool '{}' must have inputSchema", name);
3936 }
3937 }
3938
3939 #[test]
3940 fn test_public_demo_safe_query() {
3941 let _config = test_config();
3942 let hook_config = HookConfig::read_only();
3943 let manager = HookManager::new(hook_config);
3944 let ctx = HookContext::new(HookEvent::BeforeToolCall)
3945 .with_tool("memory_recall", serde_json::json!({"query": "safe query"}));
3946 let result = manager.execute(HookEvent::BeforeToolCall, ctx);
3947 assert!(
3948 result.memory_candidates.is_empty(),
3949 "safe query must not produce memory candidates in public mode"
3950 );
3951 assert_eq!(result.tool_name, Some("memory_recall".to_string()));
3952 }
3953
3954 #[test]
3955 fn test_public_demo_secret_query_filtered() {
3956 let _config = test_config();
3957 let hook_config = HookConfig::read_only();
3958 let manager = HookManager::new(hook_config);
3959 let mut ctx = HookContext::new(HookEvent::AfterToolCall)
3960 .with_tool("memory_recall", serde_json::json!({"query": "test"}));
3961 ctx.tool_result = Some("Found result with password=secret123".to_string());
3962 let result = manager.execute(HookEvent::AfterToolCall, ctx);
3963 assert!(
3964 result.memory_candidates.is_empty(),
3965 "secret-containing results must not create candidates in public mode"
3966 );
3967 }
3968
3969 #[test]
3970 fn test_public_demo_initialize_response() {
3971 let response = handle_initialize(&serde_json::json!("test"));
3972 assert_eq!(response["jsonrpc"], "2.0");
3973 assert_eq!(response["id"], "test");
3974 assert_eq!(response["result"]["protocolVersion"], "2024-11-05");
3975 assert!(response["result"]["capabilities"]["tools"].is_object());
3976 }
3977
3978 #[test]
3979 fn read_message_rejects_oversized_content_length() {
3980 let header = format!("Content-Length: {}\r\n\r\n", MAX_MESSAGE_LEN + 1);
3981 let mut reader = std::io::Cursor::new(header.into_bytes());
3982 let err = match read_message(&mut reader) {
3983 Ok(_) => panic!("oversized Content-Length must fail"),
3984 Err(e) => e,
3985 };
3986 assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
3987 assert!(err.to_string().contains("exceeds maximum allowed"));
3988 }
3989
3990 #[test]
3991 fn read_message_rejects_oversized_header_line() {
3992 let huge = "x".repeat(MAX_HEADER_LEN + 1);
3993 let mut reader = std::io::Cursor::new(huge.into_bytes());
3994 let err = match read_message(&mut reader) {
3995 Ok(_) => panic!("unterminated oversized header must fail"),
3996 Err(e) => e,
3997 };
3998 assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
3999 }
4000}