Adaptive Memory
An associative memory system using spreading activation. Memories are stored in SQLite with FTS5 full-text search, and retrieved using BM25 text matching combined with graph-based activation spreading through explicit relationships.
Core Concepts
Memories
Entries with id, datetime, text, and optional source. Stored in SQLite with FTS5 full-text indexing. IDs are sequential integers assigned on insertion.
Relationships
Symmetric connections between memories. Created only via explicit strengthen calls - no auto-generated relationships. Multiple strengthen events accumulate; effective strength is the sum of all events (with optional decay).
Relationships are stored canonically (from_mem < to_mem) as an event log. This allows strength to build up over time through repeated strengthening.
Spreading Activation
Search works by:
- FTS5 Search: Find memories matching query using BM25 ranking
- Seed Selection: Top BM25 results become seeds with energy 0.1-1.0 (proportional to relevance)
- Energy Propagation: Energy spreads through relationship graph
- Energy is distributed across neighbors (PageRank-style normalization)
- Each hop multiplies by
energy_decay(default 0.5) - Propagation stops when energy < 0.01 threshold
- Results: Memories sorted by ID (timeline order) with accumulated energy scores
Context Expansion
Instead of pre-computed temporal relationships, use --context N to fetch N memories before/after each result by ID. This is like grep -B/-A for temporal context.
Installation
# Binary at: target/release/adaptive-memory
CLI Usage
adaptive-memory [OPTIONS] <COMMAND>
Commands:
init Initialize the database
add Add a new memory
search Search for memories
strengthen Strengthen relationships between memories
Global Options:
--db <PATH> Database path (default: ~/.adaptive_memory.db)
Initialize Database
Add Memory
)
)
Examples:
# Simple memory
# With source
# Historical entry
Output:
Search Memories
)
)
)
)
Examples:
# Basic search
# With temporal context (like grep -B2 -A2)
# Limit results
# Deeper activation spread (reach more distant associations)
Output:
Results are sorted by memory ID (timeline order). The energy field indicates relevance:
- ~1.0 = direct BM25 match
- ~0.5 = one hop from a seed
- < 0.1 = reached via multi-hop spreading
Context items (from --context) have energy: 0.0 and is_context: true.
Strengthen Relationships
Create explicit associations between memories.
<IDS> Comma-separated )
Examples:
# Link two related memories (adds 1.0 strength)
# Link multiple (creates all pairs, 1.0 each)
# 4 IDs = 6 pairs, each gets 1.0 strength
Output:
FTS5 Query Syntax
The search query uses SQLite FTS5 syntax, which supports powerful search operators:
| Syntax | Meaning | Example |
|---|---|---|
word |
Match word | meeting |
word1 word2 |
Match both (implicit AND) | project meeting |
word1 OR word2 |
Match either | cat OR dog |
"phrase" |
Exact phrase | "weekly standup" |
word* |
Prefix match | meet* matches meeting, meetings |
NOT word |
Exclude | meeting NOT standup |
NEAR(w1 w2, N) |
Words within N tokens | NEAR(rust memory, 5) |
^word |
Match at start of field | ^TODO |
Special characters: Characters like +, -, @ have special meaning in FTS5. To search for literal special characters, quote them: "2024-01-15" or "email@example.com".
Examples:
# All memories with "rust" AND "async"
# Either term
# Exact phrase
# Prefix matching
# Exclude term
# Words near each other
Library Usage
use ;
Configuration
Compile-time Constants (src/lib.rs)
| Constant | Default | Description |
|---|---|---|
ENERGY_THRESHOLD |
0.01 | Stop propagation below this energy |
MAX_SPREADING_ITERATIONS |
5000 | Safety limit on activation iterations |
MAX_STRENGTHEN_SET |
10 | Max memories per strengthen call |
DEFAULT_LIMIT |
100 | Default result limit |
Runtime Parameters (SearchParams)
| Parameter | Default | Description |
|---|---|---|
limit |
100 | Max results (also seed count for FTS) |
decay_factor |
0.0 | Relationship strength decay over memory distance |
energy_decay |
0.5 | Energy multiplier per hop (0.5 = halves each hop) |
context |
0 | Fetch N memories before/after each result |
Tuning energy_decay
Controls how far activation spreads through the graph:
| Value | Behavior | Max Depth |
|---|---|---|
| 0.3 | Shallow spread, stick close to seeds | ~4 hops |
| 0.5 | Balanced (default) | ~7 hops |
| 0.7 | Deep spread, reach distant associations | ~12 hops |
Energy at each hop (starting from seed with energy 1.0):
Hop: 0 1 2 3 4
0.5: 1.0 0.50 0.25 0.125 0.0625
0.7: 1.0 0.70 0.49 0.343 0.240
Tuning decay_factor
Controls how relationship strength fades over memory distance:
effective_strength = stored_strength × exp(-distance × decay_factor)
| Value | At 10 memories | At 50 memories | At 100 memories |
|---|---|---|---|
| 0.0 | 100% (no decay) | 100% | 100% |
| 0.01 | 90% | 61% | 37% |
| 0.03 | 74% | 22% | 5% |
| 0.05 | 61% | 8% | 0.7% |
Default is 0.0 (no decay). The ln_1p compression and PageRank-style normalization already prevent old relationships from dominating.
Database Schema
(
id INTEGER PRIMARY KEY,
datetime TEXT NOT NULL,
text TEXT NOT NULL,
source TEXT
);
CREATE VIRTUAL TABLE memories_fts USING fts5(text, content=memories, content_rowid=id);
(
id INTEGER PRIMARY KEY,
from_mem INTEGER NOT NULL,
to_mem INTEGER NOT NULL,
created_at_mem INTEGER NOT NULL,
strength REAL NOT NULL,
CHECK (from_mem < to_mem)
);
How It Works
Adding a Memory
- Insert into
memoriestable - FTS5 trigger auto-indexes the text
- No relationships created (use
strengthenor--contextfor associations)
Searching
- FTS5: BM25-ranked text matches become seeds
- Spreading Activation:
- Seeds get energy proportional to BM25 score
- Energy spreads through relationships (delta propagation)
- Neighbors' strengths are normalized (sum to 1.0) - energy is distributed, not amplified
- Raw strength is compressed via
ln(1+x)for diminishing returns
- Context Expansion: Optionally fetch surrounding memories by ID
- Results: Sorted by memory ID (timeline order)
Strengthening
- For each pair of IDs, add relationship event with strength 1.0
- Events accumulate - the pair's effective strength grows with repeated strengthening
- ln_1p compression means: 1st event → 0.69 effective, 10 events → 2.40, 100 events → 4.62
Tips
- Source field: Tag memories for filtering/identification (e.g., "slack", "journal", "calendar")
- Strengthen after retrieval: If a search surfaces related memories, strengthen them to reinforce the association
- Context for temporal: Use
--context Ninstead of pre-computed temporal links - Batch import: Use
-dto preserve original timestamps when importing historical data - Quote special chars: FTS5 special characters (
+,-,*, etc.) should be quoted for literal matching
License
MIT