zagens_topic_memory/
lib.rs1mod engine;
7mod extract;
8mod graph;
9mod inject;
10mod metrics;
11mod retrieve;
12mod stopwords;
13
14pub use engine::{
15 DEFAULT_INJECT_INTERVAL_RUNS, GenerateMemorySectionOptions, apply_decay, empty_graph,
16 generate_memory_section, should_inject_memory, today_str, update_graph,
17};
18pub use extract::{detect_blocked_topics, detect_emotion, extract_topics};
19pub use graph::{
20 BlockedPoint, CognitiveTrail, EmotionMode, GRAPH_SCHEMA_VERSION, PheromoneEdge, PheromoneGraph,
21 PheromoneNode,
22};
23pub use inject::{DEFAULT_MARKERS, MemorySectionMarkers, inject_memory_section};
24pub use metrics::{
25 TopicMemoryEvalComparison, TopicMemoryEvalReport, TopicMemoryMetrics, compare_eval,
26 eval_report, load_metrics, metrics_path_for_graph, record_inject, record_turn_update,
27 save_metrics,
28};
29pub use retrieve::{
30 DEFAULT_RETRIEVE_K_HOPS, induced_subgraph, retrieve_for_query, retrieve_k_hop_subgraph,
31};
32
33use std::fs;
34use std::path::Path;
35
36#[must_use]
38pub fn load_graph(path: &Path) -> PheromoneGraph {
39 let Ok(raw) = fs::read_to_string(path) else {
40 return empty_graph();
41 };
42 serde_json::from_str(&raw).unwrap_or_else(|_| empty_graph())
43}
44
45pub fn save_graph(path: &Path, graph: &PheromoneGraph) -> std::io::Result<()> {
47 if let Some(parent) = path.parent() {
48 fs::create_dir_all(parent)?;
49 }
50 let json = serde_json::to_string_pretty(graph)?;
51 let tmp = path.with_extension("json.tmp");
52 fs::write(&tmp, json)?;
53 fs::rename(tmp, path)?;
54 Ok(())
55}
56
57#[must_use]
59pub fn as_system_block(content: &str, source: &Path) -> Option<String> {
60 let trimmed = content.trim();
61 if trimmed.is_empty() {
62 return None;
63 }
64 Some(format!(
65 "<topic_memory source=\"{}\">\n{trimmed}\n</topic_memory>",
66 source.display()
67 ))
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use tempfile::tempdir;
74
75 #[test]
76 fn round_trip_save_load() {
77 let dir = tempdir().expect("tempdir");
78 let path = dir.path().join("topic-memory.json");
79 let mut g = empty_graph();
80 g = update_graph(&g, "hello Rust", "hi there");
81 save_graph(&path, &g).expect("save");
82 let loaded = load_graph(&path);
83 assert_eq!(loaded.nodes.len(), g.nodes.len());
84 }
85}