rmbr について
rmbrは、AIエージェントやLLMアプリ向けの組み込み型・ローカル優先のメモリ/検索ライブラリです。メモリと文書インデックスを1つのSQLiteファイルに保存し、アクセス制御、フレームワークアダプター、MCPまたはHTTPサーバーモードを任意で利用できます。
主な機能
- 01名前空間ごとの永続エージェントメモリ
- 02キーワードとセマンティック検索の併用
- 03デフォルト拒否のアクセス制御
- 04フレームワークアダプターとツール呼び出し出力
- 05任意で使えるMCPおよびHTTPサーバー
活用シーン
- 01エージェントのセッションをまたいだ事実の検索
- 02RAG用のローカル文書の抜粋取得
- 03エージェント間のメモリアクセス分離
- 04MCPクライアントからローカルメモリを利用
MCP 対応
任意のMCPサーバーが、rmbrのメモリおよび検索機能をMCPクライアントに公開します。クライアントからローカルサーバーとして起動でき、任意のHTTPサーバーも利用できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
rmbr
エージェントにメモリとナレッジを。ファイル1つ、3行、サーバーもAPIキーも不要です。
rmbr(「remember」から母音を削除した名前)は、AI エージェントと LLM アプリ向けの組み込み型・ローカルファーストのメモリ+検索エンジンです。Postgres に対する SQLite のように、rmbr はホスト型メモリサービスに代わるものを目指しています。
v0.2.7。
pip install rmbrで、すぐに使えるライブラリを導入できます。Memory、Index、Policy、MCP サポート、オプションの HTTP サーバー、PDF/DOCX の取り込み、LangChain/LlamaIndex/LangGraph/mem0 用のフレームワークアダプター(以下にすべて記載)が含まれており、すべて実装・テスト済みです。設計については docs/PLAN.md と docs/ARCHITECTURE.md をご覧ください。
まずは3行から始め、必要に応じて必要な分だけ追加できます。以下の機能を使うのに、それより上の機能は必須ではありません。
Memory— エージェントが保存することを選んだ、永続的で検索可能なメモ。エージェントごとに名前空間が分かれていますIndex— RAG 向けに、独自のドキュメントをキーワード+セマンティック検索で検索しますPolicy— デフォルトで拒否するアクセス制御により、明示的に許可しない限り、あるエージェントが別のエージェントのメモリを読み取れないようにします- フレームワークアダプター — LangChain と LlamaIndex のリトリーバー、本格的な LangGraph
BaseStore、mem0 API 互換のドロップイン実装、OpenAI/Anthropic の生のツール呼び出し用エクスポート - MCP サーバー — あらゆる MCP クライアント(Claude Desktop、Claude Code、Cursor など)向け。オプション
- HTTP サーバー — サーバーレス関数や、接続を開いたままにするより
curlを使いたい用途向け。こちらもオプション
最初の3つだけを使うとしても、それは rmbr の「基本的な」使い方ではありません。多くの人にとって、それこそが rmbr です。サーバーモードは、それぞれが解決する特定の用途のために存在しており、いずれ使うことを想定しているからではありません。
目次: 理由 · クイックスタート · マルチエージェントの分離 · MCP サポート · HTTP サポート · 代替案 · パフォーマンス · ロードマップ
理由
エージェントはすでに再起動後も情報を「記憶」できます。たとえば、CLAUDE.md、システムプロンプト、ディスク上の JSON ファイルなどです。これは新しいことではなく、rmbr もその逆を主張しているわけではありません。
問題は、そのファイルが大きくなったときに起こります。静的なコンテキストファイルにある事実はすべて、現在のタスクに関係があるかどうかにかかわらず、毎回の呼び出しでトークンを消費します。そのため、ファイルは小さいまま(人が厳選した数十件のメモ程度)にとどまるか、誰も低コストでは読めないノイズに変わってしまいます。順位付けはありません。エージェントはファイル全体を受け取るか、何も受け取らないかのどちらかで、このターンに実際に重要な5件だけを受け取ることはありません。エージェントが学習するほど、静的ファイルはコストが高くなり、有用性が下がります。一方、検索可能なメモリーは有用性が高まり、呼び出しごとのコストは変わりません。mem.recall(query) は、蓄積した数千件ものメモリーの中から、最も関連性の高い k 件を返します。これこそが、「ファイルに書き込めるエージェント」と「メモリーを持つエージェント」の実質的な違いです。
もうひとつ、独自に実装しようとして困ることがあります。テキストを分割し、埋め込みを生成して、ベクトルストアに入れる。これは週末に取り組むには本当に手軽なプロジェクトです(このプロジェクトもそこから始まりました)。ただし、その週末プロジェクトで間違えやすいのは、本格的なハイブリッド検索(多くの実装はベクトル検索のみ、またはキーワード検索のみで、それに気づきません)、埋め込みキャッシュ(同じテキストを毎回再度埋め込み、料金を再度支払わずに済むようにするもの)、そして複数のエージェントが関わる場合は、それらの間の安全な分離です。多くの自作またはフレームワーク提供のマルチエージェントメモリーでは、すべてのエージェントが読み書きできるひとつのデータを共有するか、呼び出し元のモデル自身が指定する namespace/user_id パラメーターでアクセス範囲を設定します。プロンプトインジェクションは、その値を変更するよう簡単に指示できます。rmbr の MCP ツールはそのパラメーターを公開しないため、注入された指示を設定するフィールド自体がありません。
つまり、rmbr は「システムプロンプトに詰め込む」(数 KB を超えるとスケールしない)と「本格的なインフラストラクチャを構築する」(Docker、グラフデータベース、ホスト型 API キーが必要)の間にあるギャップを埋めます。検索品質が高く、安全に分離されたメモリーを、サービスではなく依存関係として提供します。
具体的には、rmbr は次のものを提供します。
- ファイルひとつ。 エージェントのメモリーとナレッジベース全体が、ひとつの
.dbファイルに収まります。git commitして、差分を確認し、ロールバックし、チームメイトに渡し、バグ報告に添付し、または決定論的な CI 用のテストフィクスチャに既知の正常な状態を保存できます。これらができるホスト型メモリーサービスはありません。 - 3行。
pip install rmbrして、インポートし、記憶させるだけです。アカウントも設定もサービスも必要ありません。 - インフラストラクチャの追加なし。 エージェントはすでに LLM 呼び出しにネットワーク接続と API キーを必要とします。rmbr はメモリーのためだけに2つ目のものを追加しません。mem0 はホスト型の LLM+埋め込み API をデフォルトで使用し、Zep には Docker+Neo4j+LLM キーが必要で、Letta にはサーバー+Postgres が必要です。どれも、モデル自体にすでに支払っているものに加えて必要になります。rmbr 自体のメモリー/検索経路はデフォルトではネットワーク呼び出しを一切行いません。ベンダーがひとつ減り、漏えいするキーがひとつ減り、障害によってエージェントのメモリーまで停止するサービスがひとつ減ります。(そのため、Ollama や llama.cpp などの完全ローカルな LLM と組み合わせても rmbr は動作し、真のオフライン環境やエアギャップ環境でも使えます。ほとんどの人には不要でしょうが、その選択肢もあります。)
- 独自形式なし。 rmbr は LLM を自ら呼び出すことはありません。
recall()/search()はプレーンな文字列、浮動小数点数、辞書(hit.text、hit.score、hit.metadata)を返します。解析するものはなく、結果を利用するためのベンダー SDK も不要です。同じ方法で Claude、GPT、Gemini に接続する方法については、以下のLLM で結果を使用するをご覧ください。 - 名前空間に固定されたマルチエージェントアクセス。
Policyはデフォルトで拒否します。MCP ツールは上書き用の名前空間パラメーターを公開しません。慣例ではなく、設計によって安全性を確保します。
クイックスタート
from rmbr import Memory
mem = Memory("agents.db", namespace="assistant")
mem.remember("user prefers dark mode and short answers")
mem.recall("user preferences")
わずか3行で、一般的なケースに必要なAPIはすべてそろいます。以下はすべてオプトインで、それぞれ独立したセクションに分かれているため、必要な内容だけを読めます。設計上、ライブラリ専用であり、覚える必要のあるCLIはありません。(python -m rmbrがあるのは、MCP クライアントがサーバーを起動できるようにするためだけです。詳しくは以下のMCP サポートをご覧ください。)
最初にagents.dbを用意する必要はありません。 rmbr initも、ダウンロードするテンプレートも、事前に準備するものもありません。まだ存在しないパスに対してMemory(path, ...)(およびIndex(path))を呼び出すと、適切なスキーマが設定されたファイルがその場で作成されます。必要なのは、パスがあるディレクトリが存在することだけです(ファイルを書き込み用に開く場合と同じです)。Memory("agents.db", ...)は実行場所を問わず使えますが、Memory("some/deep/agents.db", ...)を使うには、some/があらかじめ存在している必要があります。
ドキュメントのインデックス作成(RAG)
from rmbr import Index
idx = Index("agents.db")
idx.add_files("docs/") # .py, .md, and plain text each get an appropriate splitter automatically
hits = idx.search("how do I deploy?", k=5)
hits[0].text, hits[0].score, hits.timings # per-stage latency, always visible
IndexとMemoryは同じ.dbファイルを共有するため、エージェントにナレッジベースとメモリの両方が必要な場合は、同じパスを指定して両方を開いてください。add_files()/add_texts()は、ドキュメントIDの通常のリストであるIngestResultを返し、.timingsに内訳(chunk_ms/embed_ms/store_ms/ann_ms/docs_per_second)が付属します。検索時にhits.timingsで確認できる透明性を取り込みにも適用しているため、こちらの説明をうのみにせず、埋め込みがコストの大半を占めていることを自分で確認できます。
LLM で結果を使う
rmbrはモデルを呼び出しません。search()/recall()はプレーンテキストとスコアを返すので、それをどう使うかは自分で決められます。Claudeを使った標準的なパターン(従来型のRAG:検索して、取得したテキストをプロンプトに挿入する方法)は次のとおりです。
from anthropic import Anthropic
from rmbr import Index
idx = Index("agents.db")
idx.add_files("docs/")
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def answer(question: str) -> str:
hits = idx.search(question, k=5)
context = "\n\n".join(f"<document>{hit.text}</document>" for hit in hits)
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
# Context before the question, not after — Anthropic's own prompting
# docs measure this ordering as meaningfully better for long-context
# RAG, though it isn't required for correctness.
messages=[{"role": "user", "content": f"{context}\n\nUsing the documents above, answer: {question}"}],
)
return response.content[0].text
answer("how do I deploy?")
これはClaude専用ではありません。hit.textはラッパーもプロバイダーオブジェクトもrmbr独自の要素もない、通常のPython文字列です。上記とまったく同じcontext文字列を、OpenAIのmessages配列(client.chat.completions.create(model=..., messages=[...]))やGeminiのcontentsにそのまま渡せます。主要なチャット補完APIはどれも基本的に同じ形式(ロール情報付きテキストメッセージのリスト)を使います。そのため、rmbrが提供する唯一の統合契約である「テキストを検索し、プロンプトに入れる」は、どのプロバイダーでも同じように機能します。SDKの呼び出しを入れ替えるだけで、ほかは何も変わりません。
埋め込みに、ローカルのデフォルトではなくホスト型プロバイダーを使いたい場合は、Memory("agents.db", namespace="assistant", embedder=OpenAIEmbedder())を使います(pip install rmbr[openai])。VoyageEmbedder/pip install rmbr[voyage]とCohereEmbedder/pip install rmbr[cohere]も利用でき、3つともまったく同じEmbedderプロトコルに準拠し、APIのほかの部分も同じです。
時間の経過に伴うメモリの正確性の維持
remember()で無期限に挿入し続けても、しばらくは問題ありませんが、やがてそうはいかなくなります。よく似たメモが積み重なり、期限切れになるものもありません。mem0の抽出ループのように「同じ事実かどうか」を判断するLLMはrmbrにはありません。以下では、代わりにベクトル類似度と時間に基づく決定論的な手法を使っています。誤検出は重複よりも深刻な問題になるため、オプトインになっています。
# Update-in-place instead of appending, above a cosine-similarity threshold.
# Off by default — no LLM here to judge intent, so keep it conservative (0.92-0.95).
mem = Memory("agents.db", namespace="assistant", dedupe_threshold=0.93)
mem.remember("user prefers dark mode") # inserts
mem.remember("user really prefers dark mode") # updates the same row if similarity clears the bar
# Bound growth automatically (evicts the oldest beyond the cap on every remember()),
# or prune on your own schedule:
mem = Memory("agents.db", namespace="assistant", max_memories=5000)
mem.forget_older_than(60 * 60 * 24 * 30) # delete anything older than 30 days
# max_memories eviction is pure recency by default, which is a real risk once
# you rely on it - a trivial fact from an hour ago would otherwise outlive a
# critical one from last week for no reason but timestamp. Exempt specific
# memories from it:
mem.remember("the customer's account was permanently deactivated", pinned=True)
すでに大きくなった名前空間に多数の項目(組織内のドキュメント一式や、過去のメモリのバックフィルなど)を読み込む場合は、会話の途中でremember()を1回呼び出すのとは状況が異なるため、バッチ処理してください。
# Without this, every single remember()/add_text() call re-serializes the
# *entire* vector index (usearch has no incremental on-disk save) - fine
# at hundreds-to-low-thousands per namespace, real cost once a namespace
# has tens of thousands of items and you're adding many more sequentially.
with mem.bulk():
for fact in many_facts:
mem.remember(fact)
# SQL rows are still durable immediately inside the block; only the vector
# index's persistence is deferred to one write when the block exits - a
# crash mid-block loses whatever hadn't been flushed yet, a real tradeoff
# you're opting into, not a silent one. See Performance below for numbers.
Indexにも同じ.bulk()があります。
SQLを手書きせずに、名前空間のメモリを確認します。
mem.stats() # {"assistant": {"count": 412, "oldest": "...", "newest": "..."}}
mem.stats(namespaces="*") # same, broken down per namespace this policy can read
mem.integrity_check() # [] if healthy; otherwise, what's wrong and which ids
Indexにも同じ2つのメソッドがあり、代わりにdocuments/chunksの件数を報告します。
検索精度の調整項目
search()/recall()はデフォルトで通常のハイブリッドランキングを使いますが、デフォルトよりも関連性の品質を重視する場合は、次の3つを利用できます。
# Richer where= filtering: equality by default, $eq/$ne/$gt/$gte/$lt/$lte/$in/$nin as operators.
idx.search("deploy", where={"updated_at": {"$gt": "2026-01-01"}})
# A real confidence gate — filters on the raw cosine similarity (hit.vector_score),
# not hit.score itself, which is an RRF rank-sum with no fixed scale to threshold on.
idx.search("deploy", min_similarity=0.6)
# Recency-weighted ranking: a fresher memory/chunk can outrank an equally
# relevant older one. recency_weight=0.0 (off) by default. A chunk's "created"
# time is its document's ingestion time (add_text()/add_files()).
mem.recall("user preferences", recency_weight=0.05, recency_half_life_seconds=7 * 86400)
idx.search("deploy", recency_weight=0.05)
# A local cross-encoder re-scores the candidate pool for higher precision at
# extra latency — same fastembed dependency already installed, no new network
# call, no API key. hit.score becomes the cross-encoder's score when this is on.
idx.search("deploy", rerank=True)
会話メモリ
実際のエージェントで最も一般的なのは、会話のターンをまたいで記憶を保持するチャットループです。remember_turn()は、まさにその用途のためにremember()を薄く包む便利なメソッドです。role/session_idは保存されるテキストに埋め込まれるのではなく、メタデータに格納されます。そのため、意味検索に"user: "という接頭辞が混入せず、どちらの値を使ってもフィルター処理や再生ができます。
mem.remember_turn("user", "I prefer dark mode")
mem.remember_turn("assistant", "Got it, dark mode from now on", session_id="conv-42")
mem.recall("dark mode", where={"role": "user"}) # who said it
mem.list(where={"session_id": "conv-42"}) # replay one conversation, in order (list(), not recall() — no query needed)
既存のエージェントループやフレームワークへの接続
MCP を経由せずに、エージェントの実行に使っている環境へ rmbr を接続する方法は 3 つあります。
# Raw OpenAI/Anthropic tool-calling — one line to get a ready-made tool
# definition plus a callable, in either API's shape:
tool = idx.as_tool()
response = client.messages.create(..., tools=[tool.to_anthropic()])
result = tool.call(**tool_use_block.input) # dispatches to idx.search()
recall_tool, remember_tool = mem.as_tools() # or as_tools(read_only=True) for recall only
# LangChain — wraps Index as a real BaseRetriever, drops into any chain
# (pip install langchain-core, or whatever LangChain distribution you're on):
retriever = idx.as_langchain_retriever(k=5)
retriever.invoke("how do I deploy?") # -> list[Document]
# LlamaIndex — same idea (pip install llama-index-core):
retriever = idx.as_llamaindex_retriever(k=5)
retriever.retrieve("how do I deploy?") # -> list[NodeWithScore]
# LangGraph — a real BaseStore, drops into StateGraph(...).compile(store=...)
# (pip install langgraph-checkpoint):
from rmbr.integrations.langgraph import as_store
store = as_store("agents.db")
store.put(("memories", "user-42"), "pref-1", {"text": "user prefers dark mode"})
store.search(("memories", "user-42"), query="dark mode") # -> list[SearchItem]
どちらのリトリーバーアダプターも、同じ search() のキーワード引数(where=、min_similarity=、rerank= など)を受け付け、非同期版(Index.asearch() を基盤とする retriever.ainvoke(...)/retriever.aretrieve(...))も備えています。langchain-core も llama-index-core も rmbr の必須依存関係ではありません。各アダプターは、as_langchain_retriever()/as_llamaindex_retriever() を実際に呼び出したときにのみ、対象のフレームワークを遅延インポートします。
as_store() は LangGraph の名前空間タプルを 1 つの rmbr 名前空間(. で結合)に対応付け、LangGraph のキーを metadata["_lg_key"] に対応付けます。正確な対応付けと、意図的にサポートしていない機能(アイテムごとの TTL、フィールドパスを指定した選択的インデックス作成)については、rmbr/integrations/langgraph.py のモジュール docstring を参照してください。langgraph-checkpoint も rmbr の必須依存関係ではありません。
mem0 からの移行
rmbr.integrations.mem0_compat.Memory は、mem0 OSS のローカル Memory クラスと各呼び出しで互換性があります(add()/search()/get_all()/get()/update()/delete()/delete_all()、引数名も同じで、戻り値の形式も {"results": [...]}/{"message": "..."} と同じです)。そのため、既存の mem0 統合のほとんどは、インポートとコンストラクター呼び出しを変更するだけで移行できます。
from rmbr.integrations.mem0_compat import Memory # was: from mem0 import Memory
m = Memory("agents.db") # was: Memory() (rmbr writes to a file you name)
m.add("user prefers dark mode", user_id="alex", infer=False)
m.search("dark mode", filters={"user_id": "alex"})
これは mem0 のラッパーではなく、mem0ai への依存関係はありません(オプション依存関係としてもありません)。意図的にサポートしない動作が 1 つあります。黙って差異を生じさせるのではなく、明確にエラーにします。mem0 の実際のデフォルトである infer=True では、LLM がメッセージを読み取り、何を保持するかを判断します。rmbr は LLM を呼び出さないため、add(..., infer=True) を指定するか、infer を省略した場合(mem0 自身のデフォルト)、何が実行されないのかを明示する NotImplementedError を発生させます。より高度な処理を行うと示す引数を渡されたにもかかわらず、元のテキストを黙って保存することはありません。メッセージをそのまま保存するには infer=False を渡してください。一致する機能、変換される機能(filters={"key": {"gt": 10}} → rmbr の where=)、サポートされない機能(mem0 の AND/OR/NOT フィルター結合子、history()、画像メッセージ)の一覧は、モジュール docstring を参照してください。
as_tool()/as_tools() がエクスポートするスキーマでは、query/k だけでなく、呼び出しごとにモデルから where/min_similarity/rerank を渡すこともできます(すべて省略可能なので、これらを知らないモデルでも従来どおり動作します)。
tool.call(query="how do I deploy?", where={"tier": "public"}, min_similarity=0.6, rerank=True)
組み込みの各スキーマでは additionalProperties: false を設定し、ディスパッチ前に tool.call() が引数を検証します。モデルが存在しない引数を幻覚すると(小型・高速モデルでは、期待以上によく起こります)、プロセスを停止させる素の Python TypeError ではなく、実際の問題を示す ToolCallError が返されるため、ツール結果のエラーとしてそのまま安全に渡せます。対応しているプロバイダーでは、to_anthropic(strict=True)/to_openai(strict=True) を使うと、ディスパッチ前にプロバイダー側で不正な呼び出しを拒否するよう求められます。これは call() 自体の検証を置き換えるものではなく、補完するものです。すべてのプロバイダーが、ドキュメントに記載されたとおりの厳密さで strict を適用するわけではないためです。
エージェント間のアクセス制限
from rmbr import Memory, Policy
policy = Policy()
policy.allow("supervisor", read="*") # supervisor can read every namespace
mem = Memory("agents.db", namespace="coder", policy=policy)
デフォルトではアクセスを拒否します。明示的に許可されない限り、coder は自身の名前空間のみを読み書きできます。全体のモデル、セキュリティ上の考え方、実際のチーム構成図については、以下のマルチエージェントの分離(率直な説明)を参照してください。
Web バックエンドや並行動作するエージェント向けの非同期処理
すべての読み取りと書き込みには、a を先頭に付けた非同期版があります。Memory の aremember/arecall/aforget、Index の aadd_text/aadd_texts/aadd_files/asearch です。これらは、ブロッキング呼び出しによって同じイベントループ上のほかのすべてのリクエストが停止してしまう async def のルートハンドラー(FastAPI、Starlette、aiohttp)で使えます。
from fastapi import FastAPI
from rmbr import Memory
app = FastAPI()
mem = Memory("agents.db", namespace="assistant")
@app.post("/chat")
async def chat(message: str):
context = await mem.arecall(message, k=5)
await mem.aremember(f"user said: {message}")
return {"context": [hit.text for hit in context]}
または、スーパーバイザーが複数の許可された名前空間に対して、1つずつではなく同時に処理を振り分けることもできます。
import asyncio
coder_notes, researcher_notes = await asyncio.gather(
supervisor.arecall("release blockers", namespaces="coder"),
supervisor.arecall("release blockers", namespaces="researcher"),
)
正直に伝えておきたい制限が1つあります。同じ Memory/Index インスタンスに対する非同期呼び出しは、読み取りも含め、内部ロックによって直列化されます。これは意図的な仕様です。ベクトルインデックス(usearch)は、複数スレッドからの同時変更が安全だと文書化されておらず、インデックスが破損するより、読み取りの並行性を多少あきらめるほうがはるかにましだからです。真の並列処理が必要な場合は、同じファイルに対して個別のインスタンスを開いてください。SQLite の WAL モードなら問題なく対応できます。
MCP 経由でメモリを提供する
from rmbr import serve_mcp
serve_mcp("agents.db", namespace="coder", read_only=True)
公開される内容と、クライアントを実際に接続する方法については、以下のMCP サポートを参照してください。
HTTP 経由でメモリを提供する(オプション)
from rmbr import serve_http
serve_http("agents.db", namespace="coder", read_only=True, token="a-shared-secret")
MCP クライアントになれず、rmbr も import できない呼び出し元向けです。たとえば、サーバーレス関数、別のマシン上のプロセスなど、接続を開いたままにするより URL に curl を実行したい場合に使えます。rmbr を使うのに、これは必要ありません。既存の Memory/Index にアクセスするための別の入口であり、それらの上に追加で必要となるものではありません。エンドポイントの一覧、認証の仕組み、新しい依存関係が一切不要な理由については、以下のHTTP サポートを参照してください。
コントリビュート/ソースからの実行
git clone https://github.com/SRock44/rmbr.git
cd rmbr
python -m venv .venv && source .venv/bin/activate # .venv\Scripts\activate on Windows
pip install --only-binary :all: -e .
pytest tests/ # 284 tests, no network or API key required
デフォルトのエンベッダー(fastembed、ローカルの ONNX モデル)は、初回使用時にモデルの重みをダウンロードします。一方、tests/ 内のすべてのテストでは、決定論的で依存関係のないエンベッダー rmbr.embed.FakeEmbedder を使用するため、テストスイートは完全にオフラインで実行できます。同じ FakeEmbedder を Memory(..., embedder=FakeEmbedder())/Index(..., embedder=FakeEmbedder()) で渡せば、独自のテストでも使用できます。
マルチエージェントの分離について、正直に説明します
- 名前空間によってエージェントのメモリは分離され、すべての呼び出しでその分離が適用されます。ただし、これは組織上の分離であり、暗号学的な分離ではありません。ファイルにアクセスできるコードであれば、そのファイルを開けます。これはあらゆる組み込みデータベースに当てはまることであり、私たちはその事実を明言しています。
- 強固な分離を実現するには、信頼境界ごとに個別の
.dbファイルを用意し、さらに OS のファイル権限を設定します。 - **MCP での提供では、名前空間が固定されます。**公開されるツールには名前空間のパラメーターがないため、外部エージェントが構造上、自分の担当範囲外を問い合わせることはできません。これは、私たちが調べたほかのすべての MCP メモリサーバーとは異なります。それらでは、スコープが呼び出し元のモデルが指定するパラメーターになっており、変更するよう説得される可能性があります。
具体的なチーム構成の例です。広い権限を持つスーパーバイザー1人、互いの情報を見られない専門家2人、単一の担当範囲に固定された外部 MCP クライアント1つが、同じ agents.db ファイルを使用します。
flowchart TB
subgraph db["agents.db — one SQLite file"]
direction LR
supNS[("supervisor<br/>namespace")]
coderNS[("coder<br/>namespace")]
researchNS[("researcher<br/>namespace")]
end
supervisor["Supervisor agent<br/>policy.allow('supervisor', read='*')"] ==>|read + write| supNS
supervisor -.->|read, explicitly granted| coderNS
supervisor -.->|read, explicitly granted| researchNS
coder["Coder agent<br/>Memory(path, namespace='coder')"] ==>|read + write| coderNS
researcher["Researcher agent<br/>Memory(path, namespace='researcher')"] ==>|read + write| researchNS
external["External MCP client<br/>(Claude Code, Cursor, ...)"] -->|"serve_mcp(path, namespace='coder')"| coderNS
この図でコーダーとリサーチャーの名前空間の間に経路がないのは、記載漏れではなく、それが意図したとおりだからです。このアクセスを拒否するための設定は不要で、明示されているのはスーパーバイザーの許可(read="*")だけです。外部 MCP クライアントのツールスキーマには namespace 引数が一切ないため、要約対象のドキュメントから試すよう指示されたとしても、構造上 coder 以外のものを要求することはできません。
このパターンを実行可能なエンドツーエンドのデモで確認するには、examples/multi_agent_support/を参照してください。1つの .db ファイルを共有する、Claude を利用した3つのエージェント(分離された専門家2人とスーパーバイザー1人)の例です。API に直接アクセスして分離をテストした際に、実際に PermissionError が発生するところも確認できます。
MCP のサポート
MCP(Model Context Protocol)は、Claude Desktop、Claude Code、Cursor など、増え続ける AI アプリケーションを、独自のプラグイン形式をそれぞれのアプリが考案するのではなく、単一の標準インターフェースを通じて外部ツールやデータソースに接続するための、オープンでモデルに依存しないプロトコルです。rmbr は MCP に対応しているため、MCP 対応クライアントであれば、自分でサーバーを作成しなくても、.db ファイルを直接検索して記憶できます。
serve_mcp() が公開するもの
from rmbr import serve_mcp
serve_mcp("agents.db", namespace="coder") # read + write
serve_mcp("agents.db", namespace="coder", read_only=True) # read only
3 つのツールがあり、すべて起動時に渡した名前空間に固定されます(クライアントが上書きするための名前空間パラメーターがない理由については、上記のマルチエージェントの分離をご覧ください)。
search(query, k=5)—Index経由で追加されたドキュメントを対象とするハイブリッド検索recall(query, k=5)—Memory経由で保存されたメモリの検索remember(text, pinned=False)— 新しいメモリを保存します。pinned=Trueにすると、max_memoriesによる削除の対象外になります。read_only=Trueの場合、権限エラーになるだけでなく、ツール一覧にも表示されません。
各結果には、text/metadata とともに bm25_score/vector_score(score の元となる生のシグナル)も含まれます。呼び出し元のエージェントが、すべての検索結果を同等に信頼するのではなく、信頼度に基づいて結果に重み付けしたり、フィルタリングしたりする場合に便利です。min_similarity、recency_weight、rerank(上記の検索精度の調整項目を参照)は、まだ MCP ツールのパラメーターとして公開されていません。ツールスキーマは意図的に最小限に保たれています。サーバー側でこれらを使う必要がある場合は、serve_mcp() の呼び出し元でカスタムの Index/Memory を設定してください。
また、MCP リソーステンプレート rmbr://examples/{pattern}(有効な pattern の値を一覧表示する rmbr://examples も含む)も公開されており、一般的な使用パターンの、短く実行可能なコードスニペットを提供します。パターンは basic-memory、document-search、multi-agent-policy、conversation-memory、tool-calling、memory-hygiene です。ツールの呼び出しだけでなくリソースの参照にも対応する MCP クライアントであれば、セッションを離れたり GitHub にアクセスしたりすることなく、これらを直接取得できます。
クライアントの接続
serve_mcp() は stdio 上で処理をブロックします。自分の長時間稼働するアプリ内から呼び出すのではなく、MCP クライアントがサブプロセスとして起動することを想定しています。python -m rmbr はそのための起動用コマンドです(パッケージは同じコマンドを実行する rmbr コンソールスクリプトもインストールするため、ローカルにインストールしなくても uvx rmbr を使えます)。
python -m rmbr agents.db --namespace coder --read-only
# or, via the installed console script / uvx:
rmbr agents.db --namespace coder --read-only
Claude Desktop または Claude Code で使う場合は、MCP 設定(Claude Desktop の claude_desktop_config.json、またはプロジェクトの .mcp.json)に追加します。
{
"mcpServers": {
"rmbr-coder": {
"command": "uvx",
"args": ["rmbr", "/absolute/path/to/agents.db", "--namespace", "coder", "--read-only"]
}
}
}
クライアントを再起動すると、その単一の名前空間を対象とした search/recall(読み取り専用でなければ remember も)がツール一覧に表示されます。この接続からは、ファイル内のその他の部分、つまり他のエージェントのメモリにはアクセスできません。それを可能にするパラメーターはありません。
HTTP のサポート
このセクションはすべて任意です。 ここまでの内容にある Memory、Index、Policy、MCP は、HTTP サーバーを一切使わずに動作します。実際、ほとんどの rmbr ユーザーはそのように使っています。ライブラリをインポートして、いくつかのメソッドを呼び出せば完了です。serve_http() が存在しても、それが変わるわけではありません。rmbr を使うための、より「本格的な」方法ではなく、上記の方法では対応できない特定の状況に向けた、別の入り口です。
その状況とは、別のプロセスや別のマシン上にいる呼び出し元、または MCP クライアントのように接続を開いたままにできない呼び出し元です。MCP では、クライアントが stdio 経由で所有するサブプロセスとして serve_mcp() を起動する必要があります。リクエストごとに起動するサーバーレス関数では、それができません。また、rmbr の .db ファイルが呼び出し元のプロセスとは異なる場所(別のコンテナや、まったく別のマシン)にある場合も、import rmbr は選択肢になりません。それでも常に使えるのが HTTP リクエストです。それが serve_http() の存在する唯一の理由です。それ以上のものではありません。
作成しようとしているものがどちらにも当てはまらないなら、ここで読むのをやめてもかまいません。rmbr は、サーバーの実行を勧めているわけではありません。
起動する
from rmbr import serve_http
serve_http("agents.db", namespace="coder", read_only=True, token="a-shared-secret")
停止するまでブロックします。serve_mcp()と同様、これはプロセス全体の役割として使うもので、ほかの作業も行うアプリの内部から呼び出すものではありません。デフォルトでは127.0.0.1にバインドします。host="0.0.0.0"を指定するのは、このマシンの外部からアクセスできるようにすると実際に決めた場合に限ってください。
新たな依存関係はゼロです。 Starletteとuvicornは、rmbrがこの機能のために追加したものではありません。mcp(rmbrのHTTPトランスポートで必要なため、すでに必須の依存関係です)が、どちらもすでに取り込んでいます。serve_http()を有効にしても、依存関係ツリーにパッケージがひとつも増えることはありません。
公開されるもの
serve_mcp()と同じ原則で、名前空間が固定されます。このAPIのリクエストボディやクエリー文字列にはnamespaceフィールドが存在しないため、呼び出し元が構造上、このサーバーの起動時に指定されたひとつの名前空間の外にアクセスすることはできません。なぜこれが重要なのかについては、上記のマルチエージェントの分離(正確に言うと)を参照してください。
| メソッド | パス | 呼び出し先 |
|---|---|---|
GET | /health | — ステータスとバージョン。認証が不要な唯一のルートです |
POST | /memories | Memory.remember() |
GET | /memories | Memory.list()(?limit=と?where=<json>に対応) |
GET | /memories/{id} | Memory.get() — 見つからない場合は404 |
PATCH | /memories/{id} | Memory.update() |
DELETE | /memories/{id} | Memory.forget() |
POST | /memories/search | Memory.recall() |
GET | /memories/stats | Memory.stats() |
POST | /documents | Index.add_text() |
DELETE | /documents/{id} | Index.delete() |
GET | /documents/stats | Index.stats() |
POST | /search | Index.search() |
add_files()がこの一覧にないのは意図的です。これはこのプロセスのローカルファイルシステムから読み込むため、HTTPリクエストの送信先にいる呼び出し元には意味がありません。代わりに、テキストそのものをPOST /documentsに送信してください。read_only=Trueでサーバーを起動した場合、すべての書き込みルートは405を返します。これは、serve_mcp()でread_onlyを指定するとrememberツール自体が非表示になるのと同じ動作です。
curlだけで通信できます。
curl -X POST http://127.0.0.1:8000/memories \
-H "Authorization: Bearer a-shared-secret" \
-H "Content-Type: application/json" \
-d '{"text": "user prefers dark mode"}'
# {"id": 1}
curl -X POST http://127.0.0.1:8000/memories/search \
-H "Authorization: Bearer a-shared-secret" \
-H "Content-Type: application/json" \
-d '{"query": "dark mode"}'
# {"results": [{"id": 1, "text": "user prefers dark mode", "score": ..., ...}], "timings": {...}}
認証はオプトインであり、自動ではありません
token=を渡す(または環境変数RMBR_TOKENを設定する)と、/healthを除くすべてのルートでAuthorization: Bearer <token>が必要になります。どちらも設定しなければ、認証は一切ありません。このデフォルトは、rmbrのほかの機能と同じ方針です。ネットワーク境界は利用者が管理するものであり、rmbrが代わりに決めることはありません。ただし、デフォルトをそのまま受け入れるのではなく、意識的に判断することをおすすめします。127.0.0.1以外にバインドする場合は、トークンを設定してください。
より大きな仕組みに組み込む
serve_http()は、プレーンなStarletteアプリケーションを返すbuild_app()を呼び出す、薄いブロッキング型の便利なラッパーです。rmbr独自の仕組みは何もありません。
from rmbr.server import build_app
app = build_app("agents.db", namespace="coder")
# it's just an ASGI app from here: mount it inside a larger Starlette/FastAPI
# app, wrap it in your own middleware (CORS isn't included - add
# starlette.middleware.cors.CORSMiddleware yourself if you need it), or hand
# it to a different ASGI server entirely instead of calling serve_http().
設計に関する詳細な説明(名前空間を固定する理由、認証ミドルウェアの動作、意図的にサポートしていないもの)は、rmbr/server.pyのモジュール docstring に記載されています。
代替ツール
「競合」ではなく、実際に異なる用途向けの、まったく異なるツールです。それぞれが実際にどのような場面に適しているのか、rmbrが適さないケースも含めて紹介します。
メモリサービスを検討している場合(mem0、Zep/Graphiti、Letta):いずれも、LLMを介したメモリインテリジェンス(会話から事実を抽出し、矛盾を解消し、重複を統合すること)に優れています。rmbrは意図的にそのような処理を一切行いません。LLMを呼び出すことは絶対にありません。これは誇張ではなく、実際に機能面での不足です。しかしそのおかげで、rmbrではAPIキーが不要で、remember()を呼び出すたびに追加のLLMコストや遅延が発生せず、統合モデルが実際に発言した内容を密かに書き換えるリスクもありません。基本機能(remember/recall/forget、名前空間ポリシー)を提供するので、その上に何を載せるかは自由に決められます。
| mem0 | Zep / Graphiti | Letta | rmbr | |
|---|---|---|---|---|
| デプロイ | SDKですが、デフォルトでホスト型LLMと埋め込みAPIを呼び出します | Docker + Neo4j/FalkorDB + LLM API | サーバー(Docker)+ Postgres | 組み込み型 — 1つのファイルで、自分のプロセス内で動作 |
| 初期状態でAPIキーが必要 | はい(OpenAI) | はい(グラフ抽出用LLM) | はい(LLM) | いいえ |
| 記憶する価値があるものを判断 | LLM(事実抽出) | LLM(グラフのエッジ、矛盾の解消) | LLM(自己編集型メモリブロック) | ユーザーが判断 — 書き込み処理にLLMを使わず、決定論的に動作 |
| 状態をポータブルなファイルとして保存 | いいえ | いいえ | いいえ | はい |
(本稿執筆時点のGitHubスター数。規模の目安:mem0 約62k、Graphiti 約29k、Letta 約24k。これはrmbrが属するカテゴリよりも、はるかに大きく、変化の速いカテゴリです。検討する前に知っておく価値があります。)
ベクトルデータベースを検討している場合(Chroma、LanceDB、pgvector、Pineconeなど):これらは「組み込み型でAPIキー不要」という点で、実際に同じ土俵にある製品です。特にChromaとLanceDBは、rmbrと同じくサーバーなしで動作します。違いは、ベクトルインデックスの上に何が構築されているかです。生のベクトルデータベースを使う場合、メモリAPI、名前空間/アクセス制御レイヤー、BM25とベクトルのハイブリッド融合、埋め込みキャッシュ、MCPサーバーを自分で構築する必要があります。rmbrには、エージェントメモリという用途に特化して、これらすべてがあらかじめ組み込まれています。
正当に優位性を発揮するのは、大規模な生データ一括取り込みのスループットです。専用の検索製品向けに数百万件のドキュメントをインデックス化するなら、用途に特化したベクトルデータベースを使ってください。それがベクトルデータベースの役割であり、rmbrの役割ではありません。rmbrは、エージェント自身のメモリやナレッジベースの実際の規模(自身の履歴や、数百から数千件程度のチャンクで構成されるナレッジベース)を想定して調整されています。この規模では、一括読み込みの速度ではなく、各ターンで実際に影響する単一呼び出しのレイテンシが重要です。どちらについても、実測値は下のパフォーマンスを参照してください。
パフォーマンス
このREADMEには、bench/内のスクリプトで生成されていないパフォーマンス数値を掲載することはありません。公開されたハードウェア上で、手法も含めて誰でも再現できます。
rmbrの実際の使用パターン(コーパスを一括読み込みするのではなく、推論ループの途中でエージェントがremember()/search()を1件ずつ呼び出す場合)で重要なのは、バルクスループットではなく、実際のデフォルト埋め込みモデルを使った単一呼び出しのレイテンシです。以下は、プロジェクトで指定されたUbuntuベンチマークマシン(Intel Core Ultra 9 285K、taskset -c 0-3で分離した4コア、Ubuntu 24.04.4 LTS、Python 3.12.3)で測定した、各実行100サンプル、3回実行した中央値です。
| operation | p50 | p95 | p99 |
|---|---|---|---|
mem.remember(text) | 3.0 ms | 5.7 ms | 6.7 ms |
500件のドキュメントを含むインデックスに対するidx.search(query, k=5) | 2.9 ms | 3.6 ms | 3.7 ms |
| — うち、クエリの埋め込み処理のみ | 2.5 ms | 2.7 ms | 3.1 ms |
idx.search(query, k=5, rerank=True) | 12.2 ms | 99.6 ms | 130.2 ms |
idx.search(query, k=5, recency_weight=0.3) | 2.9 ms | 3.6 ms | 3.8 ms |
| 3 行目をよく見てください。単純な検索呼び出しのコストの約 85〜90%は、rmbr ではなく埋め込みモデルによるものです。 rmbr 自体のストレージ/取得のオーバーヘッドは 1 ミリ秒未満です。また、実際のエージェントのループでは、その後に続く LLM 呼び出しと比べると、これらはすべて体感できないほど小さなものです。これは、RAG のレイテンシが実際にはどこで発生するのかという rmbr の創業時の理念でもあります(docs/PLAN.md を参照)。 |
最後の 2 行は、単純な検索呼び出しに対して v0.2 の rerank=True と recency_weight が実際に追加するコストです。rerank=True は実測された実コストです。候補プールに対するローカルのクロスエンコーダー処理であり、無料の並べ替えではなく、実際に追加の推論を行うためです。インデックスで初めて rerank=True を呼び出す際に再ランカーモデルが遅延読み込みされ(キャッシュが空の場合はダウンロードも行われ)、インポート時には読み込まれないため、p95/p99 は p50 よりも明らかに高くなります。ミリ秒単位の短縮より結果の品質を重視する場合に使い、デフォルトで毎回使うのは避けてください。recency_weight は、追加のモデル呼び出しがなく、取得済みのチャンクに対する純粋な Python の指数減衰計算だけなので、実質的に無料です(単純な検索と同程度のレイテンシで、誤差の範囲内です)。再現するには、python bench/latency.py --n-calls 100 --n-queries 100 --corpus-size 500 を実行してください。3 回分すべての生の出力は bench/pinned/ にあります。
一括取り込みのスループットについて(透明性を期すための開示であり、前面に押し出す性能上の主張ではありません): rmbr は add_texts()/add_files() のすべての書き込みを、文書ごとではなく、バッチ全体で 1 回の SQLite トランザクション、1 回の埋め込み処理呼び出し、1 回の ANN インデックス挿入にまとめます。5,000 件の文書からなる合成コーパスで、実測値は約 2,966 文書/秒(ハイブリッド方式、デフォルト、3 つのシードの中央値)です。あまり変化しなかった点にも注目してください。この特定のベンチマークでは、埋め込み処理の呼び出しをまとめても効果はほとんどありませんでした。各エンジンに同じ事前計算済みベクトルを渡しており(ほぼ無料の辞書検索です)、ストレージ/ANN の性能を切り分けて測定しているためです。実際の埋め込み処理(ONNX 推論や API 呼び出し)では、呼び出しごとに実際の固定オーバーヘッドが発生するため、バッチ化によってそのコストを実際に償却できます。したがって、実環境での埋め込みコストをより代表するのは、上記の bench/latency.py の数値です。
専用のベクトルデータベース 2 つと比べると、純粋な一括ロードでは rmbr は依然として低速です。これは、rmbr が構築されている用途とは根本的に異なる処理です。Chroma は約 2.6 倍高速(中央値で約 7,775 文書/秒)、LanceDB は約 35〜80 倍高速(約 104,000〜236,000 文書/秒。実行ごとのばらつきは大きめ)で取り込みます。行ごとのリレーショナルな処理が一切ない Arrow バッチ書き込みが 1 回で済むためです。一方、mem0 と比べると結果は逆転します。mem0 は生のベクトルストアではなく実際のメモリ抽象化であるため、より近い比較対象です。rmbr は約 7.4 倍高速(中央値で約 2,966 対約 401 文書/秒)で取り込みます。これは、mem0 では挿入のたびに SQLite の履歴/監査ログ書き込みと、密ベクトルに加えた BM25 疎ベクトルのエンコードが行われるという、実際の行ごとのコストを反映しています。このベンチマークでは、mem0 の実際のデフォルト設定に合わせて、これらを有効にしています。rmbr がデフォルトでどちらも行わない理由については、上記の mem0 からの移行を参照してください。rmbr が 3 つすべてに対して健闘しているのは、recall@5 です(0.949)。mem0 のハイブリッド検索(0.998)と LanceDB の完全検索(1.000)にはわずかに及びませんが、Chroma のベクトルのみの検索(0.797)は明確に上回っています。mem0 を含む 3 つのシードすべての全数値は bench/pinned/ にあり、pip install -e ".[bench]" && python bench/run.py で再現できます。これは隠すのではなく、開示しています。大規模な文書の一括ロードが実際のワークロードであれば、上記の代替案を参照してください。rmbr はその用途に最適化されていません。
スケール:名前空間に数万件のアイテムが格納されるとどうなるか
ベクトルインデックスの usearch には、ディスク上への増分保存機能がありません。そのため、remember()/add_text() を呼び出すたびに、ベクトルインデックス全体を再シリアライズして書き直します。rmbr の通常の規模(名前空間あたり数百件から数千件程度)では、無視できるコストです。名前空間が数万件に増えると、逐次書き込みのたびに、それまでのすべてを再シリアライズするコストが発生します。これは実際に測定された問題で、現在は Memory.bulk()/Index.bulk() で解決されています(使い方については、上記のメモリの正確さを長期にわたって保つを参照してください)。すでにデータが格納された名前空間に remember() を50回逐次呼び出した場合のコストを、.bulk() の有無で比較します。
| 名前空間のサイズ | .bulk() なし(合計/書き込みあたり) | .bulk() あり(合計/書き込みあたり) | 高速化率 |
|---|---|---|---|
| 1,000 | 196ms / 3.93ms | 40ms / 0.81ms | 4.9x |
| 5,000 | 1,392ms / 27.83ms | 87ms / 1.74ms | 16.0x |
| 10,000 | 2,802ms / 56.04ms | 123ms / 2.46ms | 22.8x |
| 20,000 | 5,555ms / 111.10ms | 195ms / 3.89ms | 28.6x |
| 40,000 | 12,135ms / 242.70ms | 341ms / 6.82ms | 35.6x |
.bulk() を使わない場合、書き込みあたりのコストは名前空間のサイズに比例して増加します。これは、呼び出しのたびに O(n) の再シリアライズが発生していることを示しています。一方、.bulk() を使うと、コストのかかる再シリアライズは書き込みごとではなくバッチごとに1回だけ行われるため、サイズが40倍になっても書き込みあたりのコストはほとんど増えません(0.81ms → 6.82ms)。また、高速化率は維持されるだけでなく、スケールとともに向上します。Index.add_text() でも同様の傾向が見られます(40,000件で最大33.5x)。.bulk() はオプトイン方式で、デフォルトの動作には影響しません。明示的に遅延させない限り、各呼び出しは引き続き即座に永続化されます。再現するには、python bench/scale.py --sizes 1000 5000 10000 20000 40000 --n-writes 50 を実行してください。生の出力は bench/pinned/ にあります。
実際のプロトコル往復:Python API だけでなく、MCP と HTTP
上記の数値は、プロセス内の Python API を測定したものです。MCP または HTTP 経由で呼び出す際の実際の体感には、実際のサブプロセス/ソケットのオーバーヘッドが加わります。実際の python -m rmbr サブプロセスを、実際の mcp クライアント SDK から実際の標準入出力経由で呼び出して測定しました。また、実際の OS ソケット上で動作する実際の uvicorn サーバーに、実際の httpx クライアントからリクエストを送って測定しました(テストスイートが速度のために使うプロセス内の簡易経路ではありません)。デフォルトの埋め込みモデルを使用し、500件のコーパスで、各呼び出しを50回サンプリングしています。
| 平均 | p50 | p95 | p99 | |
|---|---|---|---|---|
MCP remember ツール呼び出し | 4.58ms | 4.38ms | 5.72ms | 5.82ms |
MCP recall ツール呼び出し | 3.51ms | 3.48ms | 3.69ms | 3.78ms |
MCP search ツール呼び出し | 0.52ms | 0.50ms | 0.54ms | 0.61ms |
HTTP POST /memories | 4.29ms | 3.91ms | 5.42ms | 5.76ms |
HTTP POST /memories/search | 3.02ms | 2.99ms | 3.29ms | 3.53ms |
HTTP GET /memories/{id} | 0.27ms | 0.26ms | 0.30ms | 0.36ms |
上記の生の Python API の数値に加わるプロトコルのオーバーヘッドは小さく、数ミリ秒程度です。支配的なコストではありません。本当に時間のかかる唯一の処理は session.initialize()(MCP サブプロセスの起動とハンドシェイクの完了)で、約741msかかります。これは呼び出しごとではなく、セッションごとに1回だけ発生します。再現するには、python bench/mcp_latency.py/python bench/http_latency.py を実行してください。生の出力は bench/pinned/ にあります。
bge-small-en-v1.5が引き続きデフォルトである理由
検証を行いました。bench/quality.pyは、手作業で作成した150件の(クエリ、正解の文章、紛らわしい文章)の例(rmbrがインデックス化するコンテンツの実際の形式である、記憶された設定、ドキュメント、コードをそれぞれ50件ずつ)を使って、recall@1を測定します。fastembedがサポートする、同じサイズクラスのすべてのローカル埋め込みモデルに加え、「サイズを3倍にすると何が得られるか」を見るための比較対象としてbge-base-en-v1.5も評価しました。
| モデル | サイズ | recall@1(全体) |
|---|---|---|
| bge-small-en-v1.5(デフォルト) | 67MB | 0.760 |
| snowflake-arctic-embed-xs/s | 90-130MB | 0.647-0.673 |
| all-MiniLM-L6-v2 / jina-v2-small | 90-120MB | 0.767 |
| bge-base-en-v1.5(サイズ3倍) | 210MB | 0.833 |
bge-smallと同じサイズクラスで、明確な差をもって上回るモデルはありません。上記の代替モデルは、いずれも約1ポイント以内の差に収まっており、このサンプル数では誤差の範囲です。実質的な差で上回るのはbge-base-en-v1.5だけです。recall@1は7.3ポイント向上しますが、その分、ダウンロードサイズは3倍(210MB)、埋め込みあたりのレイテンシは約3.9倍(p50で7.5ms対1.9ms)になります(どちらも絶対値で見れば依然として小さい値です)。このトレードオフを検証したうえで、より小さく高速なモデルをデフォルトとして採用しました。品質向上を求め、サイズが気にならない場合は、1行の変更で切り替えられます。
from rmbr.embed import FastEmbedEmbedder
mem = Memory("agents.db", namespace="assistant", embedder=FastEmbedEmbedder(model_name="BAAI/bge-base-en-v1.5"))
全データと各候補のカテゴリ別内訳:python bench/quality.py --models candidates
ロードマップ
- v0.1 —
Memory+Policy+Index(ハイブリッド BM25 + ベクトル検索、メタデータフィルタリング)、埋め込み + セマンティッククエリのキャッシュ、MCP サポート(名前空間固定)、3 OS 対応 CI(Linux/Windows/macOS)、ステージごとの所要時間を計測する真のバッチ取り込み、非同期 API サーフェス(aで始まるメソッド)、Python 対応チャンク分割器(標準ライブラリのastを使用し、依存関係の追加なし)、ホスト型埋め込みプロバイダー 1 つ(OpenAI)、デフォルトの埋め込みモデルをローカルの代替候補と比較検証した 150 件の例による品質評価、実際の単一呼び出しと一括処理のベンチマーク数値、PyPI の信頼された公開、uvxで起動可能なコンソールスクリプト、公式 MCP レジストリへの掲載 - v0.2 — 類似度ベースのメモリ重複排除/更新(
dedupe_threshold)、上限付き保持(max_memories、forget_older_than)、Memory.recall()とIndex.search()の両方で直近性を重み付けしたランキング、より柔軟なwhere=フィルタリング(等価比較だけでなく$gt/$gte/$lt/$lte/$in/$nin/$neに対応し、Memory.list()でも使用可能)、生のコサイン類似度に対する実際の信頼度ゲート(min_similarity、さらにすべての結果でhit.bm25_score/hit.vector_scoreを利用可能)、オプションのローカルクロスエンコーダー再ランカー(rerank=True)、会話メモリの簡易機能(remember_turn())、独自のエージェントループ向けツール呼び出しエクスポート(OpenAI 形式と Anthropic 形式に対応したas_tool()/as_tools()。query/kだけでなく、where/min_similarity/rerankのすべての設定を公開)、LangChain/LlamaIndex のリトリーバーアダプター(as_langchain_retriever()/as_llamaindex_retriever()。どちらもオプションで、遅延インポート)、ホスト型埋め込みプロバイダーを 2 つ追加(VoyageEmbedder、CohereEmbedder。OpenAIEmbedderと同じEmbedderプロトコルに対応)、自動検出されるチャンク分割器を 2 つ追加(split_json、split_rst。どちらも標準ライブラリのみを使用) - v0.2.1 — 導入しやすさ/開発者体験の改善:
py.typedマーカー(mypy/pyright が rmbr の型ヒントを信頼するようになりました)、README バッジ(PyPI/CI/ライセンス/Python バージョン)、既存のremember()/search()表にrerank=True/recency_weightの遅延時間を固定条件で追加、bench/latency.pyの修正(各シナリオを独立したサブプロセスで実行するようにしました。1 つのプロセス内で実行すると、互いのテール遅延の数値に影響していました)、実行可能なマルチエージェントのサポート例(examples/multi_agent_support/)。小型で高速ながら不安定なモデルを使った例のストレステストで明らかになった、実環境でのツール呼び出しの失敗パターンへの対策:ToolSpec.call()がツール独自のスキーマに照らして引数を検証し、モデルが引数を幻覚した場合に単なるTypeErrorではなく明確なToolCallErrorを発生させるようになりました。すべての組み込みツールスキーマでadditionalProperties: falseを設定しました。to_anthropic()/to_openai()にstrict=Trueオプションを追加しました。.dbファイルの健全性を、SQL を手書きせずに調べられるstats()とintegrity_check()をMemory/Indexに追加しました。また、remember(..., pinned=True)によって、max_memoriesの通常の直近性だけに基づく削除から特定のメモリを除外できるようになりました - v0.2.2 — Glama.ai MCP ディレクトリへの掲載(固定コミットに対してデプロイし、ライブ環境で検証済み)、MCP リソーステンプレート(
rmbr://examples/{pattern}と、インデックスとしてのrmbr://examples)を追加。リソースを参照できるあらゆる MCP クライアントに、一般的な使用パターンの短い実行可能なスニペットを提供します。また、Glama デプロイのスモークテスト中にライブ環境で判明した、serve_mcp()がserverInfoに空のversion文字列を報告する問題を修正 - v0.2.3 — すべての MCP ツール引数(
search/recall/rememberのquery/k/text/pinned)に、パラメーターごとの JSON Schemadescriptionフィールドを追加。Glama.ai 独自の品質スコアリングで実際に検出された問題を修正しました。ツール呼び出しモデルが見るのは JSON スキーマであり、docstring ではありませんが、これらのパラメーターには説明がありませんでした - v0.2.4 — フレームワークアダプターを 2 つ追加(
as_store()による実際の LangGraphBaseStore。langgraph-checkpointの実際の op コントラクトに照らして検証済み。mem0 API 互換のMemoryドロップイン実装を一から再実装し、mem0aiへの依存はありません)、オプションの HTTP サーバー(serve_http/build_app。Starlette + uvicorn を使用し、mcpがすでに両方を依存関係として取り込むため新たな依存関係は不要。MCP と同様に名前空間固定で、認証はオプトイン)、ID によるレコードの直接アクセス用にMemory.get()/Memory.update()を追加、ベンチマーク比較対象に mem0 を追加し、プロジェクトのベンチマーク用マシンで固定条件の数値を再計測、実際の CI/CD 強化:ruff の lint ゲート、実際のサブプロセス/ソケット統合テスト(プロセス内の簡易テストではなく、実際のpython -m rmbrMCP サブプロセスと実際の uvicorn ソケット)、Dependabot、CodeQL、SECURITY.md - v0.2.5 —
Memory.bulk()/Index.bulk()を追加し、実際に発生していた呼び出しごとに O(n) のコストがかかる問題を修正:usearchにはオンディスクへの増分保存機能がないため、従来はremember()/add_text()のたびにベクトルインデックス全体を再シリアライズしていました。.bulk()ではバッチごとに 1 回だけ書き込むように遅延できます(オプトインで、デフォルトの動作は変更なし)。プロジェクトのベンチマーク用マシンで測定したところ、40,000 件の名前空間への逐次書き込みが最大 35.6 倍高速になり、規模が大きくなるほど高速化の幅も広がります。Index.add_files()に PDF/DOCX の取り込みを追加(rmbr[pdf]/rmbr[docx]。オプションで遅延インポートし、追加機能が不足している場合は黙ってスキップせず、明確なImportErrorを発生させます)。実際の MCP サブプロセスと HTTP ソケットの往復遅延を計測するベンチマークスクリプトを 3 つ追加(bench/scale.py、bench/mcp_latency.py、bench/http_latency.py)。Glama.ai MCP の品質に関する問題をさらに修正:3 つすべてのツールに、実際のToolAnnotations(read_only_hint/destructive_hint/idempotent_hint/open_world_hint)を初めて設定しました。また、JSON スキーマだけでは伝えられない情報を明示するようツールの説明を改訂し、searchとrecallの使い分け、kが上限超過時にエラーを出さず黙って値を制限する動作、max_memoriesによる削除への影響とpinnedの永続性を明記しました。さらに、ドキュメントを更新:バージョンの説明とロードマップが 2 リリース分古くなっていたこと、agents.dbが自動作成されることが一度も明記されていなかったこと、古いテスト数を修正しました。 - v0.2.6 — デフォルトの
Memory(embedder=None)/Index(embedder=None)が、呼び出しのたびに新しいFastEmbedEmbedder(新規のfastembed.TextEmbedding/onnxruntimeInferenceSession)を構築し、インスタンス間で共有していなかった問題を修正しました。共有.dbファイルに対し、名前空間ごとに 1 つのMemory/Indexを開くアプリ(Policy.allow(read=[...])がサポートするパターン)では、プロセス内に onnxruntime セッションが重複して蓄積し、確実にプロセスがクラッシュする可能性がありました(ネイティブヒープの破損。他のネイティブライブラリが同じプロセスを共有している場合はさらに悪化します)。make_embedder()は、ロックで保護されたモジュールレベルのキャッシュを使い、モデル名ごとに 1 つのFastEmbedEmbedderを共有するようになりました。そのため、呼び出し側が共有埋め込みモデルを明示的に渡さなくても、デフォルトの経路を安全に使用できます。#18 で報告され、原因を特定しました。 - v0.2.7 —
FastEmbedEmbedderの共有に関する別のクラッシュを修正。今度はAnnIndex自体に起因する問題です。usearch(>=2.9。2.26.0 まで確認済み)は、インデックスのベクトル数がゼロに戻った後も、remove()後の tombstone ノードを HNSW グラフに残します。その状態をシリアライズして新しいプロセスで再読み込みすると(以前にremember()+forget()、add_text()+delete()、または重複排除による更新を行った後、2 つ目のMemory/Indexが同じ.dbファイル/コレクションを開いた時点で発生します)、再読み込み後の次のadd()でセグメンテーション違反が発生します。これは埋め込みモデルの共有自体とは無関係ですが、#18 と同じ「名前空間ごとに 1 つのMemory」というパターンで表面化しました。AnnIndexは、直前のシリアライズ以降にremove()が行われていれば、シリアライズの前に残存ベクトルから再構築するようになりました。そのため、再読み込みしたインデックスが tombstone を新しいプロセスに持ち込むことはありません。#20 で報告され、原因を特定しました。 - 既知の未対応項目 — 持ち越しはなく、新たな項目もまだありません
- 次の予定 — プラグイン可能な統合フック(
mem.consolidate(extractor)):rmbr が LLM を直接呼び出すことは今後もありませんが、呼び出し側が指定する extractor callable により、rmbr が独自のモデル選択を使った mem0 形式の事実抽出/重複排除/更新をオーケストレーションできるようになります。rmbr が API キーを保持する必要はありません。意図的にまだ実装していません。汎用メモリインポートツール(他システムの JSON/YAML/MD エクスポートを解析する機能)も検討しましたが、明示的に延期しました。rmbr のremember()は、その用途にすでに使える汎用プリミティブであり、SQLite が他のデータベース用のインポートツールを同梱していないのと同様です。再検討するのは、実際の需要がある特定の形式が明確になった場合だけであり、「エージェント全般」を対象にはしません。
License
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。