Show HN:Remembrane——一个 SQLite 文件实现零依赖 Agent 记忆
英文标题:🔶 Show HN: Remembrane – agent memory in one SQLite file, zero dependencies
Remembrane 是一个面向 AI Agent 的本地优先持久化记忆库,将全部记忆保存在单个 SQLite 文件中,默认无需额外依赖。它提供精确的向量与 BM25 混合召回、可解释排序、记忆历史回溯、冲突检测、基于任务结果学习显著性,以及受预算约束的上下文打包。项目支持 LangChain、CrewAI 和 MCP,可为 Claude 等 MCP 兼容 Agent 提供持久记忆。其定位并非替代大规模向量数据库:在约 1 万条记忆以内可获得较快召回,而超过约 5 万条记忆时,作者建议转向向量数据库方案。
108.6 KB
中文
面向 AI Agent 的本地优先持久记忆。 一个 SQLite 文件,零项必需依赖。精确混合召回(向量 + BM25——绝不近似)、可解释的排序、记忆历史时间旅行、承认不确定性的冲突感知召回、从任务结果中学习的显著性、受预算上限约束的上下文打包(安装 numpy 时可实现严格最优),以及可在 CI 中进行单元测试的确定性行为。提供 LangChain 和 CrewAI 适配器,并内置 MCP 服务器。
Agent 会在会话之间遗忘一切。现有记忆方案往往是云端 API、需要向量数据库,或引入重量级框架。remembrane 则恰好相反:
-
一个文件。 Agent 的全部记忆都是一个 SQLite 数据库文件,可以复制、备份、比较差异或删除。
-
零项必需依赖。 默认嵌入器仅使用 Python 标准库。
pip install remembrane不会拉取其他依赖。 -
类似人类的回忆。 结果按相似度、时间衰减(默认每周减半)、重要性和通过结果获得的有用性加权求和排序。被召回的记忆会得到强化——这是面向 Agent 的间隔重复。
-
精确,而非近似。 大型系统使用近似最近邻搜索,并接受漏掉部分结果。在 Agent 记忆的规模下,remembrane 会对每一条记忆评分——单次完成混合向量 + BM25 关键词检索,保证结果完整。
-
可调试的记忆。 每一次存储、遗忘和强化操作都会被记录到日志中。可以创建快照、比较差异,并重建 Agent 在任意时刻所知的内容。每个召回结果都会精确说明其排名原因。
-
可在 CI 中测试。 确定性嵌入器加上冻结时间的召回,使记忆行为可复现。
remembrane.testing提供适用于 pytest 的断言。 -
框架无关。 可直接使用,也可通过 LangChain 或 CrewAI 适配器使用,或将其作为 MCP 服务器暴露给任何支持 MCP 的 Agent(例如 Claude)。
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
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
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 ), )
默认的 HashEmbedder 具有确定性、可离线运行且无依赖——它会对词语和字符 n-gram 进行哈希。这意味着相似度是词法层面的,而不是语义层面的。它很适合典型的 Agent 记忆(事实、偏好、简短陈述)。如需真正的语义召回,可接入实际模型:
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]
任何具有 embed(texts) -> List[List[float]] 方法和 dimension 属性的对象都可以使用。
注意:不要在同一个数据库中混用嵌入器。不同嵌入器产生的向量不可比较。
对于当前版本的 LangChain(已针对 langchain-core 1.4 验证):
from langchain_core.runnables.history import RunnableWithMessageHistory
from remembrane import MemoryStore
from remembrane.adapters import RemembraneChatMessageHistory
store = MemoryStore("agent.db")
chain = RunnableWithMessageHistory(
runnable,
lambda session_id: RemembraneChatMessageHistory(store, session_id),
)
需要执行 pip install langchain-core(采用惰性导入——remembrane 的其余部分仍保持零依赖)。对于旧版 1.x 之前的代码,RemembraneChatMemory 仍提供旧的 save_context / load_memory_variables 接口,并支持语义检索——完全不需要安装 langchain。
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?") # also: delete / update / list_records / reset
一个采用鸭子类型的存储辅助工具(支持 save/search/delete/update/list_records/get_record/count/reset,且可容忍 kwargs)。**已知限制:**它不是已注册的 crewai.StorageBackend 子类,并且返回的是 dict,而不是 (MemoryRecord, score) 元组,因此截至 crewai 1.14,无法将其直接接入 crewai.Memory(...)——请直接使用,或在其外层加一层轻量 shim。原生 StorageBackend 集成已列入路线图。注意,CrewAI 本身会连接外部服务(telemetry.crewai.com);如对此有要求,请设置 CREWAI_DISABLE_TELEMETRY=true——裸 remembrane 不会打开任何 socket(已通过 Python 审计钩子下的审计验证)。
为任意支持 MCP 的 Agent(例如 Claude Desktop、Claude Code)提供持久记忆:
pip install remembrane[mcp]
remembrane-mcp --db ~/agent-memory.db
{
"mcpServers": {
"remembrane": {
"command": "remembrane-mcp",
"args": ["--db", "/path/to/agent-memory.db"]
}
}
}
暴露的工具包括:memory_store、memory_recall、memory_forget、memory_reinforce、memory_conflicts、memory_resolve、memory_feedback、memory_pack、memory_stats。每条记忆的存储内容上限为 10 万字符(可通过 REMEMBRANE_MAX_CONTENT 修改)。
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
其他所有记忆系统都会悄悄解决矛盾,并返回一个看似自信的答案——这正是 Agent 最终自信地答错的原因。remembrane 会显式呈现这种张力,并让 Agent 进行裁决(或询问用户):
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")
检测过程具有确定性且无需成本(锚点词重叠、否定标记、数值不匹配、值替换——诚实的启发式规则,而非隐藏的 LLM 判断)。提供两个置信度层级:likely(强否定、具有佐证的数字/星期/月不匹配,或类似“written in Python”→“written in Rust”的同模板值替换)和 possible(值得关注的主题性冲突)。针对一个包含 30 对对抗样本的独立审计显示,v0.4 的 likely 层级精确率为 0.875、召回率为 0.70;v0.5.0 为 0.889 / 0.80——有所提升,但并不完美。v0.5.1 新增了值替换信号,能够捕捉审计所指出的两项假阴性(技术和地区替换);尚未对完整数据集重新测量。已知限制:经过改写的(非模板化)矛盾可能仍停留在 possible 层级;描述两个共存事物的“旧 X / 新 X”组合可能被标记为 likely(若抑制这类情况,也会抑制真实的旧→新矛盾,因此项目没有这样做)。它仍然是一种启发式方法:应将冲突视为供 Agent 裁决的候选项,这正是其设计意图。可通过 conflicts(min_confidence='likely') 过滤。解决记录会被写入日志,因此每一项已解决冲突都可通过 log() 和 as_of() 审计。也可通过 memory_conflicts / memory_resolve MCP 工具,以及 remembrane conflicts CLI 使用。
从结果中获得的显著性
云端系统在写入时决定什么重要,并且每条记忆都需要一次需付费的 LLM 调用。remembrane 将这一机制反转:写入免费,重要性通过提供帮助而获得:
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
反馈会累积为有用性信号(经 sigmoid 压缩后进入排序,零值为中性)。持续提供帮助的记忆会排在仅仅匹配查询的记忆之前——基于真实结果、针对每个部署环境学习,且无需任何 LLM 调用。
Agent 不想要“前 5 条结果”;它们想要在剩余上下文窗口空间中实现最佳利用:
context = mem.pack("user preferences", budget_tokens=800)
sum(r.tokens for r in context) # <= 800, guaranteed
pack() 会精确地为每个候选项评分,抑制近似重复项,确保预算不会被用于重复表达同一内容;随后通过 0/1 背包问题求解选择结果。在任何配置下,预算都是硬性保证(已在数千次随机试验中验证)。最优性取决于执行路径:安装 numpy 时,方案可在 1 token 粒度实现严格最优;纯 Python 回退方案使用粗化权重并进行贪心补充,文档将其定义为近似最优而非最优(在对抗性随机实例中观察到的最差损失为 16%——真实记忆库远未接近这种情况)。具备确定性,不使用 LLM。传入 token_estimator=your_tokenizer 可获得精确 token 计数。
每一次变更都会被记入日志,因此可以查询过去:
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
也可通过 CLI 使用:remembrane snapshot v1、remembrane diff v1、remembrane log。现在,“我的 Agent 上周二相信什么,又是什么改变了它的想法?”已成为一个可以回答的问题。
没有黑箱——每个结果都携带完整的排序明细:
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'
测试 Agent 的记忆
确定性召回意味着记忆行为可以进行单元测试——这是任何云端记忆 API 都无法提供的能力:
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)
向 recall() 传入 now=... 可以冻结时间,使新近度评分可复现。
记忆文件具有可移植性——可合并两个 Agent 的“大脑”,并吸收近似重复项:
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
不同机器上的性能数据不可直接迁移,因此请先自行测量:
python -m remembrane.bench
两个参考点(混合召回、512 维默认嵌入器、热缓存):
| memories | recall / pack(Linux 沙盒,py3.10,numpy) | recall / pack(独立审计:Windows,py3.12,numpy) | recall(纯 Python,审计机器) |
|---|---|---|---|
| 1,000 | ~2 ms / ~17 ms | ~5 ms / ~32 ms | ~113 ms |
| 10,000 | ~30 ms / ~44 ms | ~51 ms / ~76 ms | ~1.2 s |
| 50,000 | ~205 ms / ~222 ms | ~1.0 s / ~430 ms | ~6.8 s |
核心仍保持零依赖;如可导入 numpy,则会自动使用(pip install remembrane[fast]),且损坏的 numpy 安装会被忽略而不会导致致命错误。若要在超过约 1 万条记忆后实现低于 10ms 的召回,或处理任何超过约 5 万条的规模,则说明你已超出该设计的适用范围——这属于向量数据库的领域,remembrane 不会假装不是如此。
多个连接、线程和进程可以共享同一个记忆文件:基于文件的存储默认使用 SQLite WAL 模式,配置 busy timeout 和立即写事务;缓存会通过 SQLite 的 data_version 检测外部写入,残余的锁竞争会自动重试。测试套件以 3 个连接 × 6 个线程以及 8 个进程同时访问单一文件,未出现错误。存在两项注意事项:WAL 会在数据库旁保留临时的 -wal/-shm 辅助文件(若需严格单文件行为,可传入 journal_mode="DELETE");无论采用何种模式,网络文件系统(NFS/SMB)上的 SQLite 都不安全——请将记忆文件保存在本地磁盘上。
score = 0.65·similarity + 0.15·recency + 0.10·importance + 0.10·usefulness
recency = exp(−ln2 · age / half_life)
usefulness = sigmoid(outcome feedback)
评分采用加权求和(权重归一化为 1),并且对
English / 原文
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, budget-capped context packing (exactly optimal when numpy is present), and deterministic behavior you can unit-test in CI. Adapters for LangChain and CrewAI, plus a built-in MCP server.
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 remembranepulls 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.testingships 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.
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
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
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 ), )
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.
For current LangChain (verified against langchain-core 1.4):
from langchain_core.runnables.history import RunnableWithMessageHistory
from remembrane import MemoryStore
from remembrane.adapters import RemembraneChatMessageHistory
store = MemoryStore("agent.db")
chain = RunnableWithMessageHistory(
runnable,
lambda session_id: RemembraneChatMessageHistory(store, session_id),
)
Needs pip install langchain-core (lazily imported — the rest of remembrane stays dependency-free). For legacy pre-1.x code, RemembraneChatMemory still provides the old save_context / load_memory_variables interface with semantic retrieval — no langchain install required at all.
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?") # also: delete / update / list_records / reset
A storage helper, duck-typed (save/search/delete/update/list_records/get_record/count/reset, kwargs-tolerant). Known limitation: it is not a registered crewai.StorageBackend subclass and returns dicts rather than (MemoryRecord, score) tuples, so plugging it directly into crewai.Memory(...) does not work as of crewai 1.14 — use it directly or behind a thin shim. Native StorageBackend integration is on the roadmap. Note CrewAI itself phones home (telemetry.crewai.com); set CREWAI_DISABLE_TELEMETRY=true if that matters to you — bare remembrane opens no sockets (verified by audit under Python audit hooks).
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_conflicts, memory_resolve, memory_feedback, memory_pack, memory_stats. Stored content is capped at 100k chars per memory (REMEMBRANE_MAX_CONTENT to change).
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
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, negation markers, numeric mismatches, value substitution — honest heuristics, not hidden LLM judgments). Two confidence tiers: likely (strong negation, a numeric/weekday/month mismatch with corroboration, or a same-template value swap like "written in Python" → "written in Rust") and possible (topical tension worth a look). Independent audit on a 30-pair adversarial set measured the likely tier at 0.875 precision / 0.70 recall on v0.4 and 0.889 / 0.80 on v0.5.0 — better, not perfect. v0.5.1 adds the value-substitution signal, which catches the two false negatives that audit named (technology and region swaps); the full set has not been re-measured. Known limitations: rephrased (non-template) contradictions can stay in the possible tier, and "old X / new X" pairs describing two coexisting things can flag as likely (suppressing those would also suppress real old→new contradictions, so we don't). It remains a heuristic: treat conflicts as candidates for the agent to adjudicate , which is the design intent. Filter with conflicts(min_confidence='likely'). 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.
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 with a 0/1 knapsack. The budget is a hard guarantee in every configuration (verified over thousands of randomized trials). Optimality depends on the path: with numpy installed the solution is exact at 1-token granularity; the pure-python fallback uses coarsened weights plus a greedy refill and is documented as near-optimal, not optimal (worst observed loss 16% on adversarial random instances — real memory stores sit nowhere near that). Deterministic, no LLM. Pass token_estimator=your_tokenizer for exact counts.
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.
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.
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
Performance numbers don't travel between machines, so measure your own first:
python -m remembrane.bench
Two reference points (hybrid recall, 512-dim default embedder, warm cache):
| memories | recall / pack (Linux sandbox, py3.10, numpy) | recall / pack (independent audit: Windows, py3.12, numpy) | recall (pure python, audit machine) |
|---|---|---|---|
| 1,000 | ~2 ms / ~17 ms | ~5 ms / ~32 ms | ~113 ms |
| 10,000 | ~30 ms / ~44 ms | ~51 ms / ~76 ms | ~1.2 s |
| 50,000 | ~205 ms / ~222 ms | ~1.0 s / ~430 ms | ~6.8 s |
The core stays dependency-free; if numpy is importable it is used automatically (pip install remembrane[fast]), and a broken numpy install is ignored rather than fatal. For sub-10ms recall beyond ~10k memories, or anything beyond ~50k, you've outgrown the design — that's vector-database territory, and remembrane won't pretend otherwise.
Multiple connections, threads, and processes can share one memory file: file-backed stores default to SQLite WAL mode with a busy timeout and immediate write transactions, caches detect external writes via SQLite's data_version, and residual lock races are retried. Our test suite hammers 3 connections × 6 threads and 8 processes against a single file with zero errors. Two caveats: WAL keeps transient -wal/-shm sidecar files next to the db (pass journal_mode="DELETE" for strict single-file behavior), and SQLite on network filesystems (NFS/SMB) is unsafe regardless of mode — keep memory files on local disk.
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.
-
SQLite over a vector DB — agent memory stores are small (thousands, not billions, of rows). Exact brute-force scoring at that scale is fast enough (see Performance for measured numbers), 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 adapters —
remembranenever imports langchain or crewai; the adapters match their interfaces structurally, so there are no version-pinning fights. -
The CLI writes wherever
--dbpoints, with the invoking user's permissions — it is a local tool, not a sandbox. Wrap it if you expose it to untrusted input. -
OS argv limits apply to
remembrane store "<content>"; use--file pathor--file -(stdin) for large content. -
MCP argument validation follows pydantic's lax coercion (e.g.
useful="yes"coerces toTrue). -
Recall
touchupdates (access stats) are statistics, not events — they are intentionally not journaled, andas_of()reconstructs content/importance state only. -
export()/merge_from()carry memories (content, importance, metadata, access stats, usefulness) but not the source's journal history; embeddings are regenerated by the destination's embedder. -
Journal entries with corrupt payloads are surfaced in
log()(with a_corruptkey) and skipped byas_of()reconstruction. -
A missing or corrupt embedding blob is self-healed on first read (re-embedded from content) and a
RuntimeWarningis emitted naming the affected ids — availability is preserved, and the repair is loud, not silent. -
WAL
-wal/-shmsidecar files can grow during sustained concurrent writes; they shrink back on checkpoint and disappear when all handles close. Reported sizes in our tests are settled-state, not peak. -
A process killed during initial db creation can leave an empty file; reopening it repairs the schema automatically.
git clone https://github.com/satyasairay/remembrane cd remembrane pip install -e .[dev] pytest
MIT