This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
engram/examples/basic.rs
T
Will Anderson f33d789471 feat: HNSW index, consolidation engine, Kotlin/TS/Go bindings, SQLite migration connector
- vector.rs: replace flat O(n) scan with instant-distance HNSW for stores
  >= 100 nodes; flat scan retained as fallback for small graphs; dirty-flag
  persistence in sled triggers index rebuild only when nodes are added

- consolidation.rs: Episodic → Semantic promotion based on activation_count
  and salience_floor thresholds; global decay pass after each cycle;
  ConsolidationConfig + ConsolidationReport types; 8 tests

- migration.rs: reads Neuron SQLite (memory_nodes, knowledge_entries,
  graph_edges) and writes to Engram sled; placeholder unit-vector embeddings
  with TODO for ONNX; 5 tests including full in-memory DB roundtrip

- crates/engram-migrate: CLI binary (engram-migrate --sqlite / --output)

- crates/engram-jni: JNI cdylib exposing open/close/put_node/get_node/
  activate/search_embedding/touch/decay/node_count/edge_count via
  Java_ai_neuron_engram_EngramDb_* entry points; 6 tests

- bindings/kotlin: EngramDb.kt (AutoCloseable JNI wrapper), EngramNode,
  EngramEdge, ActivatedNode, EngramTypes; build.gradle.kts; settings.gradle.kts

- bindings/typescript: engram-wasm crate (wasm-bindgen, serde-wasm-bindgen);
  WasmEngramDb with in-memory backend (sled not available in WASM);
  TypeScript wrapper (index.ts, types.ts, package.json, tsconfig.json)

- bindings/go: engram.go (CGo wrapper), engram.h (C header), engram_test.go
  (4 tests covering open/close/put_node/get_node/node_count/decay); go.mod

- engram-core: wasm feature gate for in-memory backend; mem_storage.rs;
  activation.activate_mem for WASM path; Node::with_id helper;
  salience.rs doctest fixed (text block)

- examples/basic.rs: consolidation section added
- examples/migrate.rs: migration API demonstration

Build: cargo build --workspace -- zero warnings, zero errors
Tests: 38 pass (25 engram-core + 7 engram-ffi + 6 engram-jni)
2026-04-27 16:00:47 -05:00

265 lines
10 KiB
Rust

/// 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<dyn std::error::Error>> {
// ── 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<ActivatedNode> = 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(())
}