
memex
io.github.ayushagrawal288由 GitHub 帳號 ayushagrawal288 發布v0.1.0更新於 Oct 24, 2026
Persistent memory for AI agents — semantic + recency search, ONNX embeddings, Docker Compose.
概覽
自架的 AI 代理持久記憶服務,提供儲存、搜尋、刪除與計數記憶的工具,依語意相似度與時間新近度排序。
- 可以做什麼
- 儲存記憶,可指定類型與重要性,於本機產生向量並寫入資料庫
- 依語意相似度加上時間衰減搜尋記憶,alpha 可調整
- 依 UUID 刪除指定記憶,並統計某個代理/使用者配對的記憶數量
- 以本機整套服務執行,附帶 REST API、Prometheus 指標與 Grafana 儀表板
- 適用情境
- 適合希望代理跨工作階段記住事實、偏好或過往事件,且要求記憶留在本機而非託管服務的情境。也適合對話型代理,需要近期內容比單純語意相近的舊內容更優先。
- 執行需求
- 需要 Docker 與 Docker Compose 在本機執行整套服務;MCP 端點是 8001 埠上的 streamable HTTP 服務,由用戶端連線。不需 API 金鑰,向量由本機 ONNX 模型產生,Postgres 與 pgvector 由 compose 檔啟動。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 memex,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
memex
[GitHub release] [Python] [FastAPI] [MCP] [License: MIT] [ayushagrawal288/memex MCP server]
A production-grade persistent memory service for AI agents. Agents forget everything between sessions by default — memex fixes that. It stores, retrieves, and ranks conversation memory using semantic search with recency decay, so agents surface what's relevant and recent, not just what's semantically closest.
Architecture
Write path: content → fastembed ONNX inference (local, ~12 ms CPU, BAAI/bge-small-en-v1.5) → INSERT with 384-dim vector → return memory ID.
Read path: query → embed → pgvector cosine search (top_k × 3 candidates) → re-rank with recency decay in Python → return top_k results with scores.
Design decisions
1. Recency decay on top of semantic search
Pure vector similarity returns the most semantically similar memories, not the most useful ones. A fact from 90 days ago that's a 0.95 similarity match is often less useful than a 0.80 match from yesterday.
Score formula:
Where λ = ln(2) / half_life_days (default: 30 days, so a 30-day-old memory has 50% recency weight).
α is configurable per request (default 0.7). Task-focused agents use higher α (semantic dominates). Conversational agents use lower α (recency matters more).
2. Fetch 3× candidates, re-rank in Python
The pgvector query returns top_k × 3 candidates sorted by pure similarity. Python re-ranks with the decay formula and slices to top_k. This prevents recency decay from starving high-similarity older memories — they're still in the candidate pool.
At 10× scale (>1M memories per agent): push the scoring into a Postgres function using pg_proc to eliminate the Python re-ranking round-trip.
3. asyncpg + explicit pool sizing over SQLAlchemy async
SQLAlchemy adds ORM overhead on every query. The hot retrieval path — embed, query, re-rank — needs to be tight. asyncpg gives direct control over pool min/max (same instinct as tuning HikariCP in Java). pgvector queries require raw SQL for the <=> operator anyway.
Pool defaults: min=5, max=20. Right-size for a single-instance deployment. Override via DB_MAX_POOL_SIZE env var.
4. Rate limiting in Postgres, not Redis
Sliding window counter via upsert. One fewer dependency. Correct under concurrent requests (transactional upsert). At 10× scale with distributed deployments: replace with Redis INCR + EXPIRE — atomic operations, no lock contention.
5. ivfflat index, not HNSW
ivfflat has lower build cost and lower memory footprint — the right tradeoff at small-to-medium scale (<1M vectors). lists=100 works well up to ~1M rows. At 10× scale: switch to HNSW (m=16, ef_construction=64) for better recall at the cost of higher memory and build time.
Running locally
Prerequisites: Docker and Docker Compose. No API keys required — the entire stack runs locally.
The API is live at http://localhost:8000. Interactive docs at http://localhost:8000/docs.
API reference
Store a memory
Search memories
Memory types
Load test results
Run on a MacBook M-series, Docker Desktop, single Postgres instance:
Realistic load (50 users, 100–300 ms think time — models actual agent traffic):
Saturation test (500 users, minimal think time — finds the throughput ceiling):
Run on MacBook M-series, Docker Desktop (4 CPUs), 4 uvicorn workers, 16 threads/worker.
Embeddings: local ONNX (BAAI/bge-small-en-v1.5) — zero external API calls, zero cost.
Why the ceiling is ~120 RPS:
Every write and every search requires one ONNX inference (~10–15 ms on CPU). With 4 Docker CPUs: 4 cores / 12 ms ≈ 333 embeddings/s theoretical max. After Python overhead, DB queries, and asyncio scheduling: ~120 RPS actual.
Path to higher throughput:
Project structure
Observability
docker compose up starts Prometheus and Grafana alongside the API:
The Grafana dashboard is provisioned automatically. Panels:
- HTTP request rate + latency p50/p99 — from
prometheus-fastapi-instrumentator - Embedding API latency p50/p99 — per-attempt histogram by operation (
embed/embed_batch) - Memory operations/s — create, search, delete throughput
- DB pool utilisation — active vs idle connections (update interval: 15 s)
- Summariser activity — memories condensed per hour, run outcomes
- Embedding errors/min — by operation and error type
Custom metrics are in app/services/metrics.py and exposed on /metrics alongside the standard FastAPI instrumentator metrics.
MCP endpoint
memex exposes itself as an MCP server so any MCP-aware agent (Claude Desktop, Claude Code, custom agents) can store and retrieve memories without custom HTTP integration.
Transport: Streamable HTTP (MCP 2024-11-05 spec). Single-worker process on port 8001 — session state is in-process, so a separate service avoids sticky-session complexity while keeping the REST API's multi-worker throughput.
Tools:
Connect from Claude Desktop
Add to ~/.config/claude/claude_desktop_config.json:
Connect from Claude Code
Design: why a separate service
The MCP Streamable HTTP transport is session-stateful — initialize, tools/list, and tools/call must all reach the same server process. The REST API runs 4 uvicorn workers with round-robin routing; routing different MCP requests to different workers breaks session state.
Running a dedicated single-worker MCP service on port 8001 avoids sticky-session infrastructure (nginx ip_hash, Redis session store) while keeping the REST API fully multi-worker.
Memory summarisation
Runs as a background asyncio task on a configurable interval (default: every 5 minutes). Finds any (agent_id, user_id) pair where episodic memory count exceeds a threshold, condenses the oldest batch into a single semantic memory, then deletes the originals. Fully local — no LLM API calls.
How it summarises: Pure Python extractive algorithm. Sentences are deduplicated by Jaccard similarity (≥ 0.7 threshold), scored by word frequency (TF), and the top-N are returned in original order. ~1 ms per summarisation, zero dependencies beyond the standard library.
Why episodic-only: Episodic memories are conversation events with natural time-based obsolescence. Semantic and procedural memories encode facts and skills — silently condensing them risks precision loss; they age out via recency decay instead.
Concurrency safety: Uses pg_try_advisory_xact_lock keyed on hashtext(agent_id|user_id). The lock is held only during the DB write transaction, not during the embedding call.
Tune via env vars:
What's next
- Memory summarisation — background job to condense old episodic memories (local extractive algorithm, zero API calls) when count exceeds threshold
- Prometheus + Grafana — p50/p99 latency dashboards, embedding API call duration, pool saturation
- MCP-compatible endpoint — Streamable HTTP server on port 8001; 4 tools (store, search, delete, count); connects to Claude Desktop and Claude Code
- HNSW index option — flag to switch from ivfflat to HNSW for deployments with >1M vectors
- Importance-weighted retrieval — factor
importancescore into ranking formula alongside similarity and recency
Tech stack
來源:README.md,提交 3e80a83
工具
0版本歷史
1- v0.1.0最新Sep 16, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分