memex について
memexは、AIエージェント向けの永続メモリーサービスで、RESTとMCPのインターフェースを備えています。意味検索と新しさに基づく順位付けを組み合わせ、ONNXでローカルに埋め込みを生成します。
主な機能
- 01メモリーの保存、検索、削除
- 02類似度と新しさによる順位付け
- 03ONNXによるローカル埋め込み
- 04エピソードメモリーのバックグラウンド要約
- 05PrometheusメトリクスとGrafanaダッシュボード
活用シーン
- 01セッションをまたいだエージェントの文脈保持
- 02新しく関連性の高い会話情報の検索
- 03MCPメモリーツールによるエージェント接続
MCP 対応
保存、検索、削除、件数取得のツールを備えたStreamable HTTP MCPサーバーを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm ghcr.io/ayushagrawal288/memex:v0.1.0ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/ayushagrawal288/memex:v0.1.0Streamable HTTPツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
memex
AI エージェント向けの、本番環境対応の永続メモリーサービスです。エージェントはデフォルトではセッション間であらゆる情報を忘れてしまいますが、memex はそれを解決します。セマンティック検索と時間経過に伴う新しさの減衰を用いて会話の記憶を保存、取得、順位付けするため、エージェントは意味的に最も近い情報だけでなく、関連性が高く、かつ新しい情報を提示できます。
POST /v1/memories → store a memory, embed it, persist to Postgres
POST /v1/memories/search → retrieve top-k memories ranked by similarity + recency
DELETE /v1/memories/{id} → forget a specific memory
GET /v1/memories/count → how many memories does this agent/user have
GET /health → liveness + DB connectivity check
GET /metrics → Prometheus metrics
アーキテクチャ
caller (agent / app)
│
▼
FastAPI (async)
│
┌────┴────┐
│ │
embeddings asyncpg pool (min=5, max=20)
(fastembed │
ONNX, ▼
local) PostgreSQL 16
pgvector extension
ivfflat index (cosine)
書き込みパス: コンテンツ → fastembed ONNX 推論(ローカル、CPU で約 12 ms、BAAI/bge-small-en-v1.5)→ 384 次元ベクトルを INSERT → メモリー ID を返します。
読み取りパス: クエリ → 埋め込み → pgvector のコサイン検索(top_k × 3 件の候補)→ Python で新しさの減衰を用いて再順位付け → スコア付きの上位 top_k 件を返します。
設計上の決定
1. セマンティック検索に新しさの減衰を追加
純粋なベクトル類似度では、最も役立つ記憶ではなく、意味的に最も類似した記憶が返されます。90 日前の事実が類似度 0.95 で一致しても、昨日の事実が 0.80 で一致する場合のほうが役立つことはよくあります。
スコアの計算式:
score = α × cosine_similarity + (1 − α) × exp(−λ × age_days)
ここで λ = ln(2) / half_life_days です(デフォルトは 30 日で、30 日前の記憶は新しさの重みが 50%になります)。
α はリクエストごとに設定できます(デフォルトは 0.7)。タスクに特化したエージェントでは α を高くすると、意味的な類似性が優先されます。会話型エージェントでは α を低くすると、新しさがより重視されます。
2. 候補を 3 倍取得し、Python で再順位付け
pgvector クエリは、純粋な類似度でソートされた top_k × 3 件の候補を返します。Python で減衰の計算式を用いて再順位付けし、top_k 件に絞り込みます。これにより、新しさの減衰によって類似度の高い古い記憶が候補から漏れるのを防ぎます。これらは引き続き候補に含まれます。
規模が 10 倍(エージェントあたり 100 万件超の記憶)になった場合は、pg_proc を使った Postgres 関数にスコア計算を移し、Python との往復をなくします。
3. SQLAlchemy async ではなく、asyncpg と明示的なプールサイズ設定を採用
SQLAlchemy はクエリごとに ORM のオーバーヘッドを追加します。埋め込み、クエリ、再順位付けを行う主要な検索パスは、効率的である必要があります。asyncpg ではプールの最小数と最大数を直接制御できます(Java で HikariCP を調整するのと同じ考え方です)。また、pgvector クエリではいずれにしても <=> 演算子に raw SQL が必要です。
プールのデフォルト値:min=5, max=20。単一インスタンスのデプロイに適したサイズです。DB_MAX_POOL_SIZE 環境変数で上書きできます。
4. Redis ではなく Postgres でレート制限
upsert を使ったスライディングウィンドウカウンターです。依存関係を 1 つ減らせます。同時リクエスト下でも正しく動作します(トランザクション処理される upsert)。分散デプロイで規模が 10 倍になった場合は、Redis の INCR + EXPIRE に置き換えます。アトミック操作で、ロック競合もありません。
5. HNSW ではなく ivfflat インデックス
ivfflat は構築コストとメモリー使用量が小さく、小~中規模(100 万ベクトル未満)に適したトレードオフです。lists=100 は約 100 万行まで適切に機能します。規模が 10 倍になった場合は、メモリー使用量と構築時間が増える代わりに再現率が向上する HNSW(m=16, ef_construction=64)に切り替えます。
ローカルで実行
前提条件: Docker と Docker Compose。API キーは不要です。スタック全体をローカルで実行できます。
git clone https://github.com/ayushagrawal288/memex
cd memex
docker compose up
API は http://localhost:8000 で利用できます。インタラクティブなドキュメントは http://localhost:8000/docs にあります。
API リファレンス
メモリーを保存
curl -X POST http://localhost:8000/v1/memories \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"user_id": "user-123",
"content": "User prefers concise responses and dislikes verbose explanations.",
"memory_type": "semantic",
"importance": 1.2
}'
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agent_id": "my-agent",
"user_id": "user-123",
"content": "User prefers concise responses and dislikes verbose explanations.",
"importance": 1.2,
"memory_type": "semantic",
"created_at": "2026-05-26T10:30:00Z",
"score": null
}
メモリーを検索
curl -X POST http://localhost:8000/v1/memories/search \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"user_id": "user-123",
"query": "how does this user like to communicate",
"top_k": 5,
"alpha": 0.7
}'
{
"results": [
{
"id": "3fa85f64-...",
"content": "User prefers concise responses and dislikes verbose explanations.",
"memory_type": "semantic",
"created_at": "2026-05-26T10:30:00Z",
"score": 0.8921
}
],
"query": "how does this user like to communicate",
"total": 1
}
メモリの種類
| 種類 | 用途 |
|---|---|
episodic | 特定の出来事、過去の会話 |
semantic | 事実、好み、一般的な知識 |
procedural | ワークフロー、操作手順 |
負荷テストの結果
MacBook M シリーズ、Docker Desktop、Postgres の単一インスタンスで実行:
locust -f scripts/load_test.py --host=http://localhost:8000 \
--headless -u 50 -r 10 -t 60s
現実的な負荷(ユーザー 50 人、思考時間 100~300 ms — 実際のエージェントのトラフィックをモデル化):
| エンドポイント | RPS | p50(ms) | p95(ms) | p99(ms) | エラー率 |
|---|---|---|---|---|---|
| POST /v1/memories(書き込み) | 27 | 160 | 270 | 330 | 0% |
| POST /v1/memories/search | 83 | 110 | 200 | 250 | 0% |
| 合計 | 113 | 120 | 230 | 300 | 0% |
飽和テスト(ユーザー 500 人、最小限の思考時間 — スループットの上限を特定):
| エンドポイント | RPS(横ばい) | p50(ms) | p99(ms) | エラー率 |
|---|---|---|---|---|
| POST /v1/memories(書き込み) | 28 | 3,900 | 6,100 | 0% |
| POST /v1/memories/search | 91 | 3,600 | 5,800 | 0% |
| 合計 | ~120 | 3,700 | 5,900 | 0% |
MacBook M シリーズ、Docker Desktop(4 CPUs)、uvicorn ワーカー 4 個、ワーカーあたり 16 スレッドで実行。
埋め込み:ローカル ONNX(BAAI/bge-small-en-v1.5)— 外部 API 呼び出しゼロ、コストゼロ。
上限が約 120 RPS である理由:
すべての書き込みと検索で、ONNX 推論が 1 回必要です(CPU で約 10~15 ms)。Docker の CPU が 4 個の場合:理論上の最大値は 4 cores / 12 ms ≈ 333 embeddings/s です。Python のオーバーヘッド、DB クエリ、asyncio のスケジューリングを考慮すると、実際には約 120 RPS です。
スループットを高める方法:
| 方法 | 期待される効果 | 複雑さ |
|---|---|---|
| 埋め込みキャッシュ(Redis、キー = テキストの SHA256) | 2~3 倍(エージェントのクエリが繰り返される場合、ヒット率 40~60%) | 低 |
| 水平スケーリング(ロードバランサー配下に N 個のレプリカを配置) | N 倍の線形増加 | 中 |
| GPU 推論(ONNX ランタイムを CUDA に切り替え) | 10~50 倍 | 中 |
| Voyage-3 API(Anthropic の推論フリートにオフロード) | API クォータの範囲内で数千 RPS までスケール | コード変更は少量 |
プロジェクト構成
memex/
├── app/
│ ├── main.py # REST API — FastAPI, lifespan, router registration
│ ├── mcp_server.py # MCP server — single-worker FastAPI on port 8001
│ ├── core/
│ │ └── config.py # All settings, loaded from env
│ ├── db/
│ │ └── pool.py # asyncpg pool, migrations
│ ├── models/
│ │ └── schemas.py # Pydantic request/response models
│ ├── services/
│ │ ├── embeddings.py # fastembed ONNX inference (local, zero API calls)
│ │ ├── local_summarizer.py # Extractive summariser — Jaccard dedup + TF scoring
│ │ ├── memory.py # Core write/search/scoring logic
│ │ ├── metrics.py # Prometheus metric definitions
│ │ ├── summarizer.py # Background summarisation job
│ │ └── rate_limit.py # Sliding window rate limiter
│ └── api/routes/
│ ├── memories.py # Memory endpoints
│ ├── health.py # Health + readiness
│ └── mcp_tools.py # MCP tool definitions (store, search, delete, count)
├── scripts/
│ └── load_test.py # Locust load test
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
可観測性
docker compose up を実行すると、API とともに Prometheus と Grafana が起動します:
| サービス | URL | 認証情報 |
|---|---|---|
| REST API ドキュメント | http://localhost:8000/docs | — |
| MCP サーバー | http://localhost:8001/mcp/ | — |
| Prometheus | http://localhost:9090 | — |
| Grafana | http://localhost:3000 | admin / admin |
Grafana ダッシュボードは自動的にプロビジョニングされます。パネル:
- HTTP リクエストレート + レイテンシ p50/p99 —
prometheus-fastapi-instrumentatorによる計測 - 埋め込み API レイテンシ p50/p99 — 操作(
embed/embed_batch)ごとの試行単位のヒストグラム - メモリ操作数/秒 — 作成、検索、削除のスループット
- DB プール使用率 — アクティブ接続とアイドル接続(更新間隔:15 s)
- 要約器の稼働状況 — 1 時間あたりに要約されたメモリ数、実行結果
- 埋め込みエラー数/分 — 操作とエラーの種類別
カスタムメトリクスは app/services/metrics.py にあり、標準の FastAPI instrumentator メトリクスとともに /metrics で公開されます。
MCP エンドポイント
memex はMCPサーバーとして動作するため、MCP 対応エージェント(Claude Desktop、Claude Code、カスタムエージェント)は、独自の HTTP 統合を実装せずにメモリを保存・取得できます。
トランスポート: Streamable HTTP(MCP 2024-11-05 仕様)。ポート 8001 でシングルワーカーのプロセスを実行します。セッション状態はプロセス内にあるため、別個のサービスにすることでスティッキーセッションの複雑さを避けつつ、REST API のマルチワーカーのスループットを維持できます。
ツール:
| ツール | 説明 |
|---|---|
store_memory | メモリを埋め込み、永続化します(種類と重要度は設定可能) |
search_memories | 意味的関連性と新しさで順位付けした検索(alpha の設定可能) |
delete_memory | UUID を指定して特定のメモリを忘れます |
count_memories | エージェントとユーザーの組み合わせが持つメモリの数を返します |
Claude Desktop から接続
~/.config/claude/claude_desktop_config.json に追加します:
{
"mcpServers": {
"memex": {
"type": "streamable-http",
"url": "http://localhost:8001/mcp/"
}
}
}
Claude Code から接続
claude mcp add --transport http memex http://localhost:8001/mcp/
設計:サービスを分離する理由
MCP Streamable HTTP トランスポートはセッション状態を保持します。initialize、tools/list、tools/call はすべて同じサーバープロセスに到達する必要があります。REST API は uvicorn ワーカーを 4 つ実行し、ラウンドロビン方式でルーティングします。異なる MCP リクエストが別々のワーカーに振り分けられると、セッション状態が壊れます。
専用の単一ワーカー MCP サービスをポート 8001 で実行すれば、スティッキーセッション用のインフラ(nginx ip_hash、Redis セッションストア)を使わずに済み、REST API は完全なマルチワーカー構成のまま維持できます。
メモリの要約
設定可能な間隔(デフォルト:5 分ごと)で、バックグラウンドの asyncio タスクとして実行されます。エピソード記憶の件数がしきい値を超える (agent_id, user_id) の組を見つけ、最も古いバッチを 1 件の semantic メモリにまとめてから、元のメモリを削除します。完全にローカルで実行され、LLM API の呼び出しはありません。
要約の方法: 純粋な Python による抽出アルゴリズムです。ジャッカード類似度(しきい値 ≥ 0.7)で文を重複排除し、単語頻度(TF)でスコアを付け、上位 N 件を元の順序で返します。要約 1 回あたり約 1 ms で、標準ライブラリ以外の依存関係はありません。
エピソード記憶のみを対象とする理由: エピソード記憶は、時間の経過とともに自然に古くなる会話イベントです。意味記憶と手続き記憶には事実やスキルが記録されるため、気付かないうちにまとめると精度が損なわれるおそれがあります。代わりに、最新性による減衰によって古い記憶を対象外にします。
並行処理の安全性: hashtext(agent_id|user_id) をキーとして pg_try_advisory_xact_lock を使用します。ロックは DB 書き込みトランザクション中のみ保持され、埋め込み処理中は保持されません。
環境変数で調整できます。
| 変数 | デフォルト | 説明 |
|---|---|---|
SUMMARIZATION_ENABLED | true | バックグラウンドジョブの有効・無効を切り替えます |
SUMMARIZATION_THRESHOLD | 100 | 組ごとに処理を開始するエピソード記憶の件数 |
SUMMARIZATION_BATCH_SIZE | 50 | 1 回の実行でまとめる、最も古い記憶の件数 |
SUMMARIZATION_INTERVAL_SECONDS | 300 | ジョブが起動する間隔 |
今後の予定
- メモリの要約 — 件数がしきい値を超えた際に古いエピソード記憶をまとめるバックグラウンドジョブ(ローカルの抽出アルゴリズムを使用し、API 呼び出しはゼロ)
- Prometheus + Grafana — p50/p99 レイテンシのダッシュボード、埋め込み API 呼び出し時間、コネクションプールの飽和状況
- MCP 対応エンドポイント — ポート 8001 で動作する Streamable HTTP サーバー。4 つのツール(保存、検索、削除、件数取得)に対応し、Claude Desktop と Claude Code に接続
- HNSW インデックスのオプション — 100 万超のベクトルを扱うデプロイ向けに、ivfflat から HNSW に切り替えるフラグ
- 重要度を加味した検索 — 類似度と最新性に加えて、
importanceスコアをランキング式に反映
技術スタック
| レイヤー | 選択技術 | 理由 |
|---|---|---|
| API | FastAPI + uvicorn | 非同期を前提とした設計、高速、優れた OpenAPI 生成 |
| 埋め込み | fastembed ONNX(BAAI/bge-small-en-v1.5) | ローカルで動作し、API 呼び出しはゼロ。CPU 推論は約 12 ms、384 次元 |
| データベース | PostgreSQL 16 + pgvector | リレーショナルデータとベクトルを 1 つのシステムで扱え、追加のインフラが不要 |
| ベクトルインデックス | ivfflat | この規模では HNSW より構築コストが低い |
| コネクションプール | asyncpg | 直接制御でき、ORM のオーバーヘッドがゼロ |
| 要約器 | 純粋な Python による抽出 | ジャッカードによる重複排除 + TF スコアリング、ML 依存関係はゼロ、約 1 ms |
| リトライ | tenacity | 一時的なエラーに対し、ジッターを使ったバックオフを実行 |
| メトリクス | Prometheus + prometheus-fastapi-instrumentator | 標準的な可観測性 |
| 負荷テスト | Locust | Python ネイティブで、現実的なユーザーシミュレーションが可能 |
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。