memstrand
Local-first typed memory for AI agents. Zero API keys.
Two strands, like DNA: one strand holds facts (vectors), the other holds relationships (graph). Every write joins them base-pair by base-pair — a memory is never stored without its connections, and never retrieved without its scope.
30-second demo
No Docker. No server. No LLM. pip install is the entire setup:
from memstrand import Category, Memory, Provenance
mem = Memory.in_memory()
# A fact scoped to one task...
mem.remember(
"Playwright installed as an agent-testing task dependency",
category=Category.fact,
provenance=Provenance.explicit_statement,
task_id="playwright-test",
)
# ...and a global preference
mem.remember(
"Chiran prefers a natural, unstylized look in AI portraits",
category=Category.preference,
provenance=Provenance.explicit_statement,
confidence=0.95,
)
# An unrelated session asks about testing setup.
# The Playwright fact does NOT leak across the task boundary:
for hit in mem.recall("testing setup"):
print(hit["category"], "-", hit["content"])
# preference - Chiran prefers a natural, unstylized look in AI portraits
# Same query WITH the task scope sees it:
for hit in mem.recall("testing setup", task_id="playwright-test"):
print(hit["category"], "-", hit["content"])
# fact - Playwright installed as an agent-testing task dependency
Typed. Scoped. Provenance on every atom. The hot path (remember/recall) never touches an LLM — embeddings are local ONNX, the graph is embedded Kuzu, sessions are SQLite.
Why
Flat vector memory has three failure modes, and every agent hits all three:
- No types — a preference, a decision, and a guess all look the same to cosine similarity.
- No scope — a fact learned while testing Playwright leaks into an unrelated Mac disk-cleanup session. (This actually happened. It's why this exists.)
- No provenance — nobody knows whether a memory was said, inferred, or corrected, or how confident the agent was.
memstrand fixes all three: every memory is typed (13 categories), scoped (session/task/host), and carries provenance (how it was learned, confidence, expiry).
Persistent setup
in_memory() is ephemeral — data dies with the process. For real use:
docker compose up -d # Qdrant (vectors); Kuzu + SQLite are embedded, zero setup
pip install memstrand
mem = Memory() # Docker Qdrant + on-disk Kuzu/SQLite
For the session-distillation pass (mem.improve()), point at any OpenAI-compatible local endpoint:
export MEMSTRAND_LLM_BASE_URL=http://localhost:8081/v1
Architecture
Seven layers, deterministic write routing, scoped retrieval:
| Layer | What lives there | Scope |
|---|---|---|
| L0 working | live transcript + task/host scope; outranks everything | task |
| L1 session cache | fast raw Q&A, no extraction, TTL'd | session |
| L2 episodic | distilled session learnings | task + host |
| L3 semantic | typed facts, vector-embedded | global |
| L4 consolidated | curated cross-session synthesis | global |
| L5 graph | typed edges over everything above | global |
| L6 procedural | action sequences, workflow patterns, lessons | global |
Write path (the session-commit loop): during a session, writes land in L1 with zero extraction cost. At session end (or pre-compact hook), an async distillation pass extracts Memanto-typed atoms via your local LLM, dedups by content hash, checks for conflicts — contradictions become contradicts edges, never deletions — and promotes into L2/L3/L5.
Read path: scope filter first (session/task/host is a hard filter, not a ranking signal), then tiered abstracts before full content, graph traversal for multi-hop, and a grounded_answer() primitive that answers only from stored memory.
Memory dynamics (stolen fair and square, see below): per-fact FSRS strength with a review queue, Hebbian edge weights (use strengthens, disuse decays), and replay consolidation that prunes weak edges but never facts.
How it compares
| memstrand | Mem0 | Cognee | raw vector RAG | |
|---|---|---|---|---|
| Local-first, no API keys | ✅ | OSS self-host option | ✅ | ✅ |
| Typed memory schema | ✅ 13 categories | ✅ notes | ✅ ontology | ❌ |
| Scope enforcement (hard filter) | ✅ | ❌ | ❌ | ❌ |
| Graph + vector joint writes | ✅ | ✅ | ✅ | ❌ |
| Contradictions as edges, not deletes | ✅ | ❌ | ✅ | ❌ |
API
mem.remember(content, category=..., provenance=..., confidence=..., ttl_days=...)
mem.recall(query, session_id=..., task_id=..., limit=...) # scope-aware
mem.improve(session_ids=[...]) # distill sessions → permanent memory (needs local LLM)
mem.forget(memory_id) # provenance-chain cleanup, never silent
mem.grounded_answer(question) # answer ONLY from stored memories
MCP server included: python -m memstrand.mcp_server exposes the same surface to Claude Code, Cursor, and friends over stdio.
Acceptance test
Any memory system should pass these three before you trust it (rubric adapted from a Cognee ad, because it was right):
- Remember me in a brand-new chat without re-pasting anything.
- Search by meaning, not keywords.
- Connect facts across sessions — and don't leak facts across unrelated tasks (the Playwright test).
Acknowledgments
Standing on shoulders, with credit:
- Memanto (moorcheh-ai/memanto) — the 13-category typed schema, provenance, and confidence model. Their product is cloud-only; this is the schema reimplemented local-first.
- Cognee (topoteretes/cognee) — the pipeline patterns: fast session cache → distillation, joint graph+vector writes, contradictions as additive edges.
- ZenBrain (zensation-ai/zenbrain) — per-fact FSRS, deterministic write routing, replay consolidation, Hebbian edges.
- Mem0 — the ADD/UPDATE/DELETE/NONE extraction resolution pattern.
Roadmap
- Schema + store interfaces + scaffold
- Session-commit distillation loop (extract → dedup → judge → promote)
- Scoped retrieval (task/host hard filters; unscoped queries see globals only)
- Graph neighbor hydration + Hebbian strengthen/decay
- FSRS + replay consolidation pass (math is in
pipeline/consolidate.py, not yet scheduled) - MCP server tools (remember/recall/forget)
-
memstrand import— adopt an existing Markdown memory file - Benchmarks: the 3-test rubric, automated (
tests/test_memory_flow.py)
License
MIT — see LICENSE.
Metadata
Release files for memstrand 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| memstrand-0.1.3.tar.gz | 21.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| memstrand-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.9 kB
Release files / memstrand-0.1.3.tar.gz
| Download URL | memstrand-0.1.3.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
db9de65f6725709ec7d6105c0293f5d7c0aa74cf3420929da32e6fc4c0667cc1
|
|
BLAKE2b-256 checksum How to use checksums |
eb980113c82b5e8ca03904971e3e50cbde8e4f7f7422579fc5dcf8f0fee9321f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / memstrand-0.1.3-py3-none-any.whl
| Download URL | memstrand-0.1.3-py3-none-any.whl |
|---|---|
| Size | 23.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
28d46535590d1888b5acdf48c6fbdee999e04f3836270efedc698a49bccf1e71
|
|
BLAKE2b-256 checksum How to use checksums |
77bc47ea93c03e70fe37c6031bc15d84484ee0aa6321426e3b3e55143e471f4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|