Skip to main content

Local-first persistent memory for AI agents. SQLite-backed, zero required dependencies, pluggable embeddings, framework adapters and an MCP server.

Project description

remembrane

Local-first persistent memory for AI agents. One SQLite file, zero required dependencies. Exact hybrid recall (vector + BM25 — never approximate), explainable ranking, time-travel over memory history, conflict-aware recall that admits uncertainty, salience learned from task outcomes, optimal token-budget packing, and deterministic behavior you can unit-test in CI. Adapters for LangChain and CrewAI, plus a built-in MCP server.

pip install remembrane

Why

Agents forget everything between sessions. Existing memory solutions are cloud APIs, require a vector database, or drag in a heavyweight framework. remembrane is the opposite:

  • One file. Your agent's entire memory is a SQLite database you can copy, back up, diff, or delete.
  • Zero required dependencies. The default embedder is pure stdlib. pip install remembrane pulls in nothing else.
  • Human-like recall. Results are ranked by a weighted sum of similarity, recency decay (halves every week by default), importance, and outcome-earned usefulness. Recalled memories are reinforced — spaced repetition for agents.
  • Exact, not approximate. Large systems use approximate nearest-neighbor search and accept missed results. At agent-memory scale, remembrane scores every memory — hybrid vector + BM25 keyword in one pass, guaranteed complete.
  • A memory you can debug. Every store/forget/reinforce is journaled. Snapshot, diff, and reconstruct what your agent knew at any point in time. Every recall result explains exactly why it ranked where it did.
  • Testable in CI. Deterministic embedder + frozen-time recall = reproducible memory behavior. remembrane.testing ships pytest-friendly assertions.
  • Framework-agnostic. Use it bare, through the LangChain or CrewAI adapters, or expose it to any MCP-capable agent (like Claude) as an MCP server.

Quick start

from remembrane import MemoryStore

mem = MemoryStore("agent.db")            # or ":memory:" for ephemeral

mem.store("User prefers dark mode", importance=0.8)
mem.store("Deploy target is AWS us-east-1", namespace="ops")

results = mem.recall("what theme does the user like?")
print(results[0].memory.content)         # → "User prefers dark mode"
print(results[0].score)                  # weighted: similarity + recency + importance + usefulness

Memory lifecycle

mem.reinforce(memory_id)                  # strengthen: slower decay, higher rank
mem.forget(memory_id)                     # delete one
mem.forget(namespace="ops")               # delete a namespace
mem.forget(older_than_seconds=30*86400)   # prune stale memories
mem.consolidate()                         # merge near-duplicates
mem.export()                              # plain dicts, ready for json.dump

Tuning recall

from remembrane import MemoryStore, ScoringConfig

mem = MemoryStore(
    "agent.db",
    scoring=ScoringConfig(
        weight_similarity=0.65,
        weight_recency=0.15,
        weight_importance=0.10,
        weight_usefulness=0.10,            # earned from mark_useful()/mark_useless()
        half_life_seconds=7 * 24 * 3600,   # recency halves every week
    ),
)

Embedders

The default HashEmbedder is deterministic, offline, and dependency-free — it hashes word and character n-grams. That makes similarity lexical, not semantic. It works well for typical agent memories (facts, preferences, short statements). For true semantic recall, plug in a real model:

from remembrane import MemoryStore, SentenceTransformerEmbedder, OpenAIEmbedder

mem = MemoryStore("agent.db", embedder=SentenceTransformerEmbedder())   # local, pip install remembrane[sentence-transformers]
mem = MemoryStore("agent.db", embedder=OpenAIEmbedder())                # API,   pip install remembrane[openai]

Any object with embed(texts) -> List[List[float]] and a dimension attribute works.

Note: don't mix embedders in one database. Vectors from different embedders aren't comparable.

LangChain

from remembrane import MemoryStore
from remembrane.adapters import RemembraneChatMemory

memory = RemembraneChatMemory(MemoryStore("agent.db"), session_id="user-42")

memory.save_context({"input": "my favorite color is teal"}, {"output": "Noted!"})
memory.load_memory_variables({"input": "what color do I like?"})
# {'history': 'human: my favorite color is teal\nai: Noted!'}

Unlike buffer memory, this retrieves the exchanges relevant to the current input — the context window stays small no matter how long the history grows.

CrewAI

from remembrane import MemoryStore
from remembrane.adapters import RemembraneStorage

storage = RemembraneStorage(MemoryStore("crew.db"))
storage.save("the deadline is next friday", metadata={"task": "planning"})
storage.search("when is the deadline?")

MCP server

Give any MCP-capable agent (e.g. Claude Desktop, Claude Code) persistent memory:

pip install remembrane[mcp]
remembrane-mcp --db ~/agent-memory.db
{
  "mcpServers": {
    "remembrane": {
      "command": "remembrane-mcp",
      "args": ["--db", "/path/to/agent-memory.db"]
    }
  }
}

Tools exposed: memory_store, memory_recall, memory_forget, memory_reinforce, memory_stats.

CLI

remembrane --db agent.db store "the user prefers dark mode" --importance 0.8
remembrane --db agent.db recall "what theme?"
remembrane --db agent.db list
remembrane --db agent.db stats
remembrane --db agent.db export > backup.json

Conflict-aware recall

Every other memory system silently resolves contradictions and returns one confident answer — which is how agents end up confidently wrong. remembrane surfaces the tension and lets the agent adjudicate (or ask the user):

mem.store("the user lives in London")
mem.store("the user moved to Tokyo, no longer in London")

for c in mem.conflicts("where does the user live?"):
    print(c.describe())
# Conflicting memories (likely, change_markers=['longer', 'moved', 'no']):
#   older: 'the user lives in London' (recalled 4x)
#   newer: 'the user moved to Tokyo, no longer in London' (recalled 0x)

mem.resolve(keep_id=newer.id, drop_ids=[older.id], reason="user confirmed Tokyo")

Detection is deterministic and free (anchor-word overlap + change markers + numeric mismatches — honest heuristics, not hidden LLM judgments). Resolutions are journaled, so every settled conflict stays auditable via log() and as_of(). Also exposed as the memory_conflicts / memory_resolve MCP tools and remembrane conflicts CLI.

Salience earned from outcomes

Cloud systems decide what matters at write time, with an LLM call you pay for on every memory. remembrane inverts it: writes are free, and importance is earned by helping:

results = mem.recall("how do I deploy this?")
# ... agent completes its task using results[0] ...
mem.mark_useful(results[0].memory.id)     # this memory rises
mem.mark_useless(results[2].memory.id)    # this one fades

Feedback accumulates into a usefulness signal (sigmoid-squashed into ranking, neutral at zero). Memories that keep helping outrank memories that merely match — learned per-deployment, from real outcomes, with zero LLM calls.

Token-budget packing

Agents don't want "top 5 results"; they want the best use of the context window space they have left:

context = mem.pack("user preferences", budget_tokens=800)
sum(r.tokens for r in context)   # <= 800, guaranteed

pack() scores every candidate exactly, suppresses near-duplicates so the budget is never spent saying the same thing twice, then solves the selection exactly (0/1 knapsack) — the returned set maximizes total relevance within the budget. Deterministic, no LLM, microseconds. Pass token_estimator=your_tokenizer for exact counts.

Time travel

Every mutation is journaled, so the past is queryable:

mem.snapshot("before-research")
# ... agent runs, learns things, forgets things ...

mem.diff("before-research")
# {'added': [{'content': 'competitor launched a new pricing tier', ...}],
#  'removed': [...], 'changed': [...]}

mem.as_of("before-research")          # full memory state at that point
mem.log()                             # newest-first history of every operation

Or from the CLI: remembrane snapshot v1, remembrane diff v1, remembrane log. "What did my agent believe last Tuesday, and what changed its mind?" is now an answerable question.

Explainable recall

No black boxes — every result carries its full ranking breakdown:

r = mem.recall("what theme does the user like?")[0]
r.explain()
# {'score': 0.6087, 'components': {'vector_similarity': 0.71, 'keyword_bm25': 1.0,
#   'combined_similarity': 0.81, 'recency': 0.98, 'importance': 0.8}, ...}
r.explain_text()
# 'score 0.609 = similarity 0.812 (vector 0.713, keyword 1.000) + recency 0.984 + importance 0.80 | recalled 3x'

Testing your agent's memory

Deterministic recall means memory behavior is unit-testable — something no cloud memory API can offer:

from remembrane.testing import assert_recalls, assert_recalls_first, assert_not_recalls

def test_agent_remembers_allergies():
    mem = build_agent_memory()
    assert_recalls_first(mem, "any food allergies?", "peanuts")
    assert_not_recalls(mem, "any food allergies?", "dark mode", k=1)

Pass now=... to recall() to freeze time and make recency scoring reproducible.

Merging memories

Memory files are portable — merge two agents' brains, with near-duplicate absorption:

mem.merge_from("other-agent.db")            # {'added': 12, 'merged': 3}
mem.merge_from("backup.db", namespaces=["prefs"], dedupe_threshold=0.95)

CLI: remembrane --db a.db merge b.db

How ranking works

score      = 0.65·similarity + 0.15·recency + 0.10·importance + 0.10·usefulness
recency    = exp(−ln2 · age / half_life)
usefulness = sigmoid(outcome feedback)

Scoring is a weighted sum (weights normalize to 1), with one hard rule on top: similarity must be positive for a memory to be returned at all — recency and importance rank relevant memories, they never substitute for relevance.

age is measured from the memory's last access, not creation — every recall resets the decay clock. Frequently-used memories stay vivid; untouched ones fade. In the default hybrid mode, similarity is 0.65·cosine + 0.35·bm25. All weights, the mode, and the half-life are configurable.

Design choices

  • SQLite over a vector DB — agent memory stores are small (thousands, not billions, of rows). Brute-force cosine over a few thousand vectors is sub-millisecond, and you gain transactions, a single portable file, and zero infra.
  • No background daemon — decay is computed at read time, so nothing runs when your agent doesn't.
  • Duck-typed adaptersremembrane never imports langchain or crewai; the adapters match their interfaces structurally, so there are no version-pinning fights.

Development

git clone https://github.com/satyasairay/remembrane
cd remembrane
pip install -e .[dev]
pytest

License

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

remembrane-0.3.1.tar.gz (34.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

remembrane-0.3.1-py3-none-any.whl (31.8 kB view details)

Uploaded Python 3

File details

Details for the file remembrane-0.3.1.tar.gz.

File metadata

  • Download URL: remembrane-0.3.1.tar.gz
  • Upload date:
  • Size: 34.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for remembrane-0.3.1.tar.gz
Algorithm Hash digest
SHA256 de17d1b2210ce60eff2e4d7103aaeb88c784ce9d9a4f1613f67cbca50a86788c
MD5 3d7fbe893e345eb4d933a83a3dd0332d
BLAKE2b-256 f34b67d90601313c7f294ab41e7754014c7f2921599232fd6f74c96278b31f2b

See more details on using hashes here.

File details

Details for the file remembrane-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: remembrane-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 31.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for remembrane-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b4c82b22070f916b1645a40e87d21c2dffaa25cc90eb3327089ecca1e898c45e
MD5 ff7c9eb56c16f9d6cb8fbeb01ebde06e
BLAKE2b-256 5a0b1d3be5c552df3b7699b720988ba8a28d53d1cb956bca3ca1c219384ba4a2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page