Local-first, eval-first memory for long-horizon AI agents — no LLM at ingest
Project description
Midas
Durable memory for AI agents — no LLM at ingest, $0, fully local, source-traceable.
Your AI assistant forgets everything between sessions. Midas is a memory that lives next to it, on your machine — for coding agents, research agents, assistants. It remembers the durable stuff across long, multi-session work without sending every turn through an LLM to "extract" facts. It costs nothing per message, nothing leaves your computer, and every recalled memory traces back to the exact moment it came from.
uv tool install "midas-memory[mcp,local]" # the midas-mcp command, for any MCP client
# or, no Python: npx -y midas-memory-mcp # TypeScript port
# or, as a library: pip install "midas-memory[local]"
Why it's different
Other memory tools call an LLM to summarize every session — you pay in tokens forever, in latency, and by sending every turn to a provider, and recall returns rewritten facts you can't audit. Midas makes the opposite bet:
- No LLM at ingest or query → $0 API spend, zero data egress, fast local ops (embed-bound, ~tens of ms — no per-turn network round-trip).
- Source-traceable → recall returns the verbatim source turns, not LLM-rewritten facts. No extraction step that can silently hallucinate.
- Stays current & bounded, all no-LLM → typed belief revision (the old value is superseded, not duplicated), selective forgetting with an audit trail, dedup, time tiers.
- One file, many clients → point several MCP apps at one SQLite file and they share live memory (more below).
- Eval-first → every claim has a reproducible benchmark, including the experiments that failed.
How it does on the benchmarks
Deterministic, reader-independent retrieval (recall@k — fraction of the gold supporting turns
pulled into context) on the full public sets, vs a recency-window baseline:
| Benchmark (full set) | baseline | Midas |
|---|---|---|
LongMemEval-s — 500 questions, 246,750 turns |
0.01 | 0.92 |
| LoCoMo — 10 conversations, n=1,540 | 0.05 | 0.73 |
| BEAM — frontier benchmark, 100K → 10M tokens | 0.00 | 0.56 → 0.32 |
And the cross-system metric, judged answer-rate (same gpt-4o judge the leaderboards use):
| Judged answer | baseline | Midas |
|---|---|---|
LongMemEval-s (gpt-4o reader, ties LLM-ingest SOTA at $0 ingest) |
— | 0.84 |
| BEAM-100K (gpt-4o judge, raw-turn floor, $0 ingest) | 0.05 | 0.40 |
All of it at 0 LLM calls, $0, and 0 data egress at ingest. Full numbers, per-category breakdowns, reproduce commands, and the head-to-head framing vs Mem0/Zep/Mastra are in BENCHMARKS.md.
Eval-first means we publish the misses too. Hybrid retrieval, reranking, thread-diversification, dual-granularity indexing, and naive distillation were all measured to not help (or to hurt) and are documented as such. That honesty is the point — see BENCHMARKS.md and
docs/frontier-2026.md.
Connect it to your coding agent
Midas is a standard MCP server: every client launches the same midas-mcp command with a few env
vars — only where you put the config differs. The universal block:
{
"mcpServers": {
"midas": {
"command": "midas-mcp",
"env": {
"MIDAS_MCP_EMBEDDER": "local",
"MIDAS_MCP_DB": "/home/you/.midas/memory.sqlite3",
"MIDAS_MCP_MAX_RECORDS": "50000",
"MIDAS_MCP_MIN_IMPORTANCE": "2"
}
}
}
}
Claude Code (CLI, no file editing):
claude mcp add midas -s user \
-e MIDAS_MCP_EMBEDDER=local -e MIDAS_MCP_DB="$HOME/.midas/memory.sqlite3" \
-e MIDAS_MCP_MAX_RECORDS=50000 -e MIDAS_MCP_MIN_IMPORTANCE=2 -- midas-mcp
Cursor · Claude Desktop · Codex CLI · Windsurf · VS Code / Cline / Zed · npx (click to expand)
| Client | Where the config goes |
|---|---|
| Cursor | ~/.cursor/mcp.json (all projects) or .cursor/mcp.json — paste the JSON block |
| Claude Desktop | Settings → Developer → Edit Config (claude_desktop_config.json) — paste the block, restart |
| Codex CLI | codex mcp add midas -- midas-mcp, or a [mcp_servers.midas] block in ~/.codex/config.toml (TOML) |
| Windsurf | ~/.codeium/windsurf/mcp_config.json — paste the block, refresh |
| Anything else | point it at command midas-mcp with those env vars |
| No Python | npx -y midas-memory-mcp — the TypeScript port, same tools/schema (experimental: no semantic embeddings yet) |
⚠️ #1 gotcha: GUI apps don't share your shell
PATH. If a client says "command not found", use the absolute path fromwhich midas-mcp(macOS/Linux) orwhere midas-mcp(Windows). On Windows use forward slashes in JSON paths.
Once connected, Midas injects a short policy into the agent (recall first, then capture durable
facts/decisions/preferences/constraints/corrections). The agent captures freely; Midas decides
what's kept — it scores importance (no LLM), drops trivia, skips duplicates, revises stale beliefs,
and forgets the low-value tail to stay bounded. A provenance guard (check_memory_use) blocks
memory-justified external/destructive actions unless they came from explicit user confirmation.
One memory, many clients
Point Claude Code, Claude Desktop, Cursor… at the same MIDAS_MCP_DB file and they share one
live memory — each detects the others' writes (SQLite data_version) and refreshes, so a fact
captured in your IDE is recallable from your chat app seconds later, no restarts. Scope it per
project/agent/user with MIDAS_MCP_NAMESPACE.
Real run, reconstructed chrome — the capture/recall lines are verbatim output of two separate processes sharing one file.
All tools & env knobs
Tools: remember, capture (policy-gated auto-store), recall (source-traceable),
build_context (compact, dated, today-anchored prompt block), check_memory_use (guard),
memory_policy, maintain (dedup + forgetting, returns a deletion audit), stats,
forget (chain-safe), forget_matching (topic-level erasure, dry-run by default), forget_all.
Prompts: memory_session, distill.
Env: MIDAS_MCP_DB · MIDAS_MCP_EMBEDDER (local / hashing / multilingual / any fastembed id)
· MIDAS_MCP_MAX_RECORDS · MIDAS_MCP_MIN_IMPORTANCE · MIDAS_MCP_NAMESPACE · MIDAS_MCP_ANN=1
(sub-linear IVF for huge stores) · MIDAS_MCP_SUPERSEDE · MIDAS_MCP_NLI=1 (NLI-gated revision) ·
MIDAS_MCP_AUTO_MAINTAIN=<min> (idle-time upkeep) · MIDAS_MCP_PINNED (pin standing directives).
Use it from Python (the SDK)
from midas import Memory, LocalEmbedder
mem = Memory(embedder=LocalEmbedder()) # fully local. (Or Memory() for a zero-setup offline embedder.)
mem.remember("Decision: the primary database is PostgreSQL.", kind="constraint", importance=5)
mem.remember("The launch date moved to September 14.", kind="fact", importance=5)
mem.capture("lol ok cool") # filler — auto-scored below the floor, skipped (no LLM)
mem.assemble("when do we launch?", token_budget=128) # prompt-ready, dated, source-traceable
for hit in mem.recall("which database did we pick?", limit=3):
print(f"{hit.score:.2f} {hit.record.content}") # each hit traces to its source
Belief revision · forgetting · namespaces · bitemporal · LangGraph · persistence
from midas import Memory, LocalEmbedder
from midas.nli import LocalNLI
from midas.sqlite_store import SQLiteStore
# Durable, shareable, no native extension. Safe across threads & processes (live data_version refresh).
mem = Memory(store=SQLiteStore("memory.db"), embedder=LocalEmbedder(),
supersede=True, nli=LocalNLI()) # a turn that CONTRADICTS an old belief supersedes it
mem.forget_decayed(max_records=50_000) # evict lowest value (importance × recency); protects facts
mem.consolidate(similarity_threshold=0.95) # collapse near-duplicate restatements (keeps provenance)
mem.recall("when is the launch?", as_of=1_700_000_000) # bitemporal: "what did we believe on date X"
# Right-to-be-forgotten — preview, then erase, with an audit trail:
mem.forget_matching("the user's home address", dry_run=True)
mem.forget_matching("the user's home address")
# Scoped memory: one store, many projects/users, no cross-talk:
mem.recall("api gateway", metadata_filter={"namespace": "proj-a"})
# Back LangGraph's long-term memory with Midas:
from midas.integrations.langgraph_store import MidasStore
store = MidasStore(); store.put(("user", "123"), "pref", {"text": "prefers dark mode"})
Honest status
Midas is early (the API may change) but built narrow and measured-first. Where it stands, plainly:
- Retrieval is its strength and is essentially maxed for a no-LLM design — confirmed by our own A/Bs and by the frontier papers (the retriever is not the bottleneck). The numbers above are the result.
- Distillation (turning raw turns into compact facts) is the frontier's extra lever — and a naive
pass does not help here. We built it, judged it on BEAM, and measured that replacing raw turns
with summarized facts is catastrophic (it drops the temporal/changed-value detail), while
augmenting is roughly neutral. So the optional distillation dial ships off by default, and we
don't claim it as a win. The real lift needs sophisticated, structure-preserving extraction —
open work. (Details:
docs/frontier-2026.md§2b.) - Next: a no-LLM self-learning recall policy aimed at precision — the one untested lever left.
The eval harness
eval/ (dev-only) runs Midas and competitors through synthetic / LoCoMo / LongMemEval / multiday /
conflicts-v1 / BEAM with deterministic recall@k + precision@k, cost/latency instrumentation, a
dumb-reader ablation (proves the numbers aren't reader-inflated), and an optional local-or-hosted
LLM judge. The anti-cheating checklist (no query rewriting, no LLM at ingest, no gold leakage, seeded
sampling), conflict handling, failure traces, and the verbatim MCP policy are in
docs/methodology.md.
python -m eval.runner --dataset longmemeval --variant s --local --midas-no-rerank --max-questions 40
python -m eval.runner --dataset beam --beam-tier 100K --local --dumb-reader # frontier benchmark
Privacy & license
Local-first: every memory lives in a SQLite file on your machine, recall returns the exact stored
text, and capture/recall/forget make no network calls. No account, API key, or telemetry. The
only outbound traffic is a one-time embedding-model download (for the local backend) and the
package install. Full details in PRIVACY.md · MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file midas_memory-0.0.4.tar.gz.
File metadata
- Download URL: midas_memory-0.0.4.tar.gz
- Upload date:
- Size: 6.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb4946d34787b3acc69545a18a38a02c048bd38cda5628ff466324deaf13d657
|
|
| MD5 |
13f1aebb6b4281b55368f5395d05b45d
|
|
| BLAKE2b-256 |
1d826db69ea729ae82945acbb1db0fd7178a9c4868c41741cc59d12309e26f2c
|
File details
Details for the file midas_memory-0.0.4-py3-none-any.whl.
File metadata
- Download URL: midas_memory-0.0.4-py3-none-any.whl
- Upload date:
- Size: 69.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
416a617f0bbf0ebcbdff8b1c2c37ca8e0b85aca4fbd397dcc3051e7d39a92e82
|
|
| MD5 |
a617ac243309fb3ee4e978fd286f3ce2
|
|
| BLAKE2b-256 |
65dd5917ff5e1796d2b778ca3eab437b9e3427a7a51766239ffc409b9e0d631e
|