Persistent memory for AI agents — semantic search, knowledge graph, and 22 MCP tools.
Works with any MCP-compatible agent: Cursor, Claude Desktop, OpenClaw, and more.
Repository: https://github.com/tonyzorin/agentmemory
Website: https://agentmemory.md
Prerequisites: Set
AGENTMEMORY_HOSTto your server's Tailscale IP before clicking "Add to Cursor":# Add to ~/.bashrc or ~/.zshrc export AGENTMEMORY_HOST=100.x.x.xThen restart Cursor and click the button. See Connecting Agents for full setup.
Every session starts from zero. You re-explain the same project context. The agent re-asks questions you've already answered. It suggests approaches you already tried and rejected — without knowing why.
agentmemory.md gives your agent a brain that persists across every session and every tool:
- Decisions with rationale — the agent never re-asks "should we use Redis or Postgres?" when you settled that three weeks ago
- Project context — stack, repo path, deploy commands, environments — no more three-message warm-up
- Failed experiments — root causes stored and recalled before the agent suggests the same approach again
- Goals and tasks — the agent picks up exactly where you left off
- People and relationships — stakeholders, customers, collaborators — who owns what, what customers have said
- Preferences and patterns — coding style, tooling preferences, anti-patterns, reusable workflows
Run on any Linux box — a VPS, homelab server, or Proxmox LXC. One command starts everything:
docker compose up -dInstall Tailscale on your server and laptop. Your server gets a stable private IP (100.x.x.x). No port forwarding, no firewall rules — Tailscale encrypts everything at the network layer.
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale ip -4 # note this IP
# Expose agentmemory to your tailnet (persists across reboots)
tailscale serve --bg --tcp 8081 tcp://localhost:8081Port 8081 must not be open to the public internet without Bearer token auth enabled. For Tailscale-only access, tailscale serve binds port 8081 on your Tailscale IP so only devices in your tailnet can reach it — no firewall rules needed. For public HTTPS exposure, see HTTP authentication.
Point Cursor, Claude Desktop, or any MCP-compatible agent at your server's Tailscale IP:
{
"mcpServers": {
"agentmemory": {
"url": "http://100.x.x.x:8081/mcp"
}
}
}Plain HTTP is safe here — Tailscale handles encryption at the network layer.
Copy the rules file for your agent so it knows when to fetch and store memories automatically — without you having to ask every time.
| Agent | File | Where to put it |
|---|---|---|
| All (reference) | rules/CORE.md |
Shared policy — adapters below include this |
| Cursor | rules/cursor.mdc |
.cursor/rules/agentmemory.mdc in your project |
| Claude Desktop | rules/CLAUDE.md |
Project → Set custom instructions → paste contents |
| Claude Code | rules/CLAUDE.md |
Project root as CLAUDE.md |
| ChatGPT | rules/chatgpt-instructions.md |
Settings → Personalization → Custom Instructions |
| Grok | rules/grok-instructions.md |
Custom instructions + MCP connector (see OAuth in README) |
Without rules, the agent only uses memory when you explicitly ask. With rules, it fetches context automatically at the start of each session and stores decisions as they happen.
Your agent now stores decisions, recalls context, tracks goals, and picks up exactly where you left off — across every session and every tool.
AI Agent (Cursor / Claude Desktop / OpenClaw / any MCP client)
│
├─[MCP / HTTP]──► MCP Server (FastMCP v3)
│ │
│ ▼
│ Memory Core Library
│ ├── Embeddings (BAAI/bge-base-en-v1.5, 768-dim)
│ ├── Hybrid Retrieval (Redis FT.HYBRID + AGE graph)
│ └── Storage
│ ├── Redis 8.6 (FT.HYBRID: BM25 + vector, RRF fusion)
│ └── PostgreSQL 18 + Apache AGE 1.7.0 (graph)
│
└─[CLI]──────► memory store / recall / goal / task / ...
mem store / recall / ... (short alias)
flowchart LR
Query --> Expand["Query Expansion\n(BM25 synonyms)"]
Expand --> Hybrid["Hybrid Search\n(Redis FT.HYBRID\nBM25 + Vector, RRF)"]
Hybrid --> Reranker["Cross-Encoder\nReranker\n(optional)"]
Reranker --> GraphBoost["Graph Boost\n(AGE neighborhood\n1-hop +0.3, 2-hop +0.1)"]
GraphBoost --> Score["Final Scoring\n(similarity + graph\n+ recency + importance)"]
Score --> Filter["Superseded\nFilter"]
Filter --> TopN["Top-N results\nto LLM context"]
The reranker is disabled by default. Enable it when your corpus exceeds ~1000 nodes or you need higher retrieval precision (see Reranker below).
Runs comfortably on a small VPS or homelab node.
| Component | Minimum |
|---|---|
| CPU | 2+ cores — embeddings run on CPU, no GPU needed |
| RAM | 4 GB — PostgreSQL + Redis + embedding model |
| Disk | 2 GB for Docker images, grows with your memory corpus |
| OS | Any Linux with Docker — Ubuntu, Debian, Proxmox LXC |
| Network | Tailscale recommended for secure private access |
# Start storage services
docker compose up -d
# Install
pip install -e .
# Store a memory
memory store "Anton prefers Claude for coding tasks" --tags preferences,tools
# Recall
memory recall "what does Anton prefer for coding"
# Get user profile
memory profile
# Stats
memory statsFor public internet exposure (HTTPS via Caddy or Cloudflare), enable Bearer token auth on the MCP HTTP/SSE endpoint. The LLM never sees the token — your MCP client sends it in the Authorization header.
docker compose run --rm app mem token createCopy the raw token immediately. Add the hash to your server .env:
AGENTMEMORY_AUTH_REQUIRED=true
AGENTMEMORY_TOKEN_HASHES=<sha256-hex-from-mem-token-create>Restart the app: docker compose up -d --build app
Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"agentmemory": {
"url": "https://mem.yourdomain.com/mcp",
"headers": {
"Authorization": "Bearer ${env:AGENTMEMORY_TOKEN}"
}
}
}
}Set AGENTMEMORY_TOKEN in your shell profile (never commit the raw token).
Claude Desktop (via mcp-remote):
{
"mcpServers": {
"agentmemory": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://mem.yourdomain.com/mcp",
"--header", "Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer am_..."
}
}
}
}Use env for the token value to avoid Windows/Cursor space-mangling bugs in args.
Claude Code:
claude mcp add --transport http agentmemory https://mem.yourdomain.com/mcp \
--header "Authorization: Bearer $AGENTMEMORY_TOKEN"OpenClaw / stdio: unchanged — local process, no Bearer auth.
Some browser-based MCP clients cannot paste a static Bearer token — they need OAuth with PKCE. agentmemory exposes /authorize and /token without /.well-known discovery so Cursor static Bearer headers keep working on the same host.
1. Enable on the server
Google Sign-In (recommended) — set in .env:
AGENTMEMORY_OAUTH_ENABLED=true
AGENTMEMORY_GOOGLE_CLIENT_ID=<from Google Cloud Console>
AGENTMEMORY_GOOGLE_CLIENT_SECRET=<from Google Cloud Console>
AGENTMEMORY_OAUTH_ALLOWED_EMAIL=tonyzorin@gmail.com
AGENTMEMORY_OAUTH_CLIENT_ID=agentmemory
AGENTMEMORY_PUBLIC_BASE_URL=https://mem.agentmemory.mdAdd authorized redirect URI in Google Cloud Console:
https://mem.agentmemory.md/oauth/google/callback
Or password consent (fallback):
mem oauth password-hash # prints AGENTMEMORY_OAUTH_PASSWORD_HASHProxy /authorize, /token, and /oauth/google/* in Caddy. Restart the app.
2. Custom connector (paste these in the client's OAuth form)
| Field | Value |
|---|---|
| Server URL | https://mem.agentmemory.md/mcp |
| Client ID | agentmemory |
| Client Secret | (leave blank) |
| Authorization Endpoint | https://mem.agentmemory.md/authorize |
| Token Endpoint | https://mem.agentmemory.md/token |
| Scopes | memory:full |
| Token Auth Method | none (PKCE only) |
Click Save & Connect, then Sign in with Google as tonyzorin@gmail.com (or enter the allowed email + password if using password mode).
Both auth methods run together — enabling OAuth does not replace Bearer tokens. Cursor keeps using Authorization: Bearer am_… in mcp.json; OAuth clients use amo_… tokens. /mcp accepts either via MultiAuth.
OAuth sessions are in-memory; restarting the container invalidates OAuth tokens (re-authorize). OAuth access tokens expire after 7 days. Bearer API key hashes are unaffected.
Google Sign-In binds the authorize session to an HttpOnly cookie (am_oauth_sid) to block login CSRF. If a Google OAuth client secret is ever exposed, rotate it in Google Cloud Console and update AGENTMEMORY_GOOGLE_CLIENT_SECRET on the server.
- App listens on
127.0.0.1:8081(or your port) - Caddy/Cloudflare terminates TLS and proxies to localhost
AGENTMEMORY_AUTH_REQUIRED=true+ token hashes in.env- For OAuth: also proxy
/authorize,/token,/oauth/google/*; keep/.well-known/*as 404 on the Cursor hostname - Never expose the raw Docker port publicly without TLS and auth
Cursor supports the modern Streamable HTTP transport natively. In ~/.cursor/mcp.json:
{
"mcpServers": {
"agentmemory": {
"url": "http://100.x.x.x:8081/mcp"
}
}
}Replace 100.x.x.x with your server's Tailscale IP. If HTTP authentication is enabled, add the headers block with Authorization: Bearer ${env:AGENTMEMORY_TOKEN}.
Download agentmemory.mcpb and double-click it. Claude Desktop will open an install dialog and prompt you for your server's Tailscale IP — no config file editing required.
Requires Claude Desktop 1.0.0 or later. See Anthropic's Desktop Extensions docs for more detail.
Claude Desktop does not natively support Streamable HTTP. Use
mcp-remote as a bridge. In
~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"agentmemory": {
"command": "npx",
"args": [
"mcp-remote",
"http://100.x.x.x:8081/mcp",
"--allow-http"
]
}
}
}Replace 100.x.x.x with your server's Tailscale IP. The --allow-http flag is needed
because mcp-remote defaults to HTTPS — Tailscale traffic is already encrypted at the
network layer so plain HTTP is safe here.
Restart Claude Desktop after saving.
For OpenClaw (stdio mode), see OPENCLAW_SETUP.md. For automatic recall/capture, see the agentmemory-openclaw-plugin.
Ready-to-use rule files that teach your AI agent when to fetch and store memories. Copy the one matching your client:
| Client | File | Install to |
|---|---|---|
| All (reference) | rules/CORE.md |
Shared fetch/store policy |
| Cursor | rules/cursor.mdc |
.cursor/rules/agentmemory.mdc |
| Claude Code | rules/CLAUDE.md |
Project root as CLAUDE.md |
| ChatGPT | rules/chatgpt-instructions.md |
Settings → Personalization → Custom Instructions |
| Grok | rules/grok-instructions.md |
Custom instructions + MCP connector |
| OpenClaw | rules/openclaw.md |
See OPENCLAW_SETUP.md |
These rules implement tiered context fetching — the agent loads only the memory context needed for each request, from zero (bug fixes) to full profile (explicit memory queries). See AGENTS.md for the full specification.
After copying, customize the project tags section for your own projects.
| Component | Technology |
|---|---|
| Language | Python 3.14 |
| MCP Server | FastMCP v3 (Streamable HTTP on port 8081) |
| Vector Search | Redis 8.6 (FT.HYBRID: BM25 + vector, RRF fusion) |
| Knowledge Graph | PostgreSQL 18 + Apache AGE 1.7.0 |
| Embeddings | BAAI/bge-base-en-v1.5 (768-dim, CPU, MTEB ~63) |
| CLI | Click + Rich (memory or mem) |
Every memory_recall result includes these fields:
| Field | Description |
|---|---|
score |
Final combined score (0–1) |
similarity |
Normalized RRF score from Redis FT.HYBRID, or cross-encoder score if reranker is enabled |
reranker_score |
Cross-encoder score (0–1) — present only when reranker is enabled |
graph_boost |
+0.3 for 1-hop graph neighbors, +0.1 for 2-hop |
recency |
Exponential decay: 1.0 = just stored, ~0.5 = 29 days old |
importance |
Stored importance value (varies by node type) |
Formula (similarity-adaptive):
- Strong match (
similarity > 0.6):(sim×0.80 + graph×0.15 + recency×0.05) × importance_weight - Weak/medium match:
(sim×0.50 + graph×0.20 + recency×0.20 + importance×0.10) × importance_weight
A cross-encoder reranker sits between hybrid search and the final scoring step. When enabled, it scores the top-K candidates jointly with the query, replacing the RRF similarity score with a more nuanced relevance score.
Enable with:
RERANKER_ENABLED=true
RERANKER_MODEL=cross-encoder/ms-marco-MiniLM-L6-v2 # default, CPU-feasible
RERANKER_TOP_K=20 # number of candidates to rerankOr in docker-compose:
environment:
RERANKER_ENABLED: "true"Model choices:
cross-encoder/ms-marco-MiniLM-L6-v2— 22M params, ~50ms for top-20 on CPU, good quality (default)cross-encoder/ms-marco-MiniLM-L12-v2— 33M params, slightly better qualityBAAI/bge-reranker-v2-m3— multilingual, strong MTEB scores
When to enable: Corpus of 1000+ nodes, or if you notice relevant memories being ranked below less-relevant ones. At small corpus sizes the improvement is marginal.
The model is lazy-loaded on first use and uses the same sentence-transformers dependency already in the stack — no new installs required.
17 node types: Memory, Learning, Decision, Goal, Initiative, Task, Project, Person, ExternalContact, Preference, Environment, Tool, Workflow, Resource, Competitor, Metric, CustomerFeedback
26 edge types: WORKS_ON, ABOUT, BELONGS_TO, FOR, ACHIEVED_VIA, BROKEN_INTO, TRACKS, COMPETES_WITH, PREVENTED, SUPERSEDES, and more.
When a new memory contradicts or replaces an older one, mark the old one as superseded:
# New memory that replaces an old one
result = memory_store("Anton switched to Rust for systems work", node_type="Preference")
# memory_store returns potential_conflict if a similar memory (0.75–0.92) already exists:
# {"id": "...", "potential_conflict": {"id": "<old-id>", "content": "Anton prefers Python..."}}
# Supersede the old one — it will be excluded from future search results
memory_supersede(new_id=result["id"], old_id="<old-id>")Superseded nodes are kept in the graph for audit purposes but filtered from all memory_recall results.
Core memory: memory_store, memory_recall, memory_update, memory_relate, memory_context, memory_forget, memory_supersede, memory_entities, memory_split, memory_batch_update, memory_profile
Work structure: goal_manage, initiative_manage, task_manage, timeline
Knowledge: learning_store, workflow_store
Market intelligence: competitor_manage, metric_record, metric_query, customer_feedback_store
memory and mem are interchangeable — mem is the short alias.
mem store "content" [--type Memory|Learning|Decision|...] [--tags tag1,tag2] [--importance 0.8]
mem recall "query" [--limit 10] [--type Memory]
mem profile [--no-recent] [--limit 20]
mem update <memory-id> [--content "..."] [--name "..."] [--tags tag1,tag2] [--importance 0.8]
mem forget <memory-id> [--yes]
mem learn "what failed" --what-failed "..." --why "..." --avoid "..."
mem decide "decision" --rationale "why"
mem goal create "name" [--project <id>]
mem goal list
mem initiative create "name" [--goal <id>]
mem task create "name" [--initiative <id>]
mem task done <task-id> [--summary "what was done"]
mem task list
mem relate <from-id> <to-id> <EDGE_TYPE>
mem context <entity-id> [--depth 2]
mem timeline [--since 7d] [--type Memory]
mem graph <entity-id> [--depth 2]
mem competitor add "name" [--website url] [--positioning "..."]
mem metric record "name" <value> [--type visitors] [--unit count]
mem metric query "name" [--since 30d]
mem workflow "name" "description" [--step "step 1"] [--step "step 2"]
mem split <memory-id> --chunk "fact 1" --chunk "fact 2"
mem batch-update --type Goal --importance 0.8
mem consolidate [--dry-run] [--no-dry-run] [--threshold 0.85] [--type Memory]
mem gc [--dry-run] [--type Task]
mem stats
mem export [-o backup.json]
mem import backup.jsonRun periodically to keep the corpus clean as it grows:
# Preview what would be merged (safe — no changes)
memory consolidate
# Actually merge near-duplicates (asks for confirmation)
memory consolidate --no-dry-run
# More conservative threshold (only very close duplicates)
memory consolidate --no-dry-run --threshold 0.90
# Only consolidate Memory nodes
memory consolidate --no-dry-run --type MemoryThe command groups semantically similar nodes into clusters and merges each cluster into its highest-importance node, preserving content and re-attaching graph edges.
Many agent frameworks offer simple flat-file memory (a markdown file with notes). Here's an honest comparison:
| Flat file | agentmemory.md | |
|---|---|---|
| Setup | Zero — just a file | Docker + PostgreSQL + Redis |
| Reliability | Always works | Requires running services |
| Corpus limit | ~50–100KB before context overflow | Unlimited |
| Retrieval at scale | LLM reads everything (works well <100KB) | Hybrid search + graph boost (needed >100KB) |
| Structure | Unstructured text | Typed nodes, edges, relationships |
| MCP API | Agent edits text file (fragile) | 22 purpose-built tools |
| Conflict handling | Manual | SUPERSEDES edges + potential_conflict detection |
| Maintenance | None | gc, consolidate, reindex |
Use a flat file when: You have one project, short memory, and don't want infrastructure.
Use agentmemory.md when: You have multiple projects, growing history, or want structured goal/task/decision tracking.
Self-hosted — Free
Open source. Your data stays on your server. Full control over every component. Docker Compose setup, all 22 MCP tools, CLI included, runs on any Linux box.
Managed — Coming soon
Zero infrastructure. We run the server, handle updates, and keep your memory available everywhere. Join the waitlist.
# Start services first
docker compose up -d
# Run all tests
pytest
# Run specific phase
pytest tests/test_redis_client.py -v
pytest tests/test_age_client.py -v
pytest tests/test_e2e.py -v