engramai
Neuroscience-grounded memory for AI agents. ACT-R activation, Hebbian learning, Ebbinghaus forgetting, cognitive consolidation, vector embeddings, and LLM extraction.
Features
| Feature | Description |
|---|---|
| ACT-R Activation | Retrieval based on frequency, recency, spreading activation |
| Ollama Embeddings | Semantic similarity via local embeddings (nomic-embed-text) |
| LLM Extraction | Extract key facts using Claude Haiku or local models |
| EmotionBus | Emotional valence tracking, drive alignment (multilingual), behavior feedback |
| Session Working Memory | Miller's Law 7±2, topic continuity detection |
| Confidence Calibration | Two-dimensional metacognitive monitoring |
| Hybrid Search | Adaptive vector + FTS with configurable weights (15% FTS + 60% embedding + 25% ACT-R) |
| CJK Tokenization | jieba-based Chinese/Japanese word segmentation for precise FTS matching |
| Multi-Agent | Namespaces, ACL permissions, subscriptions |
| Hebbian Learning | Associative links from co-activation |
| Ebbinghaus Forgetting | Exponential decay with spaced repetition |
Quick Start
Installation
[]
= "0.2.1"
Basic Usage
use ;
let mut mem = new?;
// Store a memory
mem.add?;
// Recall with semantic search + ACT-R
let results = mem.recall?;
With LLM Extraction (Recommended)
Instead of storing raw text, extract key facts using an LLM:
use ;
let mut mem = new?;
// Option 1: Auto-detect from environment
// Just set ANTHROPIC_API_KEY=sk-ant-... and it works automatically
// Option 2: Explicit setup
mem.set_extractor;
// Now add() automatically extracts facts
mem.add?;
// Stores: "user likes pizza", "user's girlfriend doesn't like pizza" (separate entries)
With Embeddings
use ;
let config = MemoryConfig ;
let mut mem = new?;
// Recall now uses semantic similarity + ACT-R activation
Configuration
Engram supports layered configuration with clear priority:
Auth Priority (high → low)
- Code-level
set_extractor()— for agent harnesses (RustClaw, etc.) - Environment variable
ANTHROPIC_AUTH_TOKEN/ANTHROPIC_API_KEY - Config file provider setting + env var for auth
- No extractor — stores raw text (backward compatible)
Environment Variables
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key for Haiku extraction |
ANTHROPIC_AUTH_TOKEN |
OAuth token (Claude Max plan) |
ENGRAM_EXTRACTOR_MODEL |
Override extractor model (default: claude-haiku-4-5-20251001) |
ENGRAM_DB |
Default database path for CLI |
ENGRAM_EMBEDDING_MODEL |
Override embedding model |
ENGRAM_EMBEDDING_HOST |
Override Ollama host |
Config File
Location varies by platform:
- Linux:
~/.config/engram/config.json - macOS:
~/Library/Application Support/engram/config.json - Windows:
%APPDATA%\engram\config.json
⚠️ Security: Never store API keys in the config file. Use environment variables or pass tokens in code.
Create config interactively:
Search Weights (Hybrid Recall)
Control how recall() balances FTS exact matching, semantic similarity, and temporal activation:
use MemoryConfig;
let mut config = default;
config.fts_weight = 0.15; // 15% exact term matching (via FTS)
config.embedding_weight = 0.60; // 60% semantic similarity (via embeddings)
config.actr_weight = 0.25; // 25% recency/frequency (via ACT-R)
let mut mem = new?;
Recommended presets:
| Use case | FTS | Embedding | ACT-R | Description |
|---|---|---|---|---|
| Default (balanced) | 0.15 | 0.60 | 0.25 | Good for most agents |
| Keyword-focused | 0.30 | 0.50 | 0.20 | Project names, exact terms matter |
| Semantic-heavy | 0.10 | 0.70 | 0.20 | Concept search, less exact matching |
| Recency-biased | 0.10 | 0.50 | 0.40 | Recent events most important |
💡 Tip: Weights should sum to ~1.0 for intuitive percentage interpretation.
CJK Tokenization
For Chinese/Japanese/Korean text, Engram uses jieba for intelligent word segmentation:
❌ Without jieba:
"engram是认知记忆系统" → "engram" "是" "认" "知" "记" "忆" "系" "统"
Search "记忆系统" matches poorly (3 separate characters)
✅ With jieba:
"engram是认知记忆系统" → "engram" "是" "认知" "记忆系统"
Search "记忆系统" matches precisely (as a complete term)
Performance:
- First jieba load: ~220ms (once per process)
- Per-memory tokenization: <0.02ms (negligible overhead)
- Applies to FTS path only (15% of recall weight)
No configuration needed — jieba auto-activates for CJK text.
Architecture
Memory Pipeline
User message
↓
[Extractor] → LLM extracts key facts (Claude Haiku, optional)
↓
[Embedding] → Generate 768-dim vector (Ollama nomic-embed-text, local)
↓
[Drive Alignment] → Cosine similarity with pre-embedded SOUL drives (if EmotionBus active)
↓ → Importance boost for drive-aligned memories
[Storage] → SQLite (text + FTS5 + vector BLOB + CJK tokenization)
↓
Query
↓
[Embedding] → Generate query vector (same model)
↓
[Hybrid Search] → 15% FTS + 60% embedding cosine + 25% ACT-R activation
↓
[Confidence] → Two-dimensional scoring (reliability × salience)
↓
Results with confidence labels
Embedding Model
Engram uses nomic-embed-text via local Ollama by default:
| Property | Value |
|---|---|
| Model | nomic-embed-text (Nomic AI, open source) |
| Dimensions | 768 |
| Runtime | Local Ollama (localhost:11434) |
| Cost | Free (no API calls) |
| Multilingual | Yes (sufficient for CJK ↔ Latin alignment) |
| Latency | ~5ms per embedding |
Configurable via EmbeddingConfig — swap to OpenAI text-embedding-3-large or any Ollama model without code changes.
Cognitive Models
- ACT-R: Base-level activation (frequency × recency power law) + spreading activation from context
- Ebbinghaus: Exponential forgetting curve, counteracted by consolidation
- Hebbian: "Neurons that fire together wire together" — co-activated memories form links
- STDP: Temporal ordering creates causal links during consolidation
- Miller's Law: Working memory limited to 7±2 chunks (SessionWorkingMemory)
CLI
# Install
# Initialize config
# Store memories (with extraction)
# Recall
# Manage
For Agent Developers
Integration with Agent Harness
use ;
// In your agent's init:
let mut mem = new?;
// Set up extraction using your agent's existing LLM auth
let token = your_agent.get_oauth_token?;
mem.set_extractor;
// Before LLM call: auto-recall relevant context
let memories = mem.recall?;
let context = memories.iter
.map
.
.join;
// After LLM response: auto-store important facts
mem.add?;
// Extractor automatically extracts facts from the conversation
Session Working Memory
use SessionWorkingMemory;
let mut wm = new; // 7 items, 5min decay
// Smart recall: only full search on topic change
let result = mem.session_recall?;
if result.full_recall else
Multi-Agent Shared Memory
use ;
let mut mem = new?;
// Each agent writes to its own namespace
mem.set_agent_id;
mem.add_to_namespace?;
// CEO queries across all namespaces
mem.set_agent_id;
let results = mem.recall_from_namespace?;
// ACL: CEO controls access
mem.grant?;
Emotional Bus
use Memory;
// Initialize with emotional bus connected to workspace files
let mut mem = with_emotional_bus?;
// Store with emotional tagging
mem.add_with_emotion?;
// Get emotional trends
if let Some = mem.emotional_bus
Cross-Language Drive Alignment
Engram's EmotionalBus automatically handles multilingual SOUL drives using embedding-based alignment:
Problem: SOUL.md in Chinese, agent receives English messages
SOUL drive: "帮potato实现财务自由,找到市场机会"
Message: "trading profit market opportunity"
Keyword matching: score = 0.0 ❌ (字面不匹配)
Solution: Hybrid alignment (keyword + embedding)
Keyword score: 0.0 (cross-language, can't match)
Embedding score: 0.14 (semantic similarity via nomic-embed-text)
Final score: max(0.0, 0.14) = 0.14 ✅
Drive descriptions are pre-embedded at startup. At store time, content embeddings (already computed for recall) are reused for alignment — zero additional embedding cost.
use Memory;
let mut mem = with_emotional_bus?;
// If embedding provider is available, enable cross-language alignment
if let =
// Importance boost works regardless of language
let boost = bus.align_importance; // > 1.0 if aligned
let boost = bus.align_importance; // > 1.0 if aligned
let boost = bus.align_importance; // = 1.0 (not aligned)
How it works:
nomic-embed-text(768-dim) maps semantically similar content to nearby vectors regardless of languagescore_alignment_hybrid()=max(keyword_score, embedding_score)— best of both worlds- Same-language: keyword matching wins (precise, score=1.0)
- Cross-language: embedding matching wins (semantic, score=0.1-0.3)
- Unrelated content: both return 0.0
Memory Types
| Type | Use case | Default importance |
|---|---|---|
Factual |
Facts and knowledge | 0.3 |
Episodic |
Events and experiences | 0.4 |
Relational |
Knowledge about people/entities | 0.6 |
Emotional |
Emotionally significant (slow decay) | 0.9 |
Procedural |
How-to knowledge (slow decay) | 0.5 |
Opinion |
Subjective beliefs | 0.3 |
Causal |
Cause-effect relationships | 0.7 |
Configuration Presets
use MemoryConfig;
let config = chatbot; // Slow decay, high replay
let config = task_agent; // Fast decay, recent context
let config = personal_assistant; // Very slow decay, months of memory
let config = researcher; // Minimal forgetting
Performance
| Operation | 500 memories |
|---|---|
| Store | 69ms (~0.14ms each) |
| Recall | 5ms |
| Consolidate | 60ms |
| Binary size | ~5MB |
| Memory footprint | ~5MB |
API Reference
Core Memory
| Method | Description |
|---|---|
Memory::new(path, config) |
Create or open database (auto-configures extractor) |
Memory::with_emotional_bus(path, workspace, config) |
Create with emotional bus |
mem.add(content, type, importance, source, metadata) |
Store a memory |
mem.add_to_namespace(...) |
Store to specific namespace |
mem.recall(query, limit, context, min_confidence) |
Retrieve with ACT-R ranking |
mem.recall_from_namespace(...) |
Retrieve from namespace ("*" for all) |
mem.set_extractor(extractor) |
Override auto-configured extractor |
mem.clear_extractor() |
Disable extraction |
mem.consolidate(days) |
Run consolidation cycle |
mem.forget(memory_id, threshold) |
Prune weak memories |
mem.reward(feedback, recent_n) |
Dopaminergic feedback |
mem.stats() |
Memory system statistics |
Multi-Agent ACL
| Method | Description |
|---|---|
mem.set_agent_id(id) |
Set current agent identity |
mem.grant(agent_id, namespace, permission) |
Grant access |
mem.revoke(agent_id, namespace) |
Revoke access |
mem.check_permission(agent_id, namespace, action) |
Check access |
Cross-Agent Intelligence
| Method | Description |
|---|---|
mem.discover_cross_links(ns_a, ns_b) |
Find Hebbian associations across namespaces |
mem.subscribe(agent_id, namespace, min_importance) |
Subscribe to namespace notifications |
mem.check_notifications(agent_id) |
Get new notifications |
Python vs Rust
| Feature | Python 2.1 | TypeScript 2.1 | Rust 0.2.1 |
|---|---|---|---|
| ACT-R activation | ✅ | ✅ | ✅ |
| Hebbian learning | ✅ | ✅ | ✅ |
| Ebbinghaus forgetting | ✅ | ✅ | ✅ |
| Consolidation | ✅ | ✅ | ✅ |
| Hybrid Search (FTS+Embed+ACT-R) | ✅ | ✅ | ✅ |
| CJK Tokenization | ❌ | ❌ | ✅ (jieba) |
| LLM Extraction | ❌ | ❌ | ✅ (Haiku) |
| Emotional Bus | ❌ | ❌ | ✅ |
| Cross-Language Alignment | ❌ | ❌ | ✅ (embedding) |
| Multi-Agent / Namespace | ❌ | ❌ | ✅ |
| ACL | ❌ | ❌ | ✅ |
| Cross-Agent Subscriptions | ❌ | ❌ | ✅ |
| Vector embeddings | ✅ (Ollama) | ✅ (Ollama) | ✅ (Ollama) |
| MCP server | ✅ | ❌ | ⏳ planned |
| Recall latency | ~10ms | ~8ms | ~1-5ms |
| Memory footprint | ~50MB | ~30MB | ~5MB |
| Deployment | Requires Python | Requires Node | Single binary |
License
AGPL-3.0-or-later — see LICENSE.
Citation