Local-first memory for AI agents. Typed, scoped, and it never leaks one task into another.
Quickstart · Docs · Integrations
Why memstrand
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, a hard filter — not a ranking signal), and
carries provenance (how it was learned, confidence, expiry). One
daemon (memstrand serve) shares all of that across Claude Code, Cursor,
Open WebUI, and your own scripts — over MCP, REST, and an OpenAI-compatible
proxy — backed by an embedded, on-disk store. No Docker, no cloud, no API
keys.
Quickstart
pip install memstrand
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
Memory.in_memory() runs Qdrant in :memory: mode and the graph/session/
keyword stores in a temp SQLite file — nothing touches disk and nothing
persists past the process. Embeddings are local ONNX (fastembed) by
default, downloaded once on first use.
Make it your second brain
memstrand serve # one daemon: MCP + REST + web UI + OpenAI proxy
claude mcp add --transport http memstrand http://127.0.0.1:7777/mcp # Claude Code
# Open WebUI -> Admin Settings -> Connections -> OpenAI API -> Base URL http://127.0.0.1:7777/v1
memstrand ingest ~/notes --watch # keep your notes/Obsidian vault synced in
Full setup recipes (Ollama/llama.cpp/LM Studio, Cursor, launchd/systemd autostart, cron backups) are in docs/integrations.md.
Features
- 100% local, zero API keys — embedded Qdrant + SQLite, local ONNX embeddings by default
- Typed (13 categories) + full provenance (how it was learned, confidence, expiry)
- Hard scope isolation — session/task/host is a filter applied before ranking, not a signal
- Hybrid search (dense + BM25) with absolute, non-normalized relevance + optional reranking
- Learns from your sessions — Claude Code hooks + OpenAI-proxy distillation, not just manual writes
- Ingests notes, Obsidian vaults, and PDFs, with
--watchfor continuous sync - Web UI for browsing, searching, and editing memories
- MCP + REST + OpenAI-compatible proxy, all from one daemon
- Contradictions and corrections are edges, never deletes — old memories stay queryable
export/import/backup/restorefor full portability
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 | ✅ | ❌ | ✅ | ❌ |
| Hybrid (dense + BM25) retrieval, built in | ✅ | — | — | ❌ |
| One daemon shared across tools (MCP + REST + OpenAI proxy) | ✅ | — | — | ❌ |
How it works
flowchart LR
Clients["Claude Code · Cursor · Open WebUI · scripts"] -->|MCP / REST / v1 proxy| Daemon["memstrand serve"]
Daemon --> Vector[(Vector\nQdrant)]
Daemon --> Graph[(Graph\nSQLite/Kuzu)]
Daemon --> Keyword[(Keyword\nFTS5)]
Daemon --> Session[(Session\nSQLite)]
Write (remember()) embeds once and writes vector+graph+keyword under
one scope-aware content-hash id. Distill (improve()) extracts typed
atoms from raw session turns via your local LLM and promotes them with
supersedes/contradicts edges. Read (recall()) applies the hard
scope filter first, then blends hybrid relevance with confidence, recency,
provenance, and access frequency. Consolidate expires, decays, dedups,
and replays on a schedule. Full detail, including the ranking formula and
FSRS/Hebbian dynamics, in docs/architecture.md.
Docs
- docs/configuration.md — every
MEMSTRAND_*environment variable - docs/architecture.md — stores, write/distill/read/consolidate paths, ranking formula
- docs/python-api.md — the full
Memorymethod surface - docs/cli.md — every CLI command/flag, plus REST/MCP reference and security notes
- docs/integrations.md — Ollama/llama.cpp/LM Studio, Claude Code, Cursor, Open WebUI, autostart, backups, Obsidian
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).
examples/quickstart.py runs all three against a persistent store;
tests/test_memory_flow.py runs the same rubric in CI.
Roadmap
- Typed schema, scoped retrieval, provenance ledger
- Session-commit distillation (extract → dedup → judge → promote)
- Graph neighbor hydration, Hebbian strengthen/decay, FSRS + replay consolidation
- One daemon: MCP + REST + web UI + OpenAI-compatible memory-injecting proxy
- Hybrid (dense + BM25) retrieval + optional cross-encoder reranking
- Markdown/Obsidian/PDF ingestion with
--watch - Claude Code hooks,
export/import/backup/restore - Automated migration tooling from 0.1.x's working-directory layout — see CHANGELOG.md (good first issue)
- Multi-user auth beyond a single shared bearer token (good first issue)
Contributions welcome — see CONTRIBUTING.md for dev setup, test philosophy, and how to add a store backend.
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.
License
MIT — see LICENSE.
If memstrand saves you from re-explaining yourself to your agent, a ⭐ helps others find it.
Metadata
Release files for memstrand 0.2.0
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.2.0.tar.gz | 209.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| memstrand-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 305.1 kB
Release files / memstrand-0.2.0.tar.gz
| Download URL | memstrand-0.2.0.tar.gz |
|---|---|
| Size | 209.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb8d2243985cdcc7ccbb310b5e2726df4b2e0fc60dd5528f195ba1bf751c47fe
|
|
BLAKE2b-256 checksum How to use checksums |
305bf4160abb7dcbd600ca1b977b59dcc5e720d1fc52a8dc9e775a8ab83db70b
|
| 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.2.0-py3-none-any.whl
| Download URL | memstrand-0.2.0-py3-none-any.whl |
|---|---|
| Size | 96.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cee882a87a7f1a5cc0d11ae69754dbd66c175dea041eeaa2d608779c5ed4791b
|
|
BLAKE2b-256 checksum How to use checksums |
0ab8c7e593ae7ad3508a2dd2606903026c490115f3258039ac2fb324d64d589c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|