Skip to main content

🧠 Mneme

A cue-indexed tag memory for conversational agents — no embeddings, no LLM in the memory path.

PyPI Python License: MIT

Most agent memories embed every turn and distill it with a write-time LLM, then search by vector similarity — expensive and opaque. Mneme instead does what human recall does: index by cues, reinstate by cue-match, and spread activation along both shared meaning and the thread of conversation — with a plain inverted index over deterministic tags. No vector database, no language model, no embeddings.

At zero memory cost, under an identical controlled comparison, on each benchmark's primary metric Mneme is on par with a strong dense retriever (LoCoMo F1 52.4±0.1 vs 52.9; LongMemEval acc 62.8 vs 62.2) and beats the cheaper baselines (mem0 and BM25 on LoCoMo, BM25 on LongMemEval). Same answerer, judge, prompt, and budget; only the memory differs.


✨ Why Mneme

  • Zero cost. No write-time LLM calls or embeddings; storage is local SQLite.
  • Competitive quality. Matches or beats embedding + LLM memory on standard benchmarks (see below).
  • Interpretable. Retrieval is explicit cue overlap ranked by IDF — no opaque similarity threshold to tune.
  • Human-like forgetting. What has no distinctive cue, or whose cues never recur, is simply never surfaced — forgetting as retrieval failure, for free.
  • Local & sovereign. Your memory lives in a local transactional SQLite store. English by default; Chinese optional.

📊 Benchmarks (controlled: same answerer/judge/prompt/budget, only the memory differs)

LoCoMo — low-distractor dialogue (F1 / accuracy):

method cost F1 acc
mem0 (LLM extract + vectors) LLM+emb 40.4 57.3
BM25 (raw turns) zero 45.8 57.0
Mneme (hybrid edges) zero 52.4 67.5
dense retriever (bge-large) embeddings 52.9 67.7

LongMemEval — high-distractor, ~50 sessions per question (accuracy):

method cost acc
BM25 (raw turns) zero 61.2
dense retriever (bge-large) embeddings 62.2
Mneme (hybrid) zero 62.8

Zero-cost Mneme is on par with dense retrieval on both benchmarks, at no embedding or write-time cost, and it is reader-agnostic (near-identical with two different answerer LLMs) and interpretable. Which edge type helps is regime-sensitive: discourse edges clearly help clean dialogue (~5 points on LoCoMo), while on high-distractor histories the edge configurations differ only within run-to-run noise.

🚀 Install

pip install mnemekit
python -m spacy download en_core_web_sm     # English model (required)

pip install "mnemekit[zh]"                   # optional: Chinese (jieba)

New in 0.9: identity-bound evidence composition

search_composed_evidence() applies Mneme4 after activation. It preserves the first ten sources, composes identity-correct evolving or joint evidence in the tail, and returns complete original messages as independently addressable evidence items. Direct queries retain the Mneme3 source order.

from mneme import EvidenceBudget, Memory

memory = Memory(root="~/.mneme")
evidence = memory.search_composed_evidence(
    "How did my plan change?",
    topn=100,
    scope_owner="user",
    budget=EvidenceBudget(limit=100_000, measure=search_tokens),
)

The optional scope is explicit: per-user conversational stores can bind “my” state to the user role, while multi-party stores can instead supply stable participant_ids. Budget admission is source-atomic and strict; an oversized source is skipped whole. When a budget is supplied, Mneme preserves the first 40 directly retrieved sources and then alternates remaining direct evidence with adjacent sources from corroborated multi-party events. This cascading recollection expands context only after a structurally supported Top10 trigger; fixed-source calls retain the identity-state and joint allocator. No Reader, Judge, embedding model, or LLM participates in retrieval.

Callers whose rendered budget is the sum of independent item costs can pass an item_measure to EvidenceBudget. Composed evidence then measures each source once while preserving the tuple-level measure as the compatibility path for non-additive formats.

New in 0.8: bounded evidence materialization

topn limits evidence items, while an item may contain many immutable source spans. Callers can now additionally bound materialization using the token count of the exact evidence representation sent to their Reader. Mneme retains the existing order and stops after the first atomic source span that crosses the budget, so the result contains the same Reader-visible prefix without loading and returning the unseen tail.

from mneme import EvidenceBudget, EvidenceProjection, Memory

def search_tokens(items):
    payload = render_search_response(items)
    return len(tokenizer.encode(payload))

memory = Memory(root="~/.mneme")
packet = memory.search_evidence(
    "what changed after the move?",
    topn=100,
    projection=EvidenceProjection.ACTIVATION_PACKET,
    budget=EvidenceBudget(limit=100_000, measure=search_tokens),
)

The callback keeps the core independent of any tokenizer or Reader format. It must measure the same ordered evidence content the Reader consumes, excluding transport-only JSON fields, and its cumulative measurement must not decrease as source spans are appended. Source spans remain atomic, so callers should leave headroom between this limit and the Reader's hard context limit. Omitting budget preserves the complete 0.7 evidence result. Budgets currently apply to the tag-family and activation-packet projections. Package 0.8.2 introduced logarithmic prefix probes that find the same first over-budget span for every tag-family projection, including the compatibility default. Package 0.8.3 also avoids materializing candidates that cannot survive source deduplication while preserving the exact bounded Evidence result.

New in 0.7: activation-preserving evidence packets

Mneme can now preserve cue-to-source responsibility throughout activation and project it as one lossless, source-addressable evidence packet. Tags remain internal access nodes; packet spans retain their independent source IDs, roles, timestamps, and original text. The packet preserves the activation field's source order, then uses responsibility coordinates to organize graph-reachable tail sources. It improves token-budget source recall over the canonical tag-container projection in every non-saturated tested 8K, 24K, and 100K cell on LoCoMo, LongMemEval-S, and BEAM-100K without changing source Recall@10/20/40.

The projection is the stable ALG-GHM-2 method. TAG_GRAPH_DEDUP remains the pkg 0.7 compatibility default so existing callers do not receive a different item shape implicitly:

from mneme import EvidenceProjection, Memory

memory = Memory(root="~/.mneme")
packet = memory.search_evidence(
    "what changed after the move?",
    topn=100,
    projection=EvidenceProjection.ACTIVATION_PACKET,
)

Reader and judge models are not part of this path. Retrieval quality is reported separately under source-count and tokenizer-exact evidence budgets.

New in 0.6

The retrieval network is now disk-backed rather than serialized as one Python object graph. SQLite stores scalar nodes, compressed ordered posting blocks, and incidence sets separately; search reads only cue-reachable state, while turns, events, and legacy recall indexes load on demand. Index writes remain transactional and are batched at commit. On the synthetic 10,000-turn AML-shaped benchmark, reopening takes 0.017 seconds at 167.1 MB RSS; the database is 102.2 MB and Add P95 grows 1.69× from 1,000 to 10,000 turns. The retained real-Store gate preserves byte-for-byte Evidence parity across all six fixed queries. Its cold open is 0.0132 seconds, Search is 2.64–4.94 seconds, and the migrated database is 226.7 MB. A 0.5 store requires explicit migration before writing.

New in 0.5

Mneme now persists turns and events incrementally in a transactional SQLite WAL store. Compressed retrieval-state checkpoints make reopening proportional to a short tail rather than the full turn history. remember_many() commits an ordered multi-message Add atomically, and 0.4.x JSON stores remain readable with an explicit migration path for subsequent writes.

New in 0.4

Mneme separates access, evidence, and immutable source provenance. The canonical search projects the activated tag/event graph into deduplicated, role-preserving evidence containers under an explicit item and token budget. Typed causal provenance participates inside the same conserved activation field; there is still no embedding, language model, or learned reranker in the memory path. The existing Memory.recall interface and old stored turns remain compatible.

⚡ Quick start

from mneme import Memory

m = Memory(root="~/.mneme")

m.add(
    "My dog Lucky is a golden retriever",
    role="user",
    session_id="chat-42",
    round_id=1,
    exchange_id="chat-42:x1",
)
m.add(
    "Cute! How old is Lucky?",
    role="assistant",
    session_id="chat-42",
    round_id=2,
    exchange_id="chat-42:x1",
)

m.search("what breed is my dog")     # -> [SourceTrace(...), ...]
m.search_evidence(
    "what breed is my dog",
    topn=50,
)                                      # -> [EvidenceItem(...), ...]

add preserves every role as a separate immutable source trace. Shared exchange and event identifiers organize traces without concatenating them. search activates the stored network once, takes one global Top-K, and reinstates the raw source text. The original remember(query, response) and recall interfaces remain available for compatibility.

search_evidence separates access from returned evidence. The default TAG_GRAPH_DEDUP projection puts direct query tags and tags reached through graph activation in one evidence pool. Each tag is one result item containing the raw source spans attached to that cue. Typed response provenance can reinstate its causal source inside the same activation field; it does not append context after selection. Spans are ordered by activation, exact activation ties prefer the shorter lossless source, and a source is emitted only once across items. Every span retains its source ID, role, timestamp, and full text. Explicit TAG and TAG_GRAPH preserve the corresponding non-deduplicated views for ablations. Top-K is applied to evidence items after projection; it is never followed by a hidden expansion step. The library default is 50 items; callers may request the Agent Memory Leaderboard maximum of 100.

By default, an exchange is also its lossless micro-event. Pass the same explicit event_id to organize several exchanges into one evolving event. Mneme does not silently merge exchanges using similarity thresholds or timing rules.

For ordered multi-message ingestion, remember_many() accepts a list of RememberInput values and commits them in one SQLite transaction. A later search sees the complete batch immediately; a failed batch exposes none of its turns.

from mneme import Memory, RememberInput

m.remember_many([
    RememberInput(query="I moved to Kyoto.", session_id="chat-42", round_id=3),
    RememberInput(response="Noted.", session_id="chat-42", round_id=4),
])

New stores use store.sqlite3 in WAL mode. The current storage schema keeps the retrieval index queryable on disk and loads immutable turn text on demand; checkpoint() remains as a compatibility boundary but no longer serializes the whole graph. Existing 0.4.x JSON and 0.5–0.7 SQLite stores remain readable but are write-protected until the caller explicitly invokes migrate_store():

m = Memory(root="path/to/legacy-store")
m.migrate_store()

The synthetic capacity benchmark is available as python benchmarks/store.py --checkpoints 100 1000 5000 10000. Use --batch-size 20 to measure the batch path used by multi-message Add requests.

🔍 How it works

INGEST (no LLM, no embedding)
  turn ──▶ tag extraction (spaCy NER + lemmas / jieba) ──▶ inverted index  (tag → turns)
                                                           + event tag inheritance

RECALL (per query) — spread over an ephemeral heterogeneous graph, then discard it
  query ─▶ ① lexical: IDF cue reinstatement ─seeds─┬─▶ ② associative edge (shared tag, 1 hop)
                                                   └─▶ ③ discourse edge (adjacent turn in event)
                                                                        │
                                                          recalled context ─▶ your reader
  1. Cue index. Each turn is tagged deterministically; tags form an inverted index (a sparse cue index, à la hippocampal indexing). Keyword-free follow-ups inherit their event's tags, so they stay recallable.
  2. Reinstatement, then two kinds of edge. A query reinstates directly-cued turns lexically, then spreads activation over an ephemeral graph with two edge types: associative edges (shared discriminative cue → one hop) reach the associative tail a direct cue can't; discourse edges (adjacent turns in the same event) pull in the local conversational context around a hit. Neither is materialized — both are read straight off the index and event structure, used for the one query, and discarded.

🀄 Chinese & custom dictionaries

m = Memory(root="~/.mneme", userdict="my_terms.txt")   # jieba user dictionary
m.remember("我养了只金毛狗,叫 Lucky", "可爱!", session_id="u1", round_id=1)

Chinese is segmented by jieba; a custom dictionary keeps vertical-domain terms intact.

📄 Papers & reproduction

The repository keeps each research generation in a numbered folder whose short title states its main step:

Generation Direction Paper folder
Mneme1 Cue-indexed memory paper-1-cue-indexed-memory/
Mneme2 Grounded hierarchical memory paper-2-grounded-hierarchical-memory/
Mneme3 Activation before allocation paper-3-activation-before-allocation/
Mneme4 Identity-bound evidence composition paper-4-identity-bound-evidence-composition/

The evaluation harness is in eval/. Frozen reproduction versions use the semantic paper tags listed in VERSIONING.md.

Repository/package releases and research-algorithm generations use separate version axes. See VERSIONING.md for the authoritative mapping; do not infer an algorithm generation from the package number.

📌 Citation

@misc{mneme2026,
  title  = {Do Conversational Agents Need Vectors? A Zero-LLM, Zero-Embedding Tag Memory},
  author = {Hao, Shaochun},
  year   = {2026},
  note   = {https://github.com/FTP2026/Mneme}
}

License

MIT

Metadata

Release files for mnemekit 0.9.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mnemekit 0.9.2
File Size Uploaded Provenance
mnemekit-0.9.2.tar.gz 54.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mnemekit 0.9.2
File Interpreter ABI Platform
mnemekit-0.9.2-py3-none-any.whl Python 3 none any Details

Total release size: 108.0 kB

Release files / mnemekit-0.9.2.tar.gz

Download URL mnemekit-0.9.2.tar.gz
Size 54.9 kB
Tags Source
SHA-256 checksum
How to use checksums
1b184a8da7d2efab1e01c497141d1f38ca1feb8c72c22d4335f079317b93983d
BLAKE2b-256 checksum
How to use checksums
1d5c064c2f8cf2cbe44e65536e350658a3d59928941216a8287fb4397a002ec7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release files / mnemekit-0.9.2-py3-none-any.whl

Download URL mnemekit-0.9.2-py3-none-any.whl
Size 53.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
26105ec49aa887c6d5a712c481ee601aed43f8cc78089d4762047e9c9b7fb998
BLAKE2b-256 checksum
How to use checksums
c2e3d63e644c112e43b34151a204a0db513f0685670ad1a4404c74648edb99c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Security for mnemekit 0.9.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page