/// Basic engram demonstration. /// /// This example builds a small memory graph, runs spreading activation, /// performs a vector search, shows salience decay, and demonstrates /// the consolidation engine promoting Episodic nodes to Semantic. /// /// The nodes represent a tiny knowledge graph about the spreading activation /// model itself — somewhat recursive, intentionally. use engram_core::{ ActivatedNode, ConsolidationConfig, Edge, EngramDb, MemoryTier, Node, NodeType, RelationType, }; use std::path::Path; fn main() -> Result<(), Box> { // ── 1. Open database ────────────────────────────────────────────────────── let db_path = Path::new("/tmp/engram-test"); // Clean up any previous run so we start fresh if db_path.exists() { std::fs::remove_dir_all(db_path)?; } let db = EngramDb::open(db_path)?; println!("Engram opened at {}\n", db_path.display()); // ── 2. Insert nodes ─────────────────────────────────────────────────────── // // We use 8-dimensional embeddings. In production these would come from a // language model. Here they're hand-crafted to illustrate semantic proximity: // the "activation" and "memory" cluster at [high, high, low, ...] // while "forgetting" and "decay" cluster at [low, low, high, ...] let node0 = Node::new( NodeType::Concept, vec![0.9, 0.8, 0.1, 0.2, 0.7, 0.3, 0.1, 0.4], b"Spreading activation: memory retrieval as propagation through weighted graph".to_vec(), MemoryTier::Semantic, 0.95, ); let node1 = Node::new( NodeType::Concept, vec![0.8, 0.9, 0.2, 0.1, 0.6, 0.4, 0.2, 0.3], b"Long-term potentiation: synaptic strengthening through co-activation".to_vec(), MemoryTier::Semantic, 0.90, ); let node2 = Node::new( NodeType::Memory, vec![0.7, 0.6, 0.3, 0.4, 0.8, 0.2, 0.1, 0.5], b"Hebbian learning: neurons that fire together wire together".to_vec(), MemoryTier::Episodic, 0.85, ); let node3 = Node::new( NodeType::Concept, vec![0.6, 0.7, 0.4, 0.3, 0.9, 0.1, 0.2, 0.6], b"Associative memory: retrieval by pattern completion, not address lookup".to_vec(), MemoryTier::Semantic, 0.88, ); let node4 = Node::new( NodeType::Process, vec![0.2, 0.3, 0.8, 0.9, 0.1, 0.7, 0.6, 0.2], b"Salience decay: forgetting as adaptive pruning, not failure".to_vec(), MemoryTier::Procedural, 0.75, ); let node5 = Node::new( NodeType::Event, vec![0.3, 0.2, 0.7, 0.8, 0.2, 0.6, 0.7, 0.1], b"Memory consolidation during sleep: hippocampal replay to neocortex".to_vec(), MemoryTier::Episodic, 0.70, ); let id0 = db.put_node(node0)?; let id1 = db.put_node(node1)?; let id2 = db.put_node(node2)?; let id3 = db.put_node(node3)?; let id4 = db.put_node(node4)?; let id5 = db.put_node(node5)?; println!("Inserted {} nodes", db.node_count()?); println!(" [0] Spreading activation concept (seed)"); println!(" [1] Long-term potentiation"); println!(" [2] Hebbian learning"); println!(" [3] Associative memory"); println!(" [4] Salience decay (procedural)"); println!(" [5] Memory consolidation (episodic)"); println!(); // ── 3. Create edges ─────────────────────────────────────────────────────── // // Edge weights model associative strength. Strong weights (0.9) mean these // concepts reliably co-activate. Weaker weights mean looser association. // Spreading activation Causes long-term potentiation (strong causal link) db.put_edge(Edge::new(id0, id1, RelationType::Causes, 0.9))?; // LTP is Referenced by Hebbian learning db.put_edge(Edge::new(id1, id2, RelationType::References, 0.85))?; // Spreading activation Activates associative memory db.put_edge(Edge::new(id0, id3, RelationType::Activates, 0.88))?; // Hebbian learning Exemplifies associative memory db.put_edge(Edge::new(id2, id3, RelationType::Exemplifies, 0.80))?; // Salience decay Supersedes naive forgetting db.put_edge(Edge::new(id4, id5, RelationType::TemporallyPrecedes, 0.65))?; // LTP TemporallyPrecedes memory consolidation db.put_edge(Edge::new(id1, id5, RelationType::TemporallyPrecedes, 0.72))?; println!("Inserted {} edges", db.edge_count()?); println!(" node0 --[Causes]--> node1"); println!(" node1 --[References]--> node2"); println!(" node0 --[Activates]--> node3"); println!(" node2 --[Exemplifies]--> node3"); println!(" node4 --[TemporallyPrecedes]--> node5"); println!(" node1 --[TemporallyPrecedes]--> node5"); println!(); // ── 4. Spreading activation ─────────────────────────────────────────────── // // Seed: node0 (spreading activation concept) // Query embedding: similar to node3 (associative memory) — high in dims 4,5 // This should surface node3 strongly and pull in node2 via the Hebbian path. let query_embedding = vec![0.65, 0.72, 0.35, 0.28, 0.92, 0.12, 0.18, 0.58]; println!("=== Spreading Activation ==="); println!("Seed: node0 (spreading activation concept)"); println!("Query: similar to node3 (associative memory)"); println!("Max depth: 3 hops, returning top 10"); println!(); let activated: Vec = db.activate(&[id0], &query_embedding, 3, 10)?; if activated.is_empty() { println!(" (no nodes activated — check salience values)"); } else { for a in &activated { let content = String::from_utf8_lossy(&a.node.content); // Truncate content for display let display = if content.len() > 60 { format!("{}...", &content[..60]) } else { content.to_string() }; println!( " strength={:.4} hops={} salience={:.4} tier={:?}", a.activation_strength, a.hops, a.node.salience, a.node.tier ); println!(" \"{}\"", display); } } println!(); // ── 5. Vector similarity search ─────────────────────────────────────────── // // Pure cosine scan: no graph structure, just embedding proximity. // Should return nodes with embeddings most similar to the query. println!("=== Vector Similarity Search (top 3) ==="); println!("Query: associative memory embedding"); println!(); let scored = db.search_embedding(&query_embedding, 3)?; for s in &scored { let content = String::from_utf8_lossy(&s.node.content); let display = if content.len() > 60 { format!("{}...", &content[..60]) } else { content.to_string() }; println!( " cosine={:.4} tier={:?} type={:?}", s.score, s.node.tier, s.node.node_type ); println!(" \"{}\"", display); } println!(); // ── 6. Node and edge counts ─────────────────────────────────────────────── println!("=== Database Statistics ==="); println!(" Nodes: {}", db.node_count()?); println!(" Edges: {}", db.edge_count()?); println!(); // ── 7. Salience decay ───────────────────────────────────────────────────── // // Apply 5% decay to all node saliences. This simulates the passage of time. // Nodes that haven't been activated recently become less salient, // modeling the adaptive nature of forgetting. println!("=== Salience Decay (factor=0.95) ==="); let updated = db.decay(0.95)?; println!(" Updated {} nodes", updated); println!(); // Show salience before/after for a sample node if let Some(n) = db.get_node(id0)? { println!( " node0 salience after decay: {:.6}", n.salience ); } // Show traversal from node0 println!(); println!("=== Graph Traversal from node0 (depth=2) ==="); let reachable = db.traverse(id0, None, 2)?; println!(" Reachable nodes (any relation, max 2 hops): {}", reachable.len()); for n in &reachable { let content = String::from_utf8_lossy(&n.content); let display = if content.len() > 55 { format!("{}...", &content[..55]) } else { content.to_string() }; println!(" [{:?}] \"{}\"", n.tier, display); } println!(); // ── 8. Consolidation ────────────────────────────────────────────────────── // // Touch node2 (Hebbian learning — Episodic) several times to simulate it // being frequently recalled, then run consolidation to promote it. println!("=== Memory Consolidation ==="); // Simulate repeated activation: touch node2 six times so it crosses the // default threshold of 5 activations. for _ in 0..6 { db.touch(id2)?; } // Confirm the node's activation count has increased. if let Some(n) = db.get_node(id2)? { println!(" node2 (Episodic) activation_count before consolidation: {}", n.activation_count); println!(" node2 tier before consolidation: {:?}", n.tier); } let config = ConsolidationConfig { episodic_to_semantic_threshold: 5, salience_floor: 0.0, // no salience floor — accept any salient node max_promotions_per_run: 10, decay_factor: 0.98, }; let report = db.consolidate(&config)?; println!(" Promoted {} Episodic → Semantic", report.promoted); println!(" Decayed {} nodes", report.decayed); // Confirm node2 has been promoted. if let Some(n) = db.get_node(id2)? { println!(" node2 tier after consolidation: {:?}", n.tier); } println!(); println!("Done. Engram v0.1.1."); Ok(()) }