Skip to main content

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

  1. 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 (Embedder protocol): SentenceTransformerEmbedder, OnnxEmbedder (a local ONNX export, no PyTorch), or the stdlib HashingEmbedder fallback. Nothing is ever downloaded implicitly.
  2. Score (log-linear; each exponent w is configurable in ScoringConfig, and 0 turns a component off):
    • R relevance = a * bm25/max_bm25 + (1-a) * minmax(cos) over the candidates
    • T recency = floor + (1-floor) * 0.5^(age/half_life), where age = time since last use
    • I importance = the memory's HeapDB dose
    • U usefulness = HeapDB feature_risk(useful, features) = noisy-OR of the learned P(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?) and mem:<id>
    • rules = HeapDB rule multipliers (superseded x0.1)
  3. Learning: mark_used / mark_ignored insert a HeapDB retrieval event carrying those features (no text) and confirm or reject it with HeapDB.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 until forget(namespace=...). Each feedback call adds one small retrieval event, 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)

Source distribution for heapdb-memory 0.1.0
File Size Uploaded Provenance
heapdb_memory-0.1.0.tar.gz 178.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for heapdb-memory 0.1.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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