v0.1.0 · Local-first memory substrate

Engram

The physical trace of a memory.
A database designed for minds, not tables.

Scroll
The Problem

Every existing database
gets memory wrong

Relational

You store rows. You query rows. Storage and retrieval are entirely separate systems. The structure that holds the data knows nothing about how you will need it back.

Vector

You store embeddings. You search by geometric proximity. Still fundamentally a query against static, passive data — the structure has no opinion about your context.

Graph

You store edges. You traverse paths. Still asking questions of a static structure. The graph doesn't activate — you query it from outside, like a stranger reading a map.

The brain doesn't query.
It activates.

Core Mechanism

Spreading Activation

Click any node to set it as the seed. Hit Activate to watch the pattern propagate. Activation flows outward through weighted edges — attenuating at every hop, pruned when too weak to matter.

Seed
Click a node to select
Speed
Formula strength = parent × edge_weight × salience × cos_sim(query, target) Multiplicative — every factor must be non-trivial for the path to survive
Memory Architecture

The Four Memory Tiers

Nodes migrate between tiers based on salience and reinforcement — exactly as memories migrate between the hippocampus and neocortex through activation and sleep.

🔥
Working
Prefrontal cortex — hot, volatile

The K most recently activated nodes. Ultra-fast access. Evicted by recency when capacity is exceeded. What you're actively thinking about right now.

// Currently active
task: "Implement activation BFS"
context: spreading_activation_node
recent: hebbian_learning_concept
📖
Episodic
Hippocampus — time-ordered experience

Time-stamped events and raw experiences. What happened, and when. The raw feed of observations before they have been abstracted into knowledge.

2026-04-27T14:23Z event:
"Will explained Dharma Registry.
Patterns logged. ISE confirmed."
🧠
Semantic
Neocortex — stable knowledge

The concept graph with weighted associations. Long-term structural knowledge. What you know, abstracted from any specific event that taught it to you.

concept: "spreading_activation"
→ Causes: "long_term_potentiation"
→ Activates: "associative_memory"
salience: 0.82, activations: 47
⚙️
Procedural
Cerebellum — patterns and habits

Encoded patterns, workflows, and repeatable processes. How to do things. Retrieved by similarity to current task context, not by conscious recall.

process: "commit_workflow"
steps: [stage → test → commit
→ push → check_ci]
triggered by: git_context_match
Forgetting as Design

Salience — the attention filter

Every node has a salience score. It governs whether a node surfaces during retrieval. It decays. It strengthens on activation. Adjust the sliders to see how the three signals combine.

The Salience Formula
salience = importance
salience = × 1 / (1 + days_since)
salience = × ln(activation_count + 1)
importance Explicit weight at creation. Stable over time. You set it once.
recency At activation: 1.0. After 1 day: 0.5. After 6 days: ~0.14. Asymptotic toward zero.
frequency Log-compressed: 0→1 activation matters more than 100→101. Diminishing returns.
Importance 0.85
Days since last activation 2
Activation count 12
Computed Salience
0.00

"Forgetting is not failure. It is the system prioritising what matters."

Memory Lifecycle

Consolidation

In the brain, memories consolidate during sleep — the hippocampus replays experiences into the neocortex until they become stable knowledge. Engram makes this explicit.

Episodic

Raw events. Time-stamped experience. Not yet abstract.

tier: Episodic
activation_count: 2
salience: 0.41
activation_count ≥ 5
salience ≥ 0.3
→ promote on consolidate()
Semantic

Stable concept. Integrated knowledge. No longer tied to a specific event.

tier: Semantic
activation_count: 7
salience: 0.68
Promotion is earned by use — activated, reinforced, and found relevant repeatedly over time.
System Design

Architecture

Three retrieval primitives layered over a single embedded store. No daemon. No network. No server to babysit.

[ Your Intelligence ]
EngramDb API
Spreading Activation
Best-first BFS · multiplicative weights · pruning at 0.01
Vector Search
Flat cosine scan · semantic direction filter · secondary signal
Graph Traversal
BFS by relation type · typed edges · max depth
sled — embedded persistent B-tree · bincode serialization · transactional
[ Your disk — no daemon · no network · local-first ]
Why multiplication?

Addition lets many weak signals accumulate into false relevance. Multiplication is conjunctive: a weak edge, a dormant node, or a semantically irrelevant target all kill the path. This is how associative memory actually works.

Why sled?

Local-first. Transactional. No daemon process, no network socket. HNSW indexing layers on top when needed — the graph structure itself is the primary retrieval mechanism.

Why pruning at 0.01?

Small enough to allow long indirect chains when intermediate edges are strong. Raise it to focus retrieval; lower it for more associative drift. The brain's attention filter, made explicit.

Language Bindings

Use from anywhere

A C FFI layer exposes the full API. Language bindings ship for Kotlin, TypeScript, Go, and Rust natively.

basic.rs
use engram_core::{EngramDb, Node, Edge, NodeType, MemoryTier, RelationType};

// Open or create a local database — no server, no daemon
let db = EngramDb::open(Path::new("/var/lib/agent/memory"))?;

// Store a concept with a semantic embedding
let id = db.put_node(Node::new(
    NodeType::Concept,
    embedding,        // Vec<f32> from your LLM
    content,          // Vec<u8> — any payload
    MemoryTier::Semantic,
    0.9,             // importance 0.0–1.0
))?;

// Link to a related concept
db.put_edge(Edge::new(id, related_id, RelationType::Causes, 0.88))?;

// Retrieve by spreading activation — not a query, a pattern completion
let results = db.activate(
    &[id],              // seed nodes
    &query_embedding,  // direction of thought
    3,                 // max hops
    10,                // top-N results
)?;

for r in &results {
    println!("strength={:.4} hops={}", r.activation_strength, r.hops);
}
Memory.kt
import ai.neuron.engram.*

// JVM binding via JNI — same embedded storage, no server
val db = EngramDb.open("/data/user/0/ai.agent/memory")

// Store an episodic event
val id = db.putNode(
    Node(
        nodeType = NodeType.EVENT,
        embedding = llm.embed(text),
        content = text.toByteArray(),
        tier = MemoryTier.EPISODIC,
        importance = 0.8f
    )
)

// Activate from current context
val recalled = db.activate(
    seeds = listOf(id),
    queryEmbedding = currentContext.embedding,
    maxDepth = 3,
    limit = 10
)

recalled.forEach {
    println("[${it.hops} hops] strength=${it.activationStrength}")
}
memory.ts
import { EngramDb, NodeType, MemoryTier, RelationType } from "@neuron/engram";

// WASM build — runs in Node or browser, fully in-process
const db = await EngramDb.open("./agent-memory");

// Store a concept node
const id = await db.putNode({
  nodeType: NodeType.Concept,
  embedding: await llm.embed(text),
  content: new TextEncoder().encode(text),
  tier: MemoryTier.Semantic,
  importance: 0.85,
});

// Spreading activation — pattern completion, not query
const recalled = await db.activate({
  seeds: [id],
  queryEmbedding: currentContext.embedding,
  maxDepth: 3,
  limit: 10,
});

for (const node of recalled) {
  console.log(`strength=${node.activationStrength.toFixed(4)} hops=${node.hops}`);
}
memory.go
import (
    engram "github.com/neuron-technologies/engram-go"
)

// CGo binding — links the Rust library, zero copies for embeddings
db, err := engram.Open("/var/lib/agent/memory")
if err != nil {
    return err
}
defer db.Close()

// Store a node
id, err := db.PutNode(engram.Node{
    NodeType:   engram.Concept,
    Embedding:  embedding,
    Content:    []byte(text),
    Tier:       engram.Semantic,
    Importance: 0.9,
})

// Activate from seed — the graph does the retrieval
results, err := db.Activate(
    []engram.UUID{id},
    queryEmbedding,
    3,   // maxDepth
    10,  // limit
)

for _, r := range results {
    fmt.Printf("strength=%.4f hops=%d\n", r.Strength, r.Hops)
}
Philosophy

Why local

Your memory is not a service.
It is you.

It lives on your hardware, under your control.
It does not leave your device.
It does not phone home.

It does not require a network connection
to remember who you are.

🔒
No telemetry
📡
No network required
🗄️
Embedded storage
No daemon process
Build Status

Current state

Version
v0.1.0
Build
Zero warnings · Zero errors
Test Suite
38 passing
Public API Surface · engram-core v0.1.0
impl EngramDb {
    fn open(path: &Path)                                          -> EngramResult<Self>;
    fn put_node(&self, node: Node)                                 -> EngramResult<Uuid>;
    fn get_node(&self, id: Uuid)                                 -> EngramResult<Option<Node>>;
    fn put_edge(&self, edge: Edge)                                -> EngramResult<()>;
    fn activate(&self, seeds: &[Uuid], emb: &[f32], depth: u8, n: usize)  -> EngramResult<Vec<ActivatedNode>>;
    fn search_embedding(&self, emb: &[f32], limit: usize)          -> EngramResult<Vec<ScoredNode>>;
    fn traverse(&self, from: Uuid, rel: Option<RelationType>, depth: u8) -> EngramResult<Vec<Node>>;
    fn touch(&self, id: Uuid)                                    -> EngramResult<()>;
    fn decay(&self, factor: f32)                                  -> EngramResult<usize>;
    fn consolidate(&self, config: &ConsolidationConfig)          -> EngramResult<ConsolidationReport>;
}