heapdb-memory
Long-term memory for AI agents, built on HeapDB. Every recalled memory says why it ranked, and the store learns which memories your agent actually uses.
Most agent-memory stores rank by similarity alone. heapdb-memory ranks each
candidate memory by
score = relevance x recency-since-last-use x importance x learned usefulness x rules
and returns a one-line EXPLAIN for each result (from examples/quickstart.py):
RECALL 'is Alice vegetarian? what does she eat?' IN user:alice (4 candidates)
1. [f4a74b8e042d] Alice eats fish now (pescatarian) score=0.1621
rel 0.93 (bm25 0.93) x recency 1.00 (age 0.0d) x importance 1.10 x useful 0.16 [date:no=0.02, ent:no=0.02, kind:preference=0.02]
2. [0b4d4db47864] Alice's flight to Lisbon leaves Friday 9:40 score=0.0161
rel 0.14 (bm25 0.14) x recency 0.90 (age 10.0d) x importance 0.95 x useful 0.14 [source:unknown=0.01, q:no=0.01, qt:what&date:yes=0.01]
3. [08efc219e110] Alice is vegetarian and allergic to peanuts score=0.0159
rel 1.00 (bm25 1.00) x recency 0.90 (age 10.0d, used 1x) x importance 1.04 x useful 0.17 [date:no=0.02, ent:no=0.02, kind:preference=0.02] x rules superseded x0.1
When the agent tells it which recalled memories it used (mark_used) and
which it ignored (mark_ignored), HeapDB's Beta links learn which kinds of
memory are useful for which kinds of question. Examples: dated memories for
"when" questions, preferences over raw tool output, what the user said over
what the agent said. Each confirmed use also resets that memory's decay, so
memories that keep paying off stay fresh.
Status: alpha (v0.1). It is small, readable, and pure Python apart from optional extras. It runs in a single process with file-backed storage. HeapDB core is being rewritten in Rust with the same Python API, and this package uses only that public API. APIs may change.
Install
pip install "heapdb-memory[mcp]"
# extras: mcp, langgraph, llamaindex, embeddings (sentence-transformers), onnx (local ONNX encoder)
SDK quickstart
from heapdb_memory import Memory
mem = Memory("./memory", namespace="user:alice", autosave=True) # dir is created / loaded
mem.add("Alice is vegetarian and allergic to peanuts", kind="preference", key="diet")
mem.add("Alice's flight to Lisbon leaves Friday 9:40", kind="fact", tags=["travel"])
mem.add("weather api: rain in Lisbon on Friday", kind="tool_result", source="tool")
res = mem.recall("plan Alice's Friday dinner", k=3)
print(res) # ranked, with explanations
for r in res:
r.id, r.text, r.score, r.explain() # also r.relevance, r.recency, r.importance, r.usefulness
mem.feedback(used=[res[0].id]) # used -> confirm; the other shown ones -> ignored
mem.add("Alice eats fish now", kind="preference", key="diet") # supersedes the old diet memory
mem.forget(res[2].id) # or mem.forget(namespace="user:alice") to erase a user
print(mem.stats())
print(mem.explain_usefulness()) # which kinds of memory turned out useful
| method | what it does |
|---|---|
add(text, kind=, importance=, tags=, namespace=, source=, key=, created_at=, metadata=) |
store a memory. importance is the dose (default: kind/length heuristic). With key, it supersedes the older memory with the same key |
recall(query, k=5, namespace=, now=, kinds=, tags=, explain=True, config=) |
top-k Recalled items with the score broken down |
mark_used(ids) / mark_ignored(ids) |
learning signal (HeapDB confirm / reject). Used memories also refresh last_used |
feedback(used, recall_id=None) |
used are confirmed, the other memories of that recall are ignored |
update(id, text), supersede(old, new) |
new versions; old ones are down-weighted by a HeapDB rule (x0.1) and stay recallable |
forget(ids), forget(namespace=...) |
delete memories, or a whole namespace including its learned links (forget_entity) |
list(...), get(id), stats(), explain_usefulness() |
introspection |
save(path), Memory.load(path) |
persistence (HeapDB snapshot + memories.jsonl + vector cache) |
Kinds with built-in importance defaults: instruction 1.5, preference 1.3,
summary 1.2, fact 1.0, note 1.0, episode 0.8, tool_result 0.6. Any
other string is accepted as a kind too.
MCP server
heapdb-memory-mcp serves the store over MCP (stdio by default,
--transport streamable-http optional). It works with the official mcp
Python SDK 1.x and 2.x.
Tools: remember, recall (memories plus a one-line explanation each),
mark_used, mark_ignored, forget, list_memories. Each tool takes an
optional namespace.
| setting | flag | env | default |
|---|---|---|---|
| storage dir | --dir |
HEAPDB_MEMORY_DIR |
~/.heapdb-memory |
| default namespace | --namespace |
HEAPDB_MEMORY_NAMESPACE |
default |
| embedder | --embedder |
HEAPDB_MEMORY_EMBEDDER |
none (hashing, onnx:<dir>, st:<model>) |
Claude Code
claude mcp add heapdb-memory -e HEAPDB_MEMORY_NAMESPACE=user:me -- heapdb-memory-mcp
Claude Desktop (claude_desktop_config.json) or Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"heapdb-memory": {
"command": "heapdb-memory-mcp",
"env": { "HEAPDB_MEMORY_DIR": "/Users/me/.heapdb-memory", "HEAPDB_MEMORY_NAMESPACE": "user:me" }
}
}
}
If the script is not on the client's PATH, use the absolute path of the
venv's heapdb-memory-mcp, or "command": "python", "args": ["-m", "heapdb_memory.mcp_server"].
The server's instructions tell the model to recall before answering and to
report mark_used afterwards.
Framework adapters
LangGraph: HeapDBStore is a langgraph.store.base.BaseStore
(put/get/search/delete/list_namespaces). search(query=...) is a
HeapDB recall, and SearchItem.score is the HeapDB score.
from heapdb_memory.adapters.langgraph import HeapDBStore
store = HeapDBStore(Memory("./memory"))
graph = builder.compile(store=store)
store.put(("user", "alice"), "diet", {"text": "Alice is vegetarian", "kind": "preference"})
hits = store.search(("user", "alice"), query="dinner ideas", limit=3)
store.mark_used([hits[0]]) # learning signal (beyond BaseStore)
LlamaIndex (llama_index.core.memory.Memory with memory blocks):
from llama_index.core.memory import Memory as LIMemory
from heapdb_memory.adapters.llamaindex import HeapDBMemoryBlock
block = HeapDBMemoryBlock(memory=Memory("./memory"), namespace="user:alice", k=5)
li_memory = LIMemory.from_defaults(session_id="alice", memory_blocks=[block])
block.feedback(used=[...]) # ids shown by the last aget()
How recall works
- Candidates: the top 50 memories by BM25 (stdlib implementation) are
unioned with the top 50 by embedding cosine, if an embedder is
configured. Embedders are pluggable (
Embedderprotocol):SentenceTransformerEmbedder,OnnxEmbedder(a local ONNX export, no PyTorch), or the stdlibHashingEmbedderfallback. Nothing is ever downloaded implicitly. - Score (log-linear; each exponent
wis configurable inScoringConfig, and 0 turns a component off):Rrelevance =a * bm25/max_bm25 + (1-a) * minmax(cos)over the candidatesTrecency =floor + (1-floor) * 0.5^(age/half_life), whereage= time since last useIimportance = the memory's HeapDB doseUusefulness = HeapDBfeature_risk(useful, features)= noisy-OR of the learnedP(useful | f)over the retrieval's features: the memory's shape features (kind:,source:,len:,date:,num:,q:,ent:,topic:,tag:), those features crossed with the query type (qt:when&date:yes),qsrc:(does the query name the memory's source?) andmem:<id>rules= HeapDB rule multipliers (supersededx0.1)
- Learning:
mark_used/mark_ignoredinsert a HeapDBretrievalevent carrying those features (no text) and confirm or reject it withHeapDB.feedback. Credit is proportional, and evidence is pooled per namespace plus globally, with hierarchical priors. A new namespace starts from what all the others have learned.
Details, defaults and design decisions: docs/agent-memory.md.
Evaluation: LoCoMo
Evidence retrieval on LoCoMo
(Maharana et al., ACL 2024). The dataset is CC BY-NC 4.0 and is downloaded
at run time, never committed. Reproduce with python -m evals.locomo.run --onnx-model DIR && python -m evals.locomo.report; see
evals/locomo/README.md. Full results, per-category
numbers, ablations, latency and the analysis are in
evals/locomo/RESULTS.md.
LoCoMo evidence retrieval (10 conversations, 5882 dialog turns as memories, 1982 questions with evidence). Recall@k = share of a question's evidence turns in the top k; MRR = 1/rank of the first evidence turn. Per-conv MRR = mean ± std over the 10 conversations (for +learned methods these are the 10 leave-one-conversation-out folds: usefulness learned only from the other 9 conversations' QA feedback).
| method | R@1 | R@5 | R@10 | MRR | per-conv MRR |
|---|---|---|---|---|---|
bm25 |
0.319 | 0.544 | 0.622 | 0.459 | 0.461 ± 0.029 |
recency |
0.000 | 0.002 | 0.010 | 0.005 | 0.005 ± 0.003 |
bm25+recency |
0.280 | 0.511 | 0.600 | 0.417 | 0.417 ± 0.020 |
embedding |
0.141 | 0.331 | 0.425 | 0.263 | 0.261 ± 0.042 |
bm25+embedding |
0.300 | 0.568 | 0.661 | 0.463 | 0.462 ± 0.036 |
heapdb |
0.284 | 0.518 | 0.605 | 0.421 | 0.422 ± 0.018 |
heapdb+learned |
0.317 | 0.542 | 0.622 | 0.454 | 0.455 ± 0.021 |
heapdb+learned+tuned |
0.358 | 0.571 | 0.648 | 0.495 | 0.496 ± 0.027 |
bm25+embedding+tuned |
0.321 | 0.573 | 0.654 | 0.474 | 0.474 ± 0.033 |
heapdb+emb |
0.231 | 0.468 | 0.585 | 0.376 | 0.377 ± 0.036 |
heapdb+emb+learned |
0.272 | 0.508 | 0.619 | 0.419 | 0.419 ± 0.036 |
heapdb+emb+learned+tuned |
0.374 | 0.605 | 0.687 | 0.525 | 0.526 ± 0.032 |
Paired difference in MRR vs bm25 (bootstrap over questions, 95% CI):
| method | ΔMRR | 95% CI | significant |
|---|---|---|---|
recency |
-0.454 | [-0.472, -0.435] | yes |
bm25+recency |
-0.042 | [-0.050, -0.034] | yes |
embedding |
-0.196 | [-0.216, -0.176] | yes |
bm25+embedding |
+0.005 | [-0.005, +0.014] | no |
heapdb |
-0.037 | [-0.046, -0.029] | yes |
heapdb+learned |
-0.005 | [-0.014, +0.004] | no |
heapdb+learned+tuned |
+0.037 | [+0.030, +0.044] | yes |
bm25+embedding+tuned |
+0.015 | [+0.010, +0.021] | yes |
heapdb+emb |
-0.082 | [-0.096, -0.069] | yes |
heapdb+emb+learned |
-0.040 | [-0.053, -0.026] | yes |
heapdb+emb+learned+tuned |
+0.066 | [+0.058, +0.075] | yes |
In short: on LoCoMo, learned usefulness is the main gain (+0.033 MRR
over BM25 on held-out conversations). With hybrid relevance and the weights
picked by nested cross-validation on training conversations, HeapDB scoring
is +0.051 MRR over an equally tuned BM25+MiniLM hybrid. The default
recency decay hurts on this benchmark (-0.04 MRR). LoCoMo asks about any
point in a long history and never re-uses memories, so the untuned defaults
do not beat plain BM25. For archive-style recall, set w_recency=0. A
recall over ~600 memories takes about 1.6 ms (lexical) or about 3 ms (with
the ONNX query embedding) in pure Python.
Limitations
- Single process, in-memory indexes, file persistence (JSONL). Exact vector search.
- HeapDB events are immutable.
forget(id)removes the text and index entries at once, but the memory's feature-only HeapDB events stay as blocked tombstones untilforget(namespace=...). Each feedback call adds one smallretrievalevent, so the HeapDB log grows with feedback. - Shape features and query types are English heuristics.
- Learning is association, not causation. The LoCoMo gains were measured with oracle feedback; real agent feedback is noisier.
- The benefit of recency and reinforcement for agent workloads is plausible but not shown by LoCoMo.
Development
python -m venv .venv && . .venv/bin/activate
pip install -e ../heapdb-core # optional: a local heapdb-core checkout (needs Rust); otherwise pip uses the PyPI wheel
pip install -e ".[mcp,langgraph,llamaindex,onnx,test]"
pytest
See CONTRIBUTING.md. License: Apache-2.0.
Metadata
Release files for heapdb-memory 0.1.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 | Provenance | |
|---|---|---|---|---|
| heapdb_memory-0.1.0.tar.gz | 178.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| heapdb_memory-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 224.2 kB
Release files / heapdb_memory-0.1.0.tar.gz
| Download URL | heapdb_memory-0.1.0.tar.gz |
|---|---|
| Size | 178.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d3f9f2df95ef942cf5a7db5fcee25016aff97b77501363486e529a502879a34b
|
|
BLAKE2b-256 checksum How to use checksums |
e4eac039aae59b9f3d8bf28dd8af5057a30f8f5d8ab41d7dfd1131f98e426ba4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / heapdb_memory-0.1.0-py3-none-any.whl
| Download URL | heapdb_memory-0.1.0-py3-none-any.whl |
|---|---|
| Size | 45.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7c744f378d9c4a8d06306e779a1e60977a6e30bca8a440928ea3f2e504f31e6a
|
|
BLAKE2b-256 checksum How to use checksums |
b97fea29aa79902b77c5d8852e8ecd79350483083de07225eb478a928f8fdca2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for heapdb-memory 0.1.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for heapdb_memory-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA 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.