Changelog
Release history.
Changelog
Notable changes to Midas. From 1.0.0 the public surface — the midas.Memory SDK (remember / recall /
consolidate / supersession / forget), the guard (guard_reliance, decide_memory_use, the coding
is_forbidden gate), the MCP tools, and the midas CLI — follows semver:
breaking changes only land in a major. Format loosely follows
Keep a Changelog.
[Unreleased]
[1.0.0] — 2026-07-06
The 1.0 contract: the public surface — the midas.Memory SDK, the guard, the coding is_forbidden
gate, the MCP tools, and the midas CLI — is now locked under semver. This release consolidates and
hardens everything since 0.1.1 (the retrieval core, the governance guard, the control-plane, the audit
chain, the client tooling, and the TypeScript port) and freezes the API.
Added
- The continuity control-plane (
midas/continuity.py+ MCP tools):resume— everything an agent needs to pick up a session in ONE call (pinned directives, forbidden rules, current state, what changed, open loops, unresolved conflicts; token-budgeted, prompt-ready);memory_conflicts— live beliefs that contradict each other with neither superseding the other (the multi-agent shared-memory failure mode), NLI-scored when available with a same-slot heuristic fallback, ranked candidates only; open loops —remember_commitment/open_loops/close_loopmake promised work survive across sessions with an auditable promise→resolution chain. The injected agent policy now starts sessions withresumeand keeps promises via open loops. - Per-kind TTL retention:
Memory.forget_expired({"chat": 30}),MIDAS_MCP_TTL="chat=30,note=90", andmaintain(ttl=…)— age-based retention next to value-based forgetting; user-confirmed, standing, and supersession-chain records never expire silently. - Tamper-evident audit chain in
SQLiteStore: every put/delete/clear appends a hash-chained entry (hashes only — never memory content);midas audit [--json]shows and verifies the chain andmidas doctorchecks its integrity.audit=Falseopts out for perf-sensitive paths. midas import --from claude-md | cursorrules | jsonl: file-based agent memory (CLAUDE.md, .cursorrules, exported JSONL) becomes first-class Midas records — sectioned, deduped, idempotent, and governance-safe by default (imported rules areobservationprovenance and cannot authorize guarded actions unless you pass--confirmed).- 4 more clients in
midas init: VS Code (usermcp.json,serverskey), Gemini CLI, Cline, and Zed (context_servers) — each written in its own config schema; status/uninstall understand them too. - HTTP bearer-token auth:
midas serve --http --token <secret>/MIDAS_MCP_TOKENgates every request on the shared-URL transport (constant-time compare, 401 +WWW-Authenticateotherwise). midas doctor --json— the machine-readable diagnosis, same envelope as the wiring receipt.- TypeScript port: optional local semantic embeddings.
LocalEmbedder(ONNX via the optional@huggingface/transformers, bge-small by default) behindMIDAS_MCP_EMBEDDER=local, with a clean announced fallback to the byte-parity hashing embedder; the TSMemoryAPI is now async (remember/capture/recall/buildContext/forgetMatchingreturn promises). - CI on Windows and macOS (core + MCP): the client-wiring code is full of per-OS paths that only Linux exercised.
- The Agent Continuity Bench measures the new control-plane:
resume_fidelity(the one-call pack contains live state / forbidden rules / open loops and never presents superseded values or closed loops as live),conflict_detection, andconflict_precision— all → 1.00, wired intomidas benchwith the same all-green verdict rule. - Inspector views for the control-plane: Conflicts (ranked contradiction pairs with per-side forget), Open loops (close with a recorded resolution), and Audit log (chain verification + a hash-only tail).
- TypeScript parity for the control-plane and audit chain:
resume,memoryConflicts(heuristic tier), open loops,forgetExpired/TTL, and the same hash-chainedaudit_log— the hash is canonicalised to integer microseconds so either runtime verifies chains written by the other (validated bidirectionally). midas init --claude-hook: a Claude Code SessionEnd hook (midas hook capture-session) that offers each finished session's user turns tocapture— memory even when the agent never calls the tool, with the same no-LLM keep/skip policy, fail-open by construction, removed bymidas uninstall.midas import --from mem0 | zep: migrate Mem0 exports (distilled memories → facts with original ids/attribution preserved) and Zep exports (facts/edges → facts, messages → chat turns).- Encryption at rest (opt-in):
SQLiteStore(key=…)/MIDAS_MCP_KEYwith the new[encrypted]extra (SQLCipher). The file on disk is ciphertext; opening without the key fails; if the key is set but the extra is missing Midas fails closed instead of silently writing plaintext. midas init --json/midas status --json— a machine-readable client wiring receipt (#15). One command now yields a compact, pasteable proof of what was wired: per client its config path, detected/wired/changed state, backup path, and skip reason; plus the memory DB, scope mode (shared / project-scoped / manual-namespace), server command, embedder, and the enforced policy.status --jsonparses each client config (JSONmcpServersand codex TOML) to report the actual command +MIDAS_*env every client will launch with, and warns when clients disagree on namespaces. The receipt is an audit boundary: config paths and scope/policy only — never memory contents. The human output of both commands is unchanged.midas/cli.py,tests/test_cli.py.midas bench— the full governance / safety / continuity bench suite as one CLI command (equivalent touv run python -m eval.benchesfrom a checkout; guides to clone otherwise). Deterministic, $0, no datasets, no API key — a skeptic can verify every governance number.- Stress tests proving the multi-client promise: 4 threads writing concurrently to one SQLite
store with no lost or corrupt writes; two
Memoryinstances on one file seeing each other's writes without a restart; unicode/emoji/20k-char inputs handled and empty/whitespace rejected; recall finding the signal among 1,500 noise records.
Changed
midas inspectredesigned end to end. A real light theme (not an inverted dark one) alongside the existing dark brand theme, toggle + system-preference default + persisted choice; a fixed, validated categorical color system so everykindandprovenancevalue keeps the same color everywhere in the app (Overview bars, Browse tags, Project governance) instead of one monochrome accent for everything; a real 30-day activity chart (SVG line + area, hover crosshair/tooltip) and a recency (short/medium/long) stacked bar on Overview; grouped, badge-annotated navigation (Memory / Coding / Governance) with a command palette (⌘K) and a / search shortcut; nativeconfirm()/prompt()replaced with in-app modal/toast components; a responsive layout down to phone width with a horizontal pill nav. New endpoints backing it:api_timeseries(daily capture counts),api_meta(version/db/ embedder/audit-chain identity), andby_tieradded toapi_overview. Fixed a real routing bug in the process: the old build never listened forhashchange, so browser back/forward and direct links to a view silently did nothing.- The TypeScript
MemoryAPI is async (remember/capture/recall/buildContext/forgetMatchingreturn promises) to support the optional ONNX embedder. - A bare
Memory()now auto-selects real semantic recall. With the[local]extra installed,Memory()upgrades from the offline hashing embedder toLocalEmbedderautomatically (override withMIDAS_EMBEDDER=hashing|local; defaultauto). Fixes the out-of-the-box recall weakness an external test hit: the SDK silently used the noisy hashing fallback even when semantic embeddings were available.
Security — the governance moat (memory-safety bench: 10 attacks / 4 benign, ASR 0.00, benign-pass 1.00)
- Prohibition veto. A live
forbidden_actionrule recalled for an external/destructive action vetoes it — overriding any confirmation in the same evidence, so an attacker can't approve around a standing "never do X".midas/guard.py. - Relevance bar for actions. Authorizing an external/destructive action requires strongly
relevant evidence (
GUARD_ACTION_MIN_RELEVANCE=0.25), so a weakly-matched confirmation for a different action can't lend its approval. Planning/answers unchanged. - Provenance integrity. A user-confirmed belief can no longer be superseded ("laundered") by lower-provenance memory — an attacker's observation can't overwrite confirmed truth and ride the supersession chain past the guard's currency rule. Genuine user re-confirmations still revise.
- Cross-namespace isolation. A confirmation scoped to namespace
alphadoes not authorize the same action inbeta, even on an exact text match — proven by adversarial bench cases wired through the guard's recall scope filter.
[0.1.1] — 2026-06-27
Fixed
- Recall no longer returns a lone irrelevant memory with the hashing fallback. The offline hashing
embedder now declares an absolute noise floor (a hashed cosine ~0 means no token overlap), so a query
that matches nothing returns empty instead of the only memory present — without dropping real matches
(which land well above the floor). Semantic embedders declare 0, so the benchmarked path is unchanged.
midas/embeddings.py,midas/memory.py. (Found via external testing of 0.1.0.) quickstart.pyis a real end-to-end demo (remember → assemble → recall → supersession → guard), withtests/test_quickstart.pyrunning it as a subprocess so CI catches any break.
Changed
midas doctornow flags clearly when the SDK is installed but the[mcp]server extra is not.
[0.1.0] — 2026-06-26
Fixed
- A prohibition can no longer authorize an action. A
forbidden_actionrule is stamped as a user-confirmed constraint, so the mechanical guard counted it as authorizing the very action it forbids ("never run destructive migrations" → approving a destructive migration).decide_memory_usenow excludescode_kind=="forbidden_action"records from authorizing an external/destructive action (they still inform planning/answers, and are enforced separately byis_forbidden).midas/guard.py,tests/test_coding.py. - Guard no longer authorises actions on stale beliefs.
decide_memory_usechecked provenance and actor but not currency, so a superseded memory — even one user-confirmed when it was current — could still justify an answer or an external/destructive action. It now blocks superseded evidence for any non-planning use (planning may still weigh history); the guard verifies currency itself instead of trusting recall to have filtered the stale record.midas/guard.py,tests/test_guard.py. - Hybrid recall is no longer O(N)/query at scale (~250× faster).
_hybrid_candidatesrebuilt the allowed-record map by scanning the whole store on every query; the no-filter path now reuses a version-cached_records_by_id()map (andrecall()drops the predicate when no scope filter is active). Measured on LongMemEval-s(246,750 turns): 11.6 s/query → 46 ms/query, recall unchanged (0.92, BM25 hybrid). Affects any--midas-hybriduse at scale.
Added
- The
midasCLI — one-command setup.midas initcreates one shared store (~/.midas/memory.sqlite3) and wires up the MCP clients it finds (Claude Code, Codex, Cursor, Claude Desktop, Windsurf) so they all share one memory; plusserve --http(a shared MCP URL),status,doctor,export/import(JSON backup / move machines),uninstall, andupdate.midas/cli.py. - Memory Inspector (
midas inspect) — a local, glass-box web UI over your store: an Overview metrics dashboard, Browse/search of verbatim records, belief-history time-travel, project state, a what-changed diff, the governance/audit verdict, and forget-with-receipt. Stdlib-only, zero egress.midas/inspector.py. - Per-project memory scoping —
MIDAS_MCP_NAMESPACE=auto(ormidas init --project-scoped) partitions the one shared store by project (git repo / cwd), automatically. - Agent-memory governance suite — Memory-Safety (ASR / benign-pass) and Coding benches join Continuity
(
eval/benches.py); a coding-agent vocabulary (remember_code/project_state/ forbidden-action checks), an audit trail (audit_use/ content-hashedforgetting_receipt), RBAC (access.py), and live state views (memory_state/memory_diff). - Agent Continuity Bench v0 (
eval/continuity.py) — a deterministic ($0, no-LLM) seed for the axis retrieval benchmarks miss: across sessions, does memory keep a coding agent safe (Guard allows or blocks a proposed use by provenance + currency) and current (a revised decision surfaces its live value, not the superseded one)? Both scored end-to-end and pinned intests/test_continuity.py. - Judged summarization A/B + rubric-coverage judge (
eval/summarization_ab.py) — BEAM gradessummarizationby a rubric (no reference string), so the prior distill A/B skipped the category entirely. The loader now carries the rubric (eval/datasets.py,tests/test_beam_rubric.py) and the new harness scores rubric coverage with a local (Ollama) or--backend hostedreader/judge/extractor. Finding: local structure-preserving extraction does not lift summarization (raw 0.28 vs replace 0.07); the lift is gated on a capable extractor —docs/frontier-2026.md §2b. - TurboVec compressed vector backend (
[turbovec]extra) —TurboVecStoreruns a 2/4-bit TurboQuant index (TurboVecIndexoverturbovec.IdMapIndex) for cheap candidate generation, then re-scores the candidates by exact cosine on full-precision vectors, so recall tracks an exact scan while the index is ≈8–16× smaller in RAM. The RAM-saving mode (vector_source=SQLiteVectorSource(…)) keeps full vectors on disk and strips them from memory, reading back only the candidate set per query. Measured on BEAM-100K: recall 0.53 = exact at both 4-bit and 2-bit; index RAM 153.6 MB → 10.2 MB (15×); extrapolates to 10M×768 ≈ 31 GB → ~2 GB.examples/turbovec_backend.py; benchmarks inBENCHMARKS.md. MemoryStore/VectorIndexProtocols + allowlist pushdown — the store/index surface is now an explicit contract (midas/index.py), and a query's scope is pushed into search as anallowed_idsallowlist (O(1) membership / in-kernel for native backends) instead of a per-row Python predicate.- Experimental retrieval backends (opt-in, measured). Learned-sparse hybrid (
--midas-sparse— BM42 / SPLADE++ via fastembed), ColBERT late-interaction reranker (--midas-reranker colbert), Matryoshka dim truncation (--local-truncate-dim), and Model2Vec static embeddings (--model2vec,[model2vec]extra). Per-dataset results — which generalize and which don't — indocs/MIDAS.md§5.5 anddocs/research-notes.md.
Changed
- Shared, persistent memory by default. The MCP store now defaults to
~/.midas/memory.sqlite3(was ephemeral in-memory), so a fresh install just works and every client shares one memory with zero config. SetMIDAS_MCP_DB=:memory:to opt back into ephemeral.midas/mcp_server.py. - Versioned store schema with safe migration. The SQLite store carries a schema version, auto-migrates
on open, and refuses a store written by a newer Midas (with a clear
midas updatemessage) — so updates can't silently misread an evolved store.midas/sqlite_store.py. - License changed from MIT to Apache-2.0 to keep the core permissive while adding the explicit patent grant and enterprise-facing terms expected by commercial adopters.
[0.0.4] — 2026-06-13
Added
- Distillation as one optional dial (three tiers, default no-LLM) — the frontier's gains come
from distilling raw turns into compact facts at ingest. Midas exposes this as a single opt-in dial,
never a fork: (1) default — raw turns, $0, fully source-traceable; (2) agent-driven — the
agent's own LLM distills via the injected policy + new
distillMCP prompt ($0 to Midas, keeps raw- facts); (3) local distiller —
Memory(distiller=...)/OllamaDistiller+Memory.distill( turns, keep_raw=True), the explicitly-fenced "crosses the no-LLM line" tier for non-agentic ingest (keeps $0/local/zero-egress with Ollama, but distilled records are LLM-derived, stampedmetadata["distilled"]=True). Defaultdistiller=None= pure no-LLM. README has the tier table;docs/frontier-2026.md§2b has the research.
- facts); (3) local distiller —
- BEAM adapter — the 10M-token frontier benchmark (
eval.runner --dataset beam --beam-tier 100K|500K|1M|10M). BEAM ("Beyond a Million Tokens", ICLR 2026) is the regime where context-stuffing is physically impossible; its questions carrysource_chat_idsevidence, so Midas's deterministic reader-independentrecall@kapplies on the frontier benchmark. Loader maps realtime_anchorevent times (bitemporal signal) and BEAM's abstention category onto the runner's unanswerable semantics; rubric-only categories contribute retrieval metrics only. Landscape research + positioning indocs/frontier-2026.md. - Bitemporal belief history — retired beliefs now carry a validity bound (
superseded_at= the revising record's event time) andrecall(as_of=…)(MCP:recall(as_of="YYYY-MM-DD")) answers "what did memory say on date X": later records are excluded and supersession chains resolve to the version valid then. Zep-class historical queries with no graph DB and no LLM. - Background auto-maintain (sleep-time at $0) —
MIDAS_MCP_AUTO_MAINTAIN=<minutes>runs a periodic no-LLM upkeep pass (consolidate near-duplicates + re-bound the store) while the agent is idle — the sleep-time-compute insight without an LLM rewriting memory. - Pinned standing directives (default on in MCP) — durable user rules ("from now on…",
"always…") are detected at ingest (cue regex, no LLM, user-voiced turns only —
is_standing_instruction,Memory(detect_standing=True)) andbuild_context(pinned_limit=…)/MIDAS_MCP_PINNED(default 2) pins them into every context regardless of query relevance: the no-LLM version of Letta's always-in-context core memory. Measured on BEAM-100K: instruction-following recall@k 0.26 → 0.44 (pinned=2) → 0.51 (pinned=4), overall 0.56 → 0.58. The first detector iteration (which also pinned assistant-voiced advice) hurt across the board and is documented as the negative that set the user-voice-only rule.
Measured negative (kept opt-in, default off)
- Hit-anchored expansion / pseudo-relevance feedback (
recall(anchor_boost=…),--midas-anchor-boost) — letting records near a confirmed top hit earnboost × cosinerelevance was the natural attack on the multi-evidence categories (event-ordering 0.24 on BEAM-100K), and it hurts monotonically: overall recall@k 0.56 → 0.44 (boost 0.7) → 0.28 (0.85), with the target category itself dropping to 0.17. Anchor-similar records are conversational near-duplicates, not gold. Third member of the same measured family (hybrid fusion, thread-cap): reshuffling bge's dense ranking only ever degraded it — the remaining aggregation-category gap is a reader/structure problem, not a retrieval-reordering one.
Fixed
- Belief revision did not survive restarts on SQLite-backed stores — supersession mutated the
in-memory mirror without persisting (
store.put), sosuperseded_bysilently vanished on reopen. Found while adding the bitemporal stamp; regression test included.
Added (earlier this cycle)
- TypeScript port (experimental) —
packages/midas-ts,npx -y midas-memory-mcp. Same MCP tool surface, env knobs, injected policy, and SQLite schema as the Python server; the hashing embedder is bit-comparable (md5 parity pinned by a Python-generated fixture), so a TS and a Python server can share one DB file live (both probedata_version) — verified bidirectionally in tests (13 node:test cases). Ships the core: ranking + parsimony floor, BM25+RRF hybrid (cached), supersession chains, policy-gated capture, structural importance, guard, leanbuild_context, selective forgetting,forget_matching. Not ported yet: ONNX semantic embeddings, NLI, reranker — the Python server stays the reference. - Multilingual embeddings —
MIDAS_MCP_EMBEDDER=multilingual(or any fastembed model id) selectsparaphrase-multilingual-MiniLM-L12-v2; runner gains--local-modeland asynthetic-esdataset. Measured both ways: on Spanish content the English-only bge-base drops to answer_dumb 0.68 while the multilingual model scores 1.00 (both 1.00 on the English twin); on English LongMemEval-s(n=40) the multilingual model retrieves at 0.83 vs bge's 0.95 — switch only for non-English memory. The English-trained cross-encoder reranker stays off in multilingual mode. - Opt-in ANN for big stores —
InMemoryStore(ann_threshold=…)/SQLiteStore(…)/MIDAS_MCP_ANN=1route search through the numpy-only IVF index at ≥10k records (index cached on the store's change counter; predicate pushdown; approximate — recall ~0.95 at nprobe=16 per BENCHMARKS §4, which is why exact scan stays the default).
Fixed
LocalEmbedderhardcodeddim=768— any non-default model (e.g. 384-d MiniLM) silently poisoned the on-disk embedding cache with mislabelled rows that failed to decode in later sessions. The dim now comes from the model registry (probe fallback), andDiskCachedEmbedderrefuses to write a vector that contradicts the declared dim (regression test).
Measured negative (kept opt-in)
- Thread-diversified recall (
recall(thread_cap=…),--midas-thread-cap) — capping hits per session-thread to help multi-evidence questions hurts across the board on full LongMemEval-s(overall recall@k 0.92 → 0.84 at cap=3, 0.89 at cap=5): gold evidence is often consecutive turns of one session, and the cap evicts it for other-session distractors. Trace analysis showed the remaining temporal misses are multi-evidence spread (not query-date parsing), so no date-window heuristic ships either.
Measured
- BEAM, all four tiers complete (100K → 10M tokens), deterministic. Midas recall@k 0.56 → 0.51 → 0.40 → 0.32 across a 100× scale-up while the recency baseline scores 0.00 at every tier; knowledge-update holds at 0.68 even at 10M. The 10M tier (208,696 turns per the 10 conversations) ingested in hours of local CPU at $0 — the cost regime where LLM-at-ingest systems pay for ten million tokens of extraction per conversation. Honest weak side documented: aggregation abilities collapse at 10M (instruction 0.00, event-ordering 0.02, summarization 0.03) — whole-conversation abilities top-k retrieval cannot cover by construction.
- Full-set retrieval headlines — no sampling caveat left. LongMemEval-
s, all 500 questions (246,750 turns ingested, deterministic, seed 0): Midas recall@k 0.92 vs recency-baseline 0.01; per-category fact 0.97 (n=126) · knowledge-update 0.93 (n=78) · temporal 0.91 (n=133) · multi-session 0.89 (n=133) · preference 0.89 (n=30). Together with the full LoCoMo set (0.73, n=1,540) both retrieval headlines now run on complete public question sets. Judged answer-correctness remains n=40 (hosted-reader cost-bound).
Fixed
- LoCoMo benchmark corrected to the full public set: recall@k 0.73 vs baseline 0.05 (n=1,540).
The previously published 0.85 (n=50) did not reproduce against the publicly downloadable
locomo10.json(verified not to be code drift: the v0.0.1 harness gives the same 0.28 on that sample today). Root cause of the full-set gap: the runner's old LoCoMo-specificmin_relevance=0.75absolute floor — tuned on the early sample — pruned most gold turns at scale (recall@k 0.18 with it, 0.73 without). The floor is removed; the SDK's scale-freemin_relevance_ratiois the safe replacement. BENCHMARKS.md carries the correction notice.
Added
- Scale-free context parsimony, default on (
Memory(min_relevance_ratio=0.3); per-call override;0disables; runner flag--midas-min-relevance-ratio) — recall drops any hit whose relevance is below 0.3× the query's own top hit. Measured (deterministic): zero gold evicted on any dataset, ~2× precision@k, ~30–40% fewer context tokens on spread-scale embedders (synthetic 102→87, conflicts 207→121, multiday 174→126 avg tokens), and a verified no-op on bge-base (LongMemEval-srecall@k 0.95 and tokens identical). Honest boundary documented: 0.4+ evicts multiday's buried update (recall 1.00→0.80), so the default stays at 0.3. Unlike the absolutemin_relevancefloor, the ratio transfers across embedders because it is relative to each query's best hit.
[0.0.3] — 2026-06-10
Consolidates everything since 0.0.1 (0.0.1 and 0.0.2 were cut without per-release sections).
Added
-
Live multi-process memory sharing —
SQLiteStorenow detects writes from other connections (SQLitePRAGMA data_version) and refreshes its in-memory mirror, so several MCP clients (Claude Code + Claude Desktop + an IDE) pointed at one DB file see each other's captures live, without restarts. The shared connection is also lock-guarded and usable from worker threads (how MCP frameworks actually run sync tools). -
Namespaces (scoped memory) — share one DB across projects/agents/users without cross-talk. SDK:
recall/build_context/forget_matchingtakemetadata_filter={...}(equality scoping; neighbour-window expansion respects it too — no leaks past the filter). MCP:MIDAS_MCP_NAMESPACEenv sets the server's default scope; every tool also takes a per-callnamespace;statsreports aby_namespacebreakdown. Unscoped behaviour is unchanged. -
Topic-level erasure with audit (
Memory.forget_matching, MCPforget_matching) — "forget what you know about X": matches by relevance, dry-run by default in MCP (preview what would be deleted, then confirm withdry_run=false), returns the full list of removed records as the erasure audit trail. Deliberately bypasses durability protections — an explicit erasure request outranks retention. The right-to-be-forgotten lever, no LLM. -
Chain-safe single deletion (
Memory.forget, now used by the MCPforgettool) — deleting a record mid-supersession-chain relinks the chain instead of orphaning it, so a query phrased like the old value still resolves to the current belief. -
MCP
build_contextupgrades — the context block now carries the measured temporal grounding ("# Today is …" header + per-memory relative ages — the LLM-free signal that lifted temporal recall@k 0.86→0.95 in the eval) and exposeslimit,hybrid, andnamespace. -
Token-lean by default — the context an agent actually pays for is now compact. The injected MCP policy text shrank 442 → 198 approx tokens (−55%) (kind/provenance taxonomies live in the
remember/capturetool descriptions instead of being repeated);build_contextemits lean memory lines by default (- [kind | date] …, −42%/line vs the audit format —Memory(include_provenance=False)is the new default, with a per-callinclude_provenanceoverride), and the budget accounting now charges the[source: …]suffix it previously appended for free. Full provenance/source evidence stays onerecall/inspect_memorycall away; eval adapters already ran lean, so benchmark numbers are unchanged. -
Hybrid recall is ~8× cheaper on a stable store — the BM25 index is cached on the store's change counter (rebuilt only after writes) and scores via per-term posting lists, so a query touches only documents sharing a term with it. Stable 5k-record store: ~66 ms → ~8 ms/query; identical scores and eval metrics (conflicts-v1 and LongMemEval-
sreproduce exactly). -
Measured negative (hybrid stays opt-in) — on LongMemEval-
s(n=40, bge-base, deterministic) hybrid BM25+RRF hurts retrieval: multi-session recall@k 0.97→0.81, temporal 0.95→0.86 (fact 0.89→0.90, within noise). Lexical rank-fusion displaces buried semantic evidence on paraphrased queries. Hybrid therefore stays off by default and is recommended only for exact-identifier lookups (error codes, ticket ids, function names), where BM25 catches what the bi-encoder ranks low. -
Eval methodology doc (
docs/methodology.md) — anti-cheating checklist (no query rewriting, no LLM at ingest, seeded sampling), verbatim MCP-injected policy, how conflicting memories are handled (supersession chains + gating), dumb-reader ablation, conflicts-v1 results, and publishable failure traces for selective forgetting. -
Dumb-reader ablation (
--dumb-readeroneval.runner) — deterministic extractive reader (no LLM); addsanswer_dumbto the leaderboard. If it tracksrecall@k, headline numbers are not reader-inflated. -
conflicts-v1benchmark — adversarial near-duplicates + temporal conflicts (eval/datasets.conflicts,eval.multiday --dataset conflicts,eval.runner --dataset conflicts). Reportsctx_stale/ctx_contradicton the main leaderboard when the dataset annotates outdated values. -
Retention forgetting traces (
eval.retention --trace) — per-question audit after eviction (gold survived vs evicted, value-vs-fifo wins and failures). -
Guard / provenance control-plane —
Armorer+Guard(midas/guard.py); four provenance tags; MCPcheck_memory_use; mixed-recall bundles allow actions when at least one hit satisfies policy (invalid hits reported inblocked_ids, not a whole-decision veto). -
MCP server distribution — new
midas-memory-mcplauncher on PyPI, listed on the official MCP registry (io.github.vornicx/midas); run install-free withuvx midas-memory-mcp. The MCP server now reports its own version in the handshake (previously the MCP SDK version). -
Core SDK —
Memory(remember/recall/build_context/assemble) ranking by relevance × importance × recency, with same-thread neighbour-window expansion and budgeted, highest-value-first context assembly. No LLM at ingest or query. -
Embedders —
HashingEmbedder(offline, zero-dep),LocalEmbedder(fastembed/ONNX, bge-base),OpenAIEmbedder, andDiskCachedEmbedder(persistent SQLite cache keyed by model + dim + text).LocalReranker(cross-encoder, length-capped to avoid ONNX crashes). -
Stores —
InMemoryStorewith a vectorised cosine scan over a cached embedding matrix (numpy; comfortable to ~1M memories) and an identical pure-Python fallback;SQLiteStorefor persistence across restarts with no native extension (pure stdlib sqlite3). -
float32 in-memory embeddings — records store the embedding as a float32 numpy array, not a Python
list[float](~32 B/value). Measured ~7× smaller footprint at 768 dims (a 1M-record in-memory store drops from ~24 GB to ~3.5 GB) and faster queries (float32 matmul); SQLite already persisted float32. Measured byeval/bench_perf.py(latency / throughput / real tracemalloc footprint — the numbers the project had never measured). -
Hybrid retrieval (BM25 fused with semantic) — off by default; see
BENCHMARKS.mdfor the honest negative result on conversational data. -
Belief revision (supersession) for typed durable facts — off by default; chat never supersedes chat. Paraphrased updates are caught by the embedder's cosine similarity (no hand-tuned synonym map), so it generalizes beyond any one dataset.
-
Local NLI (
midas/nli.py, LLM-free) — a small int8 ONNX MNLI cross-encoder (onnxruntime + tokenizers, ~70 MB, no torch/API). Powers contradiction-gated conversational belief revision: a chat turn revises an earlier belief only when NLI scores it an actual contradiction. This fixes the cue-heuristic's over-supersession (LongMemEval temporal recall restored 0.76 → 0.95) while staying precise on real updates — closing the "cheap no-LLM contradiction detection" open problem. Also exposes post-hoc answer-grounding (--answer-verify-nli) — override to "I don't know" when no retrieved turn entails the answer. Honest result: it does NOT reliably improve abstention (a deterministic-reader A/B is unchanged, 0.37→0.37) because the confabulation is drawn from a retrieved distractor that entails it. Abstention/Calibrated remains the open frontier; see docs. -
Entity-grounded abstention (
midas/entity.py, LLM-free) — a new abstention lever orthogonal to cosine/NLI: abstain when the answer's source turn is about a different entity than the question asks (the diagnosed confab-from-distractor root cause). Dropping recurring attribute words makes the focus the entity noun; offline-validated 8/8 on the diagnosed failure cases (incl. "favorite colour" vs "favorite food", "city" vs "Barcelona"), 11 tests. Honest limit: crafted cases — the end-to-end win needs a capable reader (local 1B doesn't confabulate-from-distractor; hosted credits exhausted). -
Time-aware retrieval (LLM-free) — memories carry real event time (
remember(created_at=…));recall/build_contexttake a querynowso recency decays from when a question is asked, context renders true dates (UTC), and a "today" header anchors relative-time reasoning. Bitemporal signal, no LLM. Eval ablation:--midas-no-time. -
Selective forgetting + temporal tiers (LLM-free) —
Memory.forget_decayed()evicts the lowest-value memories (memory_value= importance × recency) to bound storage and context growth, protecting the durable tier (facts/preferences/constraints, high importance) and never orphaning a supersession chain; returns the forgotten ids (deletion audit trail).Memory.tier()names a memory's horizon — short (≤1d) / medium (≤1w) / long (multi-day). Measured witheval/retention.py(eviction policies at the same retained budget): on data with an importance signal, value-based forgetting holds recall@k 1.00 at 25–50% retention while recency/random eviction fall to 0.17–0.60; on uniform-importance chat it reduces to recency (honest — needs a per-turn importance signal, the next step) while cutting context tokens ~3×. Purely additive: no-forget recall@k unchanged (LoCoMo 0.62). -
Content importance scoring (
ContentImportance, LLM-free) — derive a turn's importance 1–5 from content alone (content-word density, numbers/dates, proper nouns, anti-backchannel);Memory( importance_scorer=…)auto-applies it to turns ingested without one, so raw chat gets a salience for forgetting/tiering. Measured: as a forgetting protection it lifts LoCoMo recall@k under eviction from 0.10 (recency) to 0.18 (sheds filler, keeps facts); as a pure rank it helps only at moderate compression. -
Novelty-vs-store importance (
Memory(novelty_weight=…), LLM-free) — blends importance with1 − max-cosine-to-storeso a new fact can outrank a repeated one. Off by default: a measured negative. At equal budget it is neutral on LoCoMo/synthetic recall@k and harmful on multiday (1.00 → 0.60), because repetition usually signals importance and demoting restated gold evicts it. Kept as a tested, opt-in knob; its right home is consolidation (dedup), not eviction-ranking. -
Reinforcement importance (
Memory(reinforce=True), LLM-free) — the inverse of novelty: a restated turn boosts the matched memory's importance + recency (repetition ⇒ salience); incapturea restatement reinforces the existing memory and is skipped. Off by default: also a measured negative — recall@k drops at equal budget (LoCoMo 0.08→0.03 @25%; multiday 0.60→0.40 @25%). Unifying finding: on raw conversation repetition tracks commonness, not importance, so neither novelty nor reinforcement improves no-LLM forgetting. Content-salience as a protection stays the best signal. -
Extractive consolidation (
Memory.consolidate, LLM-free) — collapse near-duplicate restatements to the single highest-value copy (cosine ≥ threshold, chains preserved); extractive (drops redundant records, keeps provenance — never LLM-rewrites). Measured safe (recall@k held: LoCoMo 0.27→0.26 dropping 10 dups at 0.92); yield is modest at safe thresholds on paraphrase-heavy data and grows with literal redundancy/scale. -
MCP server (
python -m midas.mcp_server) —remember(auto-derives importance from content),recall(source-traceable),build_context,maintain(no-LLM retention: dedup + selective forgetting, returns the deletion audit of removed ids),stats(counts + temporal-tier distribution),forget,forget_all. Optional SQLite persistence viaMIDAS_MCP_DB; optional bounded memory viaMIDAS_MCP_MAX_RECORDS(auto-forget the lowest-value tail over the cap). The privacy/cost/provenance/retention surface for long-running and enterprise agents. -
Zero-config auto-memory (LLM-free) — install the MCP server and Midas starts remembering on its own. The server injects a memory policy into the agent (MCP
instructions+ amemory_sessionprompt): recall-then-capture.Memory.capture()+MemoryPolicyimpose the relevance parameters — it scores each turn's importance, enforces a floor (MIDAS_MCP_MIN_IMPORTANCE, default 2) and skips duplicates, and reports stored/skipped + why. The agent captures freely; Midas decides what's kept. -
Eval harness (
eval/, dev-only) — LoCoMo + LongMemEval + multiday + conflicts-v1 loaders; deterministicrecall@k/precision@k; optionalanswer_dumb(--dumb-reader); optionalctx_stale/ctx_contradict; per-adapter cost/latency instrumentation; optional LLM judge (hosted or local Ollama, seed-pinned + serialized for reproducibility). Reader and judge models are decoupled (--reader-modelvs--judge-model). Retention harness with--traceforgetting audits. Seedocs/methodology.md. -
Artifacts —
BENCHMARKS.md(reader-independent results + reproduce commands),docs/methodology.md(anti-cheating + failure cases + verbatim policy),docs/research-notes.md(measured findings), a coding-agent demo, PEP 561 typing (py.typed), and an MIT license.
Measured (see BENCHMARKS.md)
- Retrieval
recall@k: LongMemEval-s0.95 (n=40, time-aware) and LoCoMo 0.85 (5 conversations) vs a recency-window baseline ≤0.03. Time-awareness lifts temporal recall@k 0.86→0.95 (deterministic A/B,--midas-no-time), no real regression elsewhere. - Answer correctness (reader = gpt-4.1-mini, n=40): Midas 0.82 vs baseline 0.05. Per-category
answer deltas are within run-to-run judge noise at n≤13, so we lead with
recall@k. - Same-reader head-to-head (judge=gpt-4o, structured answerer): Midas 0.84 @ gpt-4o = SOTA Observational Memory's 0.84, with zero LLM at ingest (OM runs an LLM per conversation); 0.87–0.89 @ gpt-5-mini vs OM 0.95. Midas leads multi-session (0.89 vs 0.872).
- Structured answerer (extract relevant dated entries + compute time deltas before answering) lifts non-reasoning readers (gpt-4o 0.76→0.84) and is neutral for reasoning readers.
- Ingest cost: 0 LLM calls, $0 API, 0 data egress (local embeddings only).
- In-memory recall latency ~0.2 µs/record after matrix caching (~70× the naive Python scan).
Notes
- Reader-independent metrics (
recall@k,precision@k,answer_dumb, cost) are primary; end-to-end answer correctness is reader-dominated and reported as secondary/noisy — seedocs/methodology.mdanddocs/research-notes.md.
