| Crates.io | adaptive_memory |
| lib.rs | adaptive_memory |
| version | 0.2.1 |
| created_at | 2026-01-05 04:34:19.25156+00 |
| updated_at | 2026-01-05 16:54:45.747214+00 |
| description | An associative memory system using spreading activation with SQLite FTS5 full-text search |
| homepage | |
| repository | https://github.com/spoj/adaptive_memory |
| max_upload_size | |
| id | 2023089 |
| size | 194,097 |
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.
Entries with id, datetime, text, and optional source. Stored in SQLite with FTS5 full-text indexing. IDs are sequential integers assigned on insertion.
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.
Search works by:
energy_decay (default 0.5)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.
cargo install adaptive_memory
git clone https://github.com/spoj/adaptive_memory
cd adaptive_memory
cargo build --release
# Binary at: target/release/adaptive-memory
adaptive-memory [OPTIONS] <COMMAND>
Commands:
init Initialize the database
add Add a new memory
amend Amend (update) an existing memory's text
search Search for memories
strengthen Strengthen relationships between memories
connect Connect memories (only if no existing relationship)
tail Show the latest N memories
list List memories by ID range
stats Show database statistics
stray Sample unconnected (stray) memories
Global Options:
--db <PATH> Database path (default: ~/.adaptive_memory.db)
adaptive-memory init
adaptive-memory add [OPTIONS] <TEXT>
Options:
-s, --source <SOURCE> Source identifier (e.g., "journal", "slack")
-d, --datetime <DATETIME> Override datetime (RFC3339 format)
Examples:
# Simple memory
adaptive-memory add "Had coffee with Sarah, discussed the new project"
# With source
adaptive-memory add "Reviewed PR #123" -s "github"
# Historical entry
adaptive-memory add "Started learning Rust" -d "2023-06-15T10:00:00Z"
Output:
{
"memory": {
"id": 42,
"datetime": "2026-01-05T12:30:00Z",
"text": "Had coffee with Sarah, discussed the new project",
"source": null
}
}
adaptive-memory search [OPTIONS] <QUERY>
Options:
-l, --limit <N> Maximum results (default: 100)
-c, --context <N> Fetch N memories before/after each result (default: 0)
--decay <FACTOR> Relationship decay over memory distance (default: 0)
--energy-decay <FACTOR> Energy multiplier per hop (default: 0.5)
Examples:
# Basic search
adaptive-memory search "project meeting"
# With temporal context (like grep -B2 -A2)
adaptive-memory search "rust" --context 2
# Limit results
adaptive-memory search "database" --limit 10
# Deeper activation spread (reach more distant associations)
adaptive-memory search "ideas" --energy-decay 0.7
Output:
{
"query": "project meeting",
"seed_count": 15,
"total_activated": 47,
"iterations": 234,
"memories": [
{
"id": 38,
"datetime": "2026-01-04T09:00:00Z",
"text": "Project kickoff meeting with the team",
"source": "calendar",
"energy": 1.87
},
{
"id": 42,
"datetime": "2026-01-05T12:30:00Z",
"text": "Had coffee with Sarah, discussed the new project",
"source": null,
"energy": 2.45
}
]
}
Results are sorted by memory ID (timeline order). The energy field indicates relevance:
Context items (from --context) have energy: 0.0 and is_context: true.
Create explicit associations between memories.
adaptive-memory strengthen <IDS>
Arguments:
<IDS> Comma-separated memory IDs (max 10)
Examples:
# Link two related memories (adds 1.0 strength)
adaptive-memory strengthen 42,38
# Link multiple (creates all pairs, 1.0 each)
# 4 IDs = 6 pairs, each gets 1.0 strength
adaptive-memory strengthen 1,5,12,34
Output:
{
"relationships": [
{
"from_mem": 38,
"to_mem": 42,
"effective_strength": 2.0,
"event_count": 2
}
],
"event_count": 1
}
Like strengthen, but only creates relationships if none exist between the pair.
adaptive-memory connect <IDS>
Arguments:
<IDS> Comma-separated memory IDs (max 10)
Example:
# Connect memories only if not already related
adaptive-memory connect 42,38,15
Update the text of an existing memory. Only allowed if the memory has no relationships to later memories (preserves integrity of memories that later entries depend on).
adaptive-memory amend <ID> <TEXT>
Arguments:
<ID> Memory ID to amend
<TEXT> New text for the memory
Example:
# Fix a typo in memory 42
adaptive-memory amend 42 "Had coffee with Sarah, discussed the new project timeline"
List memories by ID range.
adaptive-memory list [OPTIONS]
Options:
--from <FROM> Start ID (inclusive)
--to <TO> End ID (inclusive)
-l, --limit <N> Maximum number of results
Examples:
# List memories 10-20
adaptive-memory list --from 10 --to 20
# List last 50 memories
adaptive-memory list --limit 50
Show the latest N memories (shorthand for list --limit N).
adaptive-memory tail [N]
Arguments:
[N] Number of memories to show (default: 10)
Example:
# Show last 5 memories
adaptive-memory tail 5
Show database statistics including memory count, relationship count, and graph metrics.
adaptive-memory stats
Output:
{
"memory_count": 1234,
"relationship_count": 567,
"connected_memories": 890,
"stray_memories": 344,
"avg_connections": 1.27
}
Sample unconnected (stray) memories - useful for finding memories that could benefit from being linked to others.
adaptive-memory stray [N]
Arguments:
[N] Number of stray memories to sample (default: 10)
Example:
# Find 5 unconnected memories to review
adaptive-memory stray 5
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"
adaptive-memory search "rust async"
# Either term
adaptive-memory search "rust OR python"
# Exact phrase
adaptive-memory search '"weekly standup"'
# Prefix matching
adaptive-memory search "meet*"
# Exclude term
adaptive-memory search "project NOT cancelled"
# Words near each other
adaptive-memory search "NEAR(database migration, 10)"
use adaptive_memory::{MemoryStore, MemoryError, SearchParams};
fn main() -> Result<(), MemoryError> {
let mut store = MemoryStore::open("~/.adaptive_memory.db")?;
// Add memories
let result = store.add("Learning about spreading activation", Some("research"))?;
println!("Added memory {}", result.memory.id);
// Search with default params
let results = store.search("activation", &SearchParams::default())?;
for mem in results.memories {
println!("{}: {} (energy: {:.2})", mem.memory.id, mem.memory.text, mem.energy);
}
// Search with context expansion
let params = SearchParams {
limit: 50,
context: 2,
..SearchParams::default()
};
let results = store.search("activation", ¶ms)?;
// Strengthen relationships
store.strengthen(&[1, 2, 3])?;
Ok(())
}
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 |
50 | Default result limit |
SearchParams)| Parameter | Default | Description |
|---|---|---|
limit |
50 | Max results (also seed count for FTS) |
decay_factor |
0.0 | Relationship strength decay over memory distance |
energy_decay |
0.7 | Energy multiplier per hop (0.7 = 70% retained each hop) |
context |
0 | Fetch N memories before/after each result |
energy_decayControls 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
decay_factorControls 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.
CREATE TABLE memories (
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);
CREATE TABLE relationships (
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)
);
memories tablestrengthen or --context for associations)ln(1+x) for diminishing returns--context N instead of pre-computed temporal links-d to preserve original timestamps when importing historical data+, -, *, etc.) should be quoted for literal matchingMIT