memex

io.github.ayushagrawal288由 GitHub 账号 ayushagrawal288 发布v0.1.0更新于 Oct 24, 2026

Persistent memory for AI agents — semantic + recency search, ONNX embeddings, Docker Compose.

已验证STDIO仅桌面数据库知识与记忆

概览

AI 生成的概览

自托管的 AI 智能体持久记忆服务,提供存储、搜索、删除和统计记忆的工具,按语义相似度与时间新近度排序。

可以做什么
  • 存储记忆,可指定类型与重要性,本地生成向量并写入数据库
  • 按语义相似度加时间衰减搜索记忆,alpha 可调
  • 按 UUID 删除指定记忆,并统计某个智能体/用户对的记忆数量
  • 以本地整套服务运行,附带 REST API、Prometheus 指标与 Grafana 面板
适用场景
适合希望智能体跨会话记住事实、偏好或过往事件,且要求记忆留在本机而非托管服务的场景。也适合对话型智能体,需要近期内容比单纯语义相近的旧内容更靠前。
运行要求
需要 Docker 与 Docker Compose 在本机运行整套服务;MCP 端点是 8001 端口上的 streamable HTTP 服务,由客户端连接。无需 API 密钥,向量由本地 ONNX 模型生成,Postgres 与 pgvector 由 compose 文件启动。
安装前请注意
记忆保存在本地 Postgres 数据库中,对话内容会持久化到磁盘,应按敏感数据处理。删除工具与后台摘要任务会移除数据:当情景记忆超过阈值时,旧记忆会被压缩合并并删除原始记录。自带的 Grafana 使用默认管理员密码,应尽快修改。

安装

在 SourceWeft 中

  1. 打开 控制台中的 memex,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

memex

[GitHub release] [Python] [FastAPI] [MCP] [License: MIT] [ayushagrawal288/memex MCP server]

[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.

POST /v1/memories          → store a memory, embed it, persist to PostgresPOST /v1/memories/search   → retrieve top-k memories ranked by similarity + recencyDELETE /v1/memories/{id}   → forget a specific memoryGET  /v1/memories/count    → how many memories does this agent/user haveGET  /health               → liveness + DB connectivity checkGET  /metrics              → Prometheus metrics

Architecture

caller (agent / app)        │        ▼  FastAPI (async)        │   ┌────┴────┐   │         │embeddings  asyncpg pool (min=5, max=20)(fastembed  │ ONNX,      ▼ local)  PostgreSQL 16           pgvector extension           ivfflat index (cosine)

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:

score = α × cosine_similarity + (1 − α) × exp(−λ × age_days)

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.

bash
git clone https://github.com/ayushagrawal288/memexcd memexdocker compose up

The API is live at http://localhost:8000. Interactive docs at http://localhost:8000/docs.


API reference

Store a memory

bash
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  }'
json
{  "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}

Search memories

bash
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  }'
json
{  "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}

Memory types

TypeUse for
episodicSpecific events, past conversations
semanticFacts, preferences, general knowledge
proceduralWorkflows, how-to instructions

Load test results

Run on a MacBook M-series, Docker Desktop, single Postgres instance:

bash
locust -f scripts/load_test.py --host=http://localhost:8000 \       --headless -u 50 -r 10 -t 60s

Realistic load (50 users, 100–300 ms think time — models actual agent traffic):

EndpointRPSp50 (ms)p95 (ms)p99 (ms)Error rate
POST /v1/memories (write)271602703300%
POST /v1/memories/search831102002500%
Aggregated1131202303000%

Saturation test (500 users, minimal think time — finds the throughput ceiling):

EndpointRPS (plateau)p50 (ms)p99 (ms)Error rate
POST /v1/memories (write)283,9006,1000%
POST /v1/memories/search913,6005,8000%
Aggregated~1203,7005,9000%

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:

ApproachExpected gainComplexity
Embedding cache (Redis, key = SHA256 of text)2–3× (40–60% hit rate on repeated agent queries)Low
Horizontal scaling (N replicas behind a load balancer)N× linearMedium
GPU inference (swap ONNX runtime → CUDA)10–50×Medium
Voyage-3 API (offload to Anthropic's inference fleet)Scales to thousands of RPS, limited by API quotaLow code change

Project structure

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

Observability

docker compose up starts Prometheus and Grafana alongside the API:

ServiceURLCredentials
REST API docshttp://localhost:8000/docs—
MCP serverhttp://localhost:8001/mcp/—
Prometheushttp://localhost:9090—
Grafanahttp://localhost:3000admin / admin

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:

ToolDescription
store_memoryEmbed + persist a memory (type, importance configurable)
search_memoriesSemantic + recency ranked retrieval with configurable alpha
delete_memoryForget a specific memory by UUID
count_memoriesHow many memories an agent/user pair has

Connect from Claude Desktop

Add to ~/.config/claude/claude_desktop_config.json:

json
{  "mcpServers": {    "memex": {      "type": "streamable-http",      "url": "http://localhost:8001/mcp/"    }  }}

Connect from Claude Code

bash
claude mcp add --transport http memex http://localhost:8001/mcp/

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:

VarDefaultDescription
SUMMARIZATION_ENABLEDtrueToggle the background job
SUMMARIZATION_THRESHOLD100Episodic count to trigger per pair
SUMMARIZATION_BATCH_SIZE50Oldest N memories to condense per run
SUMMARIZATION_INTERVAL_SECONDS300How often the job wakes up

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 importance score into ranking formula alongside similarity and recency

Tech stack

LayerChoiceWhy
APIFastAPI + uvicornAsync-first, fast, excellent OpenAPI generation
Embeddingsfastembed ONNX (BAAI/bge-small-en-v1.5)Local, zero API calls, ~12 ms CPU inference, 384-dim
DatabasePostgreSQL 16 + pgvectorRelational + vector in one system, no extra infra
Vector indexivfflatLower build cost than HNSW at this scale
PoolasyncpgDirect control, zero ORM overhead
SummariserPure Python extractiveJaccard dedup + TF scoring, zero ML deps, ~1 ms
RetrytenacityJitter-based backoff on transient errors
MetricsPrometheus + prometheus-fastapi-instrumentatorStandard observability
Load testingLocustPython-native, realistic user simulation

来源:README.md,提交 3e80a83

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

1
  1. v0.1.0最新Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分