Memories
Local semantic memory for AI assistants. Zero-cost, <50ms, hybrid BM25+vector search.
Works with Claude Code, Claude Desktop, Claude Chat, Codex, OpenCode, Cursor, ChatGPT, OpenClaw, and anything that can call HTTP or MCP.
Key capabilities (v5.17.0):
- Hybrid search — BM25 + vector + recency + feedback + confidence + graph (6-signal RRF fusion with PPR-scored graph expansion)
- Write doctrine — corrections supersede instead of being dropped: a colliding write replaces the similar memory and archives the old version with a supersedes link (
on_duplicate: supersede|skip|add); agents update facts viamemory_update - Secret redaction — credential-shaped content (API keys, JWTs, tokens, URL credentials) is redacted before any extraction LLM call or storage, with a context guard that spares placeholders and localhost DSNs
- Resilient capture — hook circuit breaker (instant skip while the backend is down), extract-queue backpressure (429 + Retry-After), crash-safe atomic persistence
- Graph-aware retrieval — automatic
related_tolinks between memories, PPR-scored multi-hop traversal, +20% retrieval lift on 2-hop benchmarks - Temporal reasoning —
document_attimestamps, version preservation on UPDATE (archive + supersedes link),since/untildate-range filters - Automatic extraction — LLM-powered AUDN (Add/Update/Delete/Noop) with dry-run, per-fact approval, and auto-linking
- Operator workbench — create, edit, merge, bulk actions, extraction trigger, lifecycle panel, conflict resolution
- Feedback-weighted ranking — search learns from useful/not_useful signals
- Lifecycle policies — per-prefix TTL and confidence-based auto-archive with operator-visible evidence
- Quality benchmarks — LongMemEval system eval 83.6% overall (vs 69.5% v4.0.0 baseline), 85.5% on temporal reasoning (vs 42.2% baseline), 98.3% R@5; MuSiQue + graph benchmarks included
- Full audit trail — every mutation tracked, lifecycle timeline in UI, version chains via supersedes links
- Self-hosted — your data, your infrastructure, no cloud dependency
Start here:
API Quick Start
# 1. Clone and build
git clone https://github.com/divyekant/memories.git
cd memories
docker compose up -d
# 2. Verify
curl http://localhost:8900/health
# 3. Add a memory
curl -X POST http://localhost:8900/memory/add \
-H "Content-Type: application/json" \
-d '{"text": "Always use TypeScript strict mode", "source": "standards.md"}'
# 4. Search
curl -X POST http://localhost:8900/search \
-H "Content-Type: application/json" \
-d '{"query": "TypeScript config", "k": 3, "hybrid": true}'
The service runs at http://localhost:8900. API docs at http://localhost:8900/docs. Web UI at http://localhost:8900/ui.
Web UI
The built-in UI at /ui provides:
- Dashboard — memory stats, extraction metrics, server info
- Memories — browse, search, filter, and manage memories with list+detail or grid view
- Create, inline edit, pin/archive with undo, bulk actions (archive/delete/retag/re-source/merge)
- Extraction trigger with dry-run preview and per-fact approve/reject
- Tabbed detail panel: Overview (edit) | Lifecycle (origin, confidence, audit timeline, feedback history) | Links
- Conflict resolution modal (Keep A/B/Merge/Defer with soft archive)
- Extractions — extraction job stats and token usage
- API Keys — configure authentication
- Health — conflicts, problem queries (negative feedback), stale memories (retrieved but never useful), evidence strength badges
- Settings — provider config, server info, theme toggle (dark/light/system), export and maintenance
No build step — vanilla JS + CSS served directly from webui/.
CLI
The memories CLI provides full access to the API from your terminal.
Install
pip install -e .
# Or if using the Docker image, the CLI is included
Usage
# Search
memories search "TypeScript config"
# Add a memory
memories add "Always use strict mode" --source standards
# List memories
memories list --source standards
# Check novelty before adding
memories is-novel "TypeScript strict mode"
# Batch operations
memories batch add memories.jsonl
# Admin
memories admin stats
memories admin health
# Backups
memories backup create
memories backup list
# Full help
memories --help
Export & Import
# Export all memories
memories export -o backup.jsonl
# Export filtered by source
memories export --source "claude-code/" -o project.jsonl
# Export with date range
memories export --source "proj/" --since 2026-01-01 -o recent.jsonl
# Import (clean migration)
memories import backup.jsonl
# Import with smart dedup
memories import backup.jsonl --strategy smart
# Import with source remapping
memories import backup.jsonl --source-remap "old/=new/"
Agent Integration
The CLI auto-detects when piped and outputs JSON:
# JSON output for agents (automatic when piped)
memories search "auth" | jq '.data.results[0].text'
# Force JSON in any context
memories --json search "auth"
# Force human-readable when piped
memories --pretty list
Configuration
# Set server URL
memories config set url http://localhost:8900
# Set API key
memories config set api_key your-key-here
# View resolved config
memories config show
Config resolution: CLI flags > ~/.config/memories/config.json > env vars > defaults.
Shared project memory (Phase 1)
Two collaborators can share durable project knowledge through one configured Memories host while keeping their private memories separate. Commit only this declaration to the repository:
# .memories/project.yaml
project_id: fplguru
shared_memory: true
The declaration is an identity signal, not an access grant: it contains no
URL, API key, member list, or role. Collaborative mode requires exactly one
backend and a server-issued managed key whose GET /api/keys/me response has
type: "managed" and a stable principal_id. Use separate prefix scopes such
as person/dk/fplguru plus project/fplguru and
person/darshan/fplguru plus project/fplguru; both clients must point to the
same host. Environment/admin keys and multi-backend configuration do not
activate collaborative mode.
Automatic extraction remains private at
person/<principal>/<project>/knowledge. After applying the durable-sharing
test (another contributor will need the fact without the current session),
write one deliberate project fact with memory_add and a source of
project/<project>/<decisions|knowledge|state|operations>. The server stamps
the authenticated author and origin client; client metadata cannot impersonate
either field. See the shared project memory playbook
for exact administrator payloads, fresh-session isolation probes, narrowing
and revocation, legacy-prefix migration, and the post-deployment manual seed
step. No production memory is seeded by this implementation.
Shared project memory (Phase 2 promotion gate)
Phase 2 is private-first and model-reviewed, but remains inert by default:
PROJECT_PROMOTION_MODE=off
Upgrade the backend first and verify the v5.15.1 pruning-safety hotfix (every
project/ source is protected from scheduled and manual pruning/consolidation)
before upgrading client hooks or MCP packages. Then run the managed-key
isolation check from fresh sessions: exactly one backend; type: "managed"
keys with stable principal_id; each principal can read the shared prefix but
cannot read the other principal's private prefix. An admin/environment key,
missing principal, revoked key, or multi-backend setup fails closed.
After those checks, a reviewed repository must explicitly add
promotion: {mode: shadow} to .memories/project.yaml, set
PROJECT_PROMOTION_MODE=shadow, and set the measured
PROJECT_PROMOTION_RELEVANCE_THRESHOLD. Shadow records would-promote evidence but
never writes a new shared target. Do not add .memories/project.yaml, activate
FPLGuru, or seed project history in this implementation PR. The exact
provider-backed fixture command is:
uv run python eval/run_promotion_eval.py --fixtures eval/fixtures/project_promotion_v1.jsonl --threshold <candidate-threshold> --output /tmp/promotion-eval.json
Pre-register the exact provider/model/policy/threshold identity and retain
three consecutive attempts; every report must pass. The versioned suite must
have at least 100 weighted fixtures and 100 distinct transcripts, at least 95%
precision, at least 85% recall, zero unsafe high-risk promotions, and zero
high-risk approve reviews before deterministic vetoes. Before
auto is considered, live evidence must show two weeks, 50 total reviewed
candidates, 30 manually inspected would-promote outcomes, five would-promote
outcomes from each principal, and zero unsafe live outcomes. A classifier or
reviewer policy/provider/model change resets prior approvals and restarts the
time and volume gates. Alert on five new unreviewable candidates in one hour
and separately flag any unresolved item reaching seven days.
Rollback is PROJECT_PROMOTION_MODE=off; it stops new review and shared-target
creation while allowing only idempotent finalization/linkage repair already in
flight. Phase 2 adds no bulk dismissal API, project consolidation, or seed.
Changing both caps to auto does not bulk-publish the shadow backlog:
shadow_approved candidates require individual owner/admin approval in small,
observed cohorts, while only newly captured auto candidates may promote
automatically. After a backend restart, promotion remains private until a
managed project extraction authenticates and reports the current declaration;
an authenticated repository off declaration still stops delayed work.
The separate FPLGuru shadow evidence record is not created or satisfied by
this PR. See the promotion activation playbook for
the full operator sequence.
Architecture
AI Client (Claude, Codex, OpenCode, Cursor, ChatGPT, OpenClaw)
|
|-- MCP protocol (Claude Code / Desktop / Codex / OpenCode / Cursor)
|-- Client hooks/plugins (Claude Code / Codex / OpenCode / Cursor)
|-- REST API (everything else)
v
MCP Server (mcp-server/index.js)
|
v
Memories Service (Docker :8900)
|-- FastAPI REST API
|-- Hybrid Search (Memories vector + BM25 keyword, RRF fusion)
|-- Markdown-aware chunking
|-- Event Bus (SSE stream + webhook delivery)
|-- Audit Log (append-only trail)
|-- Memory Relationships (graph edges between memories)
|-- Confidence Decay (time-based relevance attenuation)
|-- Auto-backups
v
Persistent Storage (data/)
|-- Qdrant vector store (embeddings + metadata)
|-- metadata.json (memory text + metadata)
|-- backups/ (auto, keeps last 10)
Detailed docs:
Integration Guides
For Claude Code, Codex, and Cursor, npx memories-mcp@latest init (see Quick setup) wires everything below automatically. The manual steps in this section remain accurate if you prefer to configure a client by hand or need a target the CLI doesn't cover yet (OpenCode, OpenClaw, ChatGPT).
Claude Code (CLI)
The MCP server gives Claude Code native memory_search, memory_get, memory_add, memory_update, memory_extract, memory_timeline, memory_evidence, memory_delete, memory_delete_batch, memory_delete_by_source, memory_count, memory_list, memory_stats, memory_is_novel, memory_is_useful, memory_conflicts, memory_missed, and memory_deferred tools.
Setup:
- Install the MCP server dependencies:
cd memories/mcp-server
npm install
- Register the MCP server with Claude Code (user scope — available in every project):
claude mcp add -s user \
-e MEMORIES_URL=http://localhost:8900 \
-e MEMORIES_API_KEY=your-api-key-here \
-- memories node /path/to/memories/mcp-server/index.js
This writes to ~/.claude.json. Do not add MCP servers to ~/.claude/settings.json or ~/.claude/.mcp.json — Claude Code CLI does not read MCP config from those files (Claude Desktop uses separate config, see below).
-
Restart Claude Code. The tools are now available in every project.
-
(Optional) Install the Memories skill for disciplined memory capture and proactive recall:
mkdir -p ~/.claude/skills/memories
ln -s /path/to/memories/skills/memories ~/.claude/skills/memories
The skill teaches the assistant three responsibilities: when to search (proactive recall), when and how to store (hybrid memory_add + memory_extract), and when to maintain (updates, deletes, cleanup via AUDN). It adds ~11% token overhead but improves memory discipline by ~43% in eval benchmarks.
- (Recommended) Install the CC plugin for a single-step setup that bundles hooks, skills, and CLAUDE.md:
Hooks, skills, and CLAUDE.md are now packaged as a Claude Code plugin in the plugin/ directory. See plugin/INSTALL.md for details.
Usage (Claude Code will call these automatically when relevant):
- "Search my memory for authentication patterns"
- "Remember that we decided to use Prisma for the ORM"
- "Check if this pattern is already in memory before adding it"
- "Show me all memories from the bug-fixes source"
For a single project only, use project scope instead:
claude mcp add -s project \
-e MEMORIES_URL=http://localhost:8900 \
-e MEMORIES_API_KEY=your-api-key-here \
-- memories node /path/to/memories/mcp-server/index.js
Claude Desktop (Chat / Cowork)
Same MCP server, different config file. Claude Desktop reads MCP config from its own config file — not from ~/.claude.json (which is Claude Code CLI only).
Setup:
- Install dependencies (same as above):
cd memories/mcp-server
npm install
- Add to
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) or%APPDATA%\Claude\claude_desktop_config.json(Windows):
{
"mcpServers": {
"memories": {
"command": "node",
"args": ["/path/to/memories/mcp-server/index.js"],
"env": {
"MEMORIES_URL": "http://localhost:8900",
"MEMORIES_API_KEY": "your-api-key-here"
}
}
}
}
- Restart the Claude Desktop app. Memory tools appear in chat and cowork mode.
Claude Chat (Web at claude.ai)
Claude Chat on the web does not support MCP directly. Two options:
Option A: Remote MCP via Cloudflare Tunnel (recommended)
If you expose the Memories service via a tunnel (e.g., memory.yourdomain.com), you can use Claude's remote MCP connector feature to connect to it. See the Remote Access section below.
Option B: Manual curl in prompts
Paste curl commands in your messages and ask Claude to interpret the results:
Search my memory service for React patterns:
curl -X POST https://memory.yourdomain.com/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_KEY" \
-d '{"query": "React patterns", "k": 5, "hybrid": true}'
Codex (OpenAI)
Codex supports MCP natively via ~/.codex/config.toml.
Repo-local Codex plugin (optional):
This repository now includes a repo-local Codex plugin at plugins/memories, exposed through .agents/plugins/marketplace.json.
Install the memories plugin from the repo marketplace if you want its
discipline and setup guidance. The plugin is intentionally thin: its setup
skill guides the published npm installer, which owns hooks and MCP wiring.
It does not require this checkout or bundle a server.
Local stdio setup (recommended):
Run the published installer from any directory:
npx -y memories-mcp@latest init --codex
This configures the local stdio MCP server and the supported Codex hooks. The
backend --url (or MEMORIES_URL) is the local REST service that the stdio
server and hooks use; it is distinct from a direct remote MCP URL. If backend
authentication is enabled, enter the key only in the local installer prompt
or environment and never paste or print it in a setup report.
Direct remote MCP (OAuth):
npx -y memories-mcp@latest init --codex --mcp-url https://... --yes
codex mcp login memories
--mcp-url must be an absolute HTTPS MCP endpoint. It writes a URL/OAuth
registration, skips local REST health/bootstrap, and never copies a backend
API key into Codex. Do not combine it with --url or --api-key.
Remote MCP tools and lifecycle-hook transport are separate: the hooks remain
installed, but they are inactive until MEMORIES_URL or a REST
backends.yaml configuration is available to the hook process.
If your local stdio API key is prefix-scoped and does not allow codex/*, set
hook source overrides in ~/.config/memories/env:
MEMORIES_SOURCE_PREFIXES="your-authorized-prefix/{project},learning/{project},wip/{project}"
MEMORIES_EXTRACT_SOURCE="your-authorized-prefix/{project}"
Restart Codex after setup. The installer verifies only local hook/config presence; it does not print credentials.
Codex lifecycle and approvals:
The npm installer checks codex --version and selects the profile supported by
that client. Codex >= 0.146.0 receives ten events — SessionStart,
UserPromptSubmit, Stop, PostToolUse, PreToolUse, PreCompact,
PostCompact, SubagentStart, SubagentStop, and SessionEnd. Older or
unparseable versions receive the five-event profile (the first five above).
PostCompact is silent and returns only suppressOutput; compaction recall is
provided by SessionStart(source=compact). SessionEnd sends one extract POST
to the first routed backend with curl --max-time 2; it does not poll, and its
manifest hook timeout is exactly 3 seconds.
The installer auto-approves six read-only tools (memory_search,
memory_list, memory_count, memory_stats, memory_is_novel, and
memory_conflicts). memory_is_useful is persistent feedback and remains
prompt-gated whenever it is mentioned or called.
Codex native Memories coexistence:
External Memories is the durable, searchable cross-client authority. Native
Codex Memories is an optional local derived cache. The npm installer never sets
either system. If duplicate context is a concern, users may optionally add
memories.disable_on_external_context = true under the exact root [memories]
table; this is a recommendation only, not an installer default.
Codex uses ~/.codex/hooks.json for lifecycle hooks and
~/.codex/config.toml for MCP + developer instructions. The optional native
settings are reported read-only by memories doctor; setup does not mutate
them.
Multi-backend: Codex uses its own npm-shipped hook scripts, and they honor the same multi-backend routing env/config described in multi-backend routing.
Usage (Codex will discover the tools automatically):
- "Search memory for how we handle error logging"
- "Store this architecture decision in memory"
- "List all memories from the project-setup source"
OpenCode
OpenCode uses Memories through MCP plus OpenCode plugin hooks. It does not use Claude Code or Codex shell hooks.
Setup:
cd memories
npm --prefix ./mcp-server install
./integrations/claude-code/install.sh --opencode
This configures:
~/.config/opencode/opencode.jsonwith a mergedmcp.memorieslocal server entry- a
zsh -lcMCP command that sources~/.config/memories/envand runsmcp-server/index.js - the repo-local plugin path
integrations/opencode/plugin/memories.js - the Memories skill at
~/.config/opencode/skills/memories/SKILL.mdwith marker-safe behavior
The plugin reads ~/.config/memories/env by default for MEMORIES_URL, MEMORIES_API_KEY, MEMORIES_ACTIVE_SEARCH_LOG, and MEMORIES_ACTIVE_SEARCH_METRICS. It injects prompt-time recall context and records active-search tool_call telemetry for memory tool calls with client=opencode to ~/.config/memories/active-search.jsonl unless metrics are disabled. It searches exact project prefixes first in this order: opencode/{project}, claude-code/{project}, codex/{project}, learning/{project}, wip/{project}.
OpenCode-authored extracted memories should use opencode/{project} when extraction is added. The first implementation does not auto-extract by default because automatic extraction remains gated until reliable OpenCode end-of-turn transcript access is proven.
See integrations/opencode/README.md for details.
Cursor
Cursor supports MCP with the same server.
Setup:
- Install dependencies:
cd memories/mcp-server
npm install
- Add to Cursor MCP config:
- Global:
~/.cursor/mcp.json - Project:
.cursor/mcp.json
{
"mcpServers": {
"memories": {
"command": "node",
"args": ["/path/to/memories/mcp-server/index.js"],
"env": {
"MEMORIES_URL": "http://localhost:8900",
"MEMORIES_API_KEY": "your-api-key-here"
}
}
}
}
- Restart Cursor.
Cursor also supports the full hook lifecycle via its "Third-party skills" feature. Run ./integrations/claude-code/install.sh --cursor to install hooks alongside the MCP config.
Multi-backend: Cursor uses the same hook scripts as Claude Code, so multi-backend routing works automatically when configured.
ChatGPT (Custom GPT)
ChatGPT uses Custom Actions (OpenAPI schema) rather than MCP. This requires exposing the Memories service over the internet.
Prerequisites: Memories service accessible via HTTPS (see Remote Access).
Setup:
-
Enable API key auth on the Memories service (set
API_KEYenv var in docker-compose). -
In ChatGPT, go to Explore GPTs > Create a GPT > Configure > Actions.
-
Import this OpenAPI schema (replace
memory.yourdomain.comwith your URL):
openapi: 3.0.0
info:
title: Memories
version: 2.0.0
description: Semantic memory search and storage
servers:
- url: https://memory.yourdomain.com
paths:
/search:
post:
operationId: searchMemory
summary: Search memories by semantic similarity
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [query]
properties:
query:
type: string
description: Natural language search query
k:
type: integer
default: 5
description: Number of results
hybrid:
type: boolean
default: true
description: Use hybrid BM25+vector search
responses:
'200':
description: Search results
/memory/add:
post:
operationId: addMemory
summary: Store a new memory
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text, source]
properties:
text:
type: string
description: Memory content
source:
type: string
description: Source identifier
deduplicate:
type: boolean
default: true
responses:
'200':
description: Memory added
/memory/is-novel:
post:
operationId: isNovel
summary: Check if text is already known
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text]
properties:
text:
type: string
threshold:
type: number
default: 0.88
responses:
'200':
description: Novelty check result
/memories:
get:
operationId: listMemories
summary: Browse stored memories with pagination
parameters:
- name: offset
in: query
schema:
type: integer
default: 0
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 5000
- name: source
in: query
description: Source prefix filter
schema:
type: string
responses:
'200':
description: List of memories
delete:
operationId: deleteMemoriesByPrefix
summary: Bulk delete all memories matching a source prefix
parameters:
- name: source
in: query
required: true
description: Source prefix to match
schema:
type: string
responses:
'200':
description: Delete count
/memories/count:
get:
operationId: countMemories
summary: Count memories optionally filtered by source prefix
parameters:
- name: source
in: query
description: Source prefix filter
schema:
type: string
responses:
'200':
description: Memory count
/stats:
get:
operationId: getStats
summary: Memory index statistics
responses:
'200':
description: Index stats
-
Under Authentication, choose API Key with header name
X-API-Key. -
Add instructions to the GPT system prompt:
You have access to a persistent memory system. Use it to:
- Search for relevant context before answering questions (searchMemory)
- Store important decisions, patterns, and learnings (addMemory)
- Check if something is already known before adding (isNovel)
- Browse what's stored (listMemories)
Always search memory at the start of conversations to load context.
OpenClaw
OpenClaw uses a Skill (SKILL.md) with shell helper functions that call the REST API directly.
Setup:
- Create the skill directory and copy the skill file:
mkdir -p ~/.openclaw/skills/memories
cp integrations/openclaw-skill.md ~/.openclaw/skills/memories/SKILL.md
Or see the full SKILL.md in this repo at integrations/openclaw-skill.md.
- Add the API key to the OpenClaw gateway config so skill exec calls can authenticate:
openclaw config patch '{"env": {"vars": {"MEMORIES_URL": "http://localhost:8900", "MEMORIES_API_KEY": "your-api-key-here"}}}'
Or edit ~/.openclaw/openclaw.json directly under env.vars, then restart the gateway.
The SKILL.md reads $MEMORIES_API_KEY from the environment and includes automatic lifecycle guidance for when to recall, extract, and sync memories.
Key commands available to OpenClaw agents:
memory_search_memories "query" [k] [threshold] [hybrid]
memory_add_memories "text" "source" [deduplicate]
memory_is_novel "text" [threshold]
memory_delete_memories <id>
memory_delete_source_memories "pattern"
memory_delete_by_prefix "source_prefix"
memory_count_memories [source_prefix]
memory_list_memories [offset] [limit] [source]
memory_rebuild_index
memory_dedup_memories [dry_run] [threshold]
memory_stats
memory_health
memory_backup [prefix]
memory_restore "backup_name"
All functions use jq for safe JSON construction and read auth from $MEMORIES_API_KEY in the gateway environment (no hardcoded secrets).
Multi-backend: Not yet supported for OpenClaw. OpenClaw uses skill-based extraction (not hooks), so multi-backend routing does not apply. This is planned for a future release.
Remote Access
To use Memories from anywhere (Claude Chat web, ChatGPT, mobile, other machines), expose it via a Cloudflare Tunnel or similar.
Setup with Cloudflare Tunnel
- Enable API key auth in your docker-compose:
environment:
- API_KEY=your-secret-key-here
Rebuild and restart: docker compose build memories && docker compose up -d memories
- Add to your Cloudflare tunnel config (e.g., in
~/.cloudflared/config.yml):
ingress:
- hostname: memory.yourdomain.com
service: http://localhost:8900
- Update MCP server env to use the remote URL:
{
"env": {
"MEMORIES_URL": "https://memory.yourdomain.com",
"MEMORIES_API_KEY": "your-secret-key-here"
}
}
Now every client — Claude Code on your laptop, Codex, OpenCode, Cursor, Claude Desktop on your phone, ChatGPT, OpenClaw — all hit the same memory store running on your Mac mini.
Claude web (claude.ai) connector
mcp-server/remote/ is a small OAuth 2.1 + Streamable HTTP front door that lets you add Memories as a custom connector in claude.ai, so Claude in the browser (or mobile) can search and write memories without a local MCP client. It sits in front of your existing memories service — it does not replace it.
- Generate a password hash and a token secret:
node -e "import('./mcp-server/remote/oauth.mjs').then(m => console.log(m.hashPassword(process.argv[1])))" 'your-password'
openssl rand -hex 32
The first command prints a single line shaped like scrypt:<base64-salt>:<base64-hash> — that's REMOTE_MCP_PASSWORD_HASH. The second prints a 64-char hex string — that's REMOTE_MCP_TOKEN_SECRET.
- Set the env vars (e.g. in
.envnext todocker-compose.yml):
REMOTE_MCP_ISSUER=https://mcp.yourdomain.com # public URL this will be reachable at
REMOTE_MCP_PASSWORD_HASH=scrypt:... # from step 1
REMOTE_MCP_TOKEN_SECRET=... # from step 1
Put the service behind a Cloudflare Tunnel (or similar) the same way as the "Setup with Cloudflare Tunnel" section above, pointing at http://localhost:8910 instead of 8900.
- Start it (profile-gated, so it won't start on a plain
docker compose up):
docker compose --profile remote-mcp up -d
- Add the connector in claude.ai: Settings → Connectors → Add custom connector →
https://mcp.yourdomain.com/mcp. Claude walks you through the OAuth login (the password from step 1); once authorized, Memories tools show up in the chat like any other connector.
REMOTE_MCP_TRUST_PROXY — the per-IP rate limiter buckets on req.ip, which by default is the raw TCP socket address. In the docker/tunnel topology above, every request arrives from the same container-bridge hop (e.g. Caddy or the tunnel sidecar), so without this setting every real client collapses into one shared bucket. docker-compose.yml defaults it to uniquelocal — an Express trust-proxy preset that trusts X-Forwarded-For only from private/link-local/unique-local ranges (RFC 1918 and friends), which is exactly the bridge network a container's proxy hop lives on — so req.ip becomes the real client IP again. Set REMOTE_MCP_TRUST_PROXY=off to disable and bucket on the raw socket address instead (e.g. if you're running the server bare, with no proxy in front of it) — false and an empty value are also accepted as the same "disabled" sentinel. Never set it to true (trusts every hop in the chain) on an internet-facing deployment — that lets any client spoof X-Forwarded-For to pick its own rate-limit bucket.
See mcp-server/remote/server.mjs for the full env contract (REMOTE_MCP_AUTH=none disables auth for local testing — never expose that mode publicly) and the commented-out remote-mcp block in mcp-server/assets/backend/docker-compose.standalone.yml for the standalone-deployment equivalent.
Cloud coding sessions (claude.ai/code)
A cloud session runs in a fresh VM built from a git clone of your repository. Nothing from your local ~/.claude is present — not your user-level CLAUDE.md, skills, hooks, enabledPlugins, or ~/.claude.json MCP servers. That splits Memories into two halves:
Memories reaches an agent through three independent layers, and they do different jobs:
The layers are complementary, not redundant. Tools without hooks means memories are reachable but nothing recalls them automatically. Hooks without the rules means context gets injected but the agent may ignore it, paraphrase it as its own, or re-ask something already answered — the rules are what turn injected text into behaviour ("search before asking a clarifying question", "never say memory confirms", "preserve until/unless clauses verbatim"). Tools plus rules without hooks means the agent searches only when it remembers to.
A cloud session gets the first for free and the other two only from the setup script below.
If you only need Claude to be able to reach your memories, the connector alone is enough; stop here. The rest buys you automatic per-prompt recall and end-of-session extraction.
1. Install the hooks from the environment's setup script.
npx -y memories-mcp init --claude --yes \
--url https://memory.yourdomain.com \
--mcp-name YourMcpServerName \
--no-persist-api-key
Put that in the cloud environment's Setup script box (it runs before Claude Code launches). It writes the hooks to ~/.claude/hooks/memory/, wires them into ~/.claude/settings.json, and appends the behavioural rules to ~/.claude/CLAUDE.md.
Committing
enabledPluginsdoes NOT work in cloud — do not try it. A repo-committed.claude/settings.jsondeclaringextraKnownMarketplacesandenabledPluginslooks like it should be enough, but the container performs no marketplace fetch and no plugin install, so none of the hook events register. Verified in a real container:installed_plugins.jsonwas{"plugins":{}},~/.claude/plugins/marketplaces/did not exist, andhook.logwas never created. It is not a network problem — the marketplace clones fine from inside the VM. The setup script sidesteps the broken step rather than working around it.
Two flags earn their place:
--url— the setup script runs before the environment variables exist, so the installer cannot readMEMORIES_URLand would otherwise persist itshttp://localhost:8900default into~/.config/memories/env. Hooks let a real environment variable win over that file, so it still works without the flag, butmemories doctorand anything else reading the file would report the wrong backend. Pass the URL and the file stays honest. It is not a secret.--no-persist-api-key— keepsMEMORIES_API_KEYout of the MCP entry insettings.json. The key comes from the environment instead, so it never lands in a config file. Do not pass--api-key: that would write the credential into both the setup script and~/.config/memories/env.
2. Set the environment variables. On claude.ai/code, click the cloud icon showing the environment's name in the row above the message box, then Add cloud environment (or the gear icon on an existing one). The dialog holds environment variables in .env format:
MEMORIES_URL=https://memory.yourdomain.com
MEMORIES_API_KEY=...
Values are copied into the VM once at session start, so edits only affect sessions you start afterwards. Note that cloud environment variables are not a secrets vault — anyone with access to the environment can read them in plaintext. Weigh that before putting a backend key there; the connector route keeps credentials out of the VM entirely.
If you do put a key in a cloud environment, use a scoped key rather than your full-access one — the backend supports restricting a key to specific source prefixes via MEMORIES_SOURCE_PREFIXES (read/search) and MEMORIES_EXTRACT_SOURCE (writes from extraction), so a leaked cloud-session key can't read or write memories outside the project it was issued for. Create it with a prefixes list rather than an admin/unrestricted key.
3. Allow the backend's domain. This is the step that silently breaks everything if skipped. Hook curls are ordinary session traffic and are subject to the environment's network policy — unlike connector traffic, which is exempt. The default Trusted level is a closed list (package registries, GitHub, the major clouds) and will not include your backend. In the same dialog, set Network access → Custom, add your host to Allowed domains (one bare domain per line; a leading *. matches subdomains), and tick "Also include default list of common package managers" so package installs keep working.
A blocked domain does not return a tidy 403 — it fails at the proxy's connect stage with no readable error, so a hook that "does nothing" is the symptom. Debug by logging curl -v output or $? from the hook rather than hunting for an error body. Changing the allowlist also invalidates the environment's cached snapshot, so its setup script re-runs on the next session.
Permission prompts if your MCP server isn't named memories. Repo-committed permissions.allow rules do apply in cloud sessions, and connector tools are not auto-approved just because you authorized the connector — every tool call, connector or not, goes through the same permission machinery. This repo's .claude/settings.json alone does not pre-approve Memories MCP tools; the npm/deprecated installer-generated user settings can pre-approve the 6 read-only memory tools for a server literally named memories, while persistent feedback via memory_is_useful remains prompt-gated. If yours is registered under a different name — a claude.ai connector you named Remote_Memories, or one surfaced under a generated ID — those rules won't match and you'll be prompted on every call. There is no wildcard for the server segment (mcp__*__memory_search is skipped with a warning and approves nothing), so this can't be automated for a name that isn't known at install time. Fix it either by running npx memories-mcp init --mcp-name YourServerName (repeatable for multiple names), or by adding mcp__YourServerName__memory_search and the other 5 read-only tools to permissions.allow yourself.
Contributors without a backend get a true no-op, not noise. Hooks gate themselves before making any network call: if no backend is configured and MEMORIES_ENABLED was never set either, the hook exits silently — no service-unreachable notice, no curl stalls, no hook.log even created. That's what makes it safe to commit .claude/settings.json project-wide; a clone without credentials just does nothing. The precedence is:
MEMORIES_DISABLEDtruthy always wins — hard off, regardless of anything else.MEMORIES_ENABLEDset explicitly —true/1/yes/onforces hooks to run even with nothing else configured (falling back tohttp://localhost:8900);false/0/no/offforces them off even with a backend configured.MEMORIES_ENABLEDunset — auto-detect: active if any supported backend source is present (MEMORIES_URL,MEMORIES_BACKENDS_FILE,~/.config/memories/backends.yaml, or a per-project.memories/backends.yaml), silent no-op otherwise. Multi-backend installs configure via those files instead ofMEMORIES_URL, so this gate must recognize all of them, not just the single-backend env var.
Set MEMORIES_ENABLED=false in your own gitignored .claude/settings.local.json (or ~/.config/memories/env) if you want to opt out explicitly instead of relying on auto-detect, or claude plugin disable --scope local memories@dk-marketplace to disable the plugin outright — pass --scope local explicitly, because a user-scoped disable cannot override the true this repo commits at project scope.
A wrong or missing API key is not silent. /health is unauthenticated, so it cannot tell a bad MEMORIES_API_KEY apart from a healthy backend — without this check, recall would just keep returning nothing, forever, with no warning. The session-start hook now detects a 401 from the /search calls it already makes (no extra round-trip) and surfaces a distinct warning naming MEMORIES_API_KEY, instead of the generic "check that the service is running" message reserved for actually-unreachable backends.
Multi-Backend Routing (Optional)
A single agent session can talk to multiple Memories instances simultaneously. This is useful when you want to:
- Dev + Prod: search both local dev and remote production, extract to dev only
- Personal + Shared: search both personal and team memories, route decisions to shared
Multi-backend is configured via YAML files and is fully backward compatible — no config file means single-backend behavior from environment variables, exactly as before.
Config locations:
- Global:
~/.config/memories/backends.yaml - Per-project:
.memories/backends.yaml(should be gitignored)
Three tiers:
- Scenario-based — pick a preset (
dev+prod,personal+shared,single) and routing is automatic - Scenario + overrides — start from a scenario, then customize routing rules
- DIY — define backends and routing rules from scratch
Quick example (dev + prod):
backends:
dev:
url: http://localhost:8900
api_key: ${MEMORIES_DEV_KEY}
scenario: dev
prod:
url: https://memory.yourdomain.com
api_key: ${MEMORIES_PROD_KEY}
scenario: prod
Config supports env var interpolation (${VAR_NAME}) for API keys and URLs.
Multi-backend works automatically with Claude Code, Codex, and Cursor because their hook paths read backends.yaml. OpenCode currently uses MCP plus plugin prompt recall and memory-tool telemetry, not shell-hook routing. OpenClaw does not yet support multi-backend (it uses skill-based extraction, not hooks).
For full setup instructions, config format, and verification steps, see the Multi-Backend Setup section in the LLM quickstart guide.
Authentication
Memories supports multiple API keys with role-based access control:
- Three tiers:
read-only(search/list),read-write(search/list + add/delete),admin(full access + key management) - Prefix scoping: keys can be restricted to specific source prefixes for tenant isolation
- Key management: create, list, update, and revoke keys via
POST/GET/PATCH/DELETE /api/keysor the Web UI (admin-only) - Backward compatible: the existing
API_KEYenv var still works as an implicit admin key
API Reference
All endpoints accept/return JSON. Auth via X-API-Key header.
Search
POST /search
{"query": "...", "k": 5, "hybrid": true, "threshold": 0.3,
"vector_weight": 0.7, "recency_weight": 0.1, "recency_half_life_days": 30,
"source_prefix": "team/project/"}
POST /search/evidence
{"query": "latest deploy target", "k": 8, "source_prefix": "team/project/"}
POST /search/batch
{"queries": [{"query": "...", "k": 5}, {"query": "...", "hybrid": true}]}
Add Memory
POST /memory/add
{"text": "...", "source": "file.md", "on_duplicate": "supersede"}
on_duplicate controls the write doctrine when the text collides with a similar existing memory: supersede (replace it; the old version is archived with a supersedes link), skip (keep the existing memory; the response reports blocked_by), or add (store unconditionally). The legacy deduplicate flag still works but is superseded by on_duplicate.
Update / Supersede
POST /memory/{id}/supersede
{"text": "corrected fact", "source": "file.md"}
Replaces memory {id} with the new text; the old version is archived (recoverable, visible in timelines) with a supersedes link. This is what the memory_update MCP tool calls. Pinned memories refuse with 409 until unpinned.
Add Batch
POST /memory/add-batch
{"memories": [{"text": "...", "source": "..."}, ...], "deduplicate": true}
Delete
DELETE /memory/{id}
DELETE /memories?source=<prefix> # Bulk delete by source prefix; returns {"count": N}
POST /memory/delete-batch {"ids": [1, 2, 3]}
POST /memory/delete-by-source {"source_pattern": "credentials"}
POST /memory/delete-by-prefix {"source_prefix": "team/project/"}
Get
GET /memory/{id}
POST /memory/get-batch {"ids": [1, 2, 3]}
Upsert / Patch
POST /memory/upsert
{"text":"...", "source":"team/project/file", "key":"entity-1", "metadata": {"owner":"team"}}
POST /memory/upsert-batch
{"memories":[{"text":"...", "source":"...", "key":"..."}]}
PATCH /memory/{id}
{"text":"optional", "source":"optional", "metadata_patch":{"tag":"v2"}}
Novelty Check
POST /memory/is-novel
{"text": "...", "threshold": 0.88}
Browse
GET /memories?offset=0&limit=20&source=filter # limit up to 5000; source uses prefix matching
GET /memories/count?source=<prefix> # returns {"count": N}
Deduplication
POST /memory/deduplicate
{"threshold": 0.90, "dry_run": true}
Index Operations
POST /index/build {"sources": ["file1.md", "file2.md"]}
GET /stats
GET /health
GET /health/ready
GET /metrics
POST /maintenance/embedder/reload
Backups
GET /backups
POST /backup?prefix=manual
POST /restore {"backup_name": "manual_20260213_120000"}
Extraction
POST /memory/extract {"messages": "...", "source": "proj", "context": "stop", "debug": true} # 202 queued
GET /memory/extract/{job_id}
GET /extract/status
Memory Relationships
POST /memory/{id}/link {"target_id": N, "type": "related"}
GET /memory/{id}/links
DELETE /memory/{id}/link/{link_id}
Conflict Detection
GET /memory/conflicts?limit=10
Events (SSE + Webhooks)
GET /events/stream # SSE stream (auth-filtered)
POST /webhooks # Register webhook
GET /webhooks
DELETE /webhooks/{id}
Search Explainability
POST /search/explain # Admin-only scoring breakdown
POST /search/evidence # Agent-facing current/older evidence packet
Quality & Metrics
GET /metrics/search-quality?period=7d
POST /search/feedback # Submit relevance feedback
GET /metrics/quality-summary?period=7d
GET /metrics/failures?type=retrieval&limit=10
Maintenance
POST /maintenance/reembed # Migrate embedding model
POST /maintenance/compact # Find similar clusters (dry-run); clusters tightened to prevent chain-connected outliers
POST /maintenance/consolidate # LLM-powered merge
Audit
GET /audit/log?limit=50&source=prefix
Full OpenAPI schema at http://localhost:8900/docs.
Future API Candidates (Swarm Scale)
POST /memory/compare(pairwise conflict scoring for concurrent agent writes)POST /memory/resolve-conflicts(policy-driven merge: latest/manual/model)POST /memory/lock+DELETE /memory/lock/{key}(explicit lock reservation APIs)POST /memory/events+GET /memory/events/stream(change feed for agent synchronization)POST /search/stream(progressive search responses for very large corpora)POST /memory/ttl(time-bound memories with auto-expiry)
MCP Tools Reference
When connected via MCP (Claude Code, Claude Desktop, Codex, Cursor), these tools are available:
Configuration
Environment Variables
Docker Compose guardrails
Default compose files now include:
mem_limit: ${MEMORIES_MEM_LIMIT:-3g}to bound container memory growthMALLOC_ARENA_MAX=2to reduce glibc arena fragmentation in multithreaded workloadsMALLOC_TRIM_THRESHOLD_=131072andMALLOC_MMAP_THRESHOLD_=131072to encourage earlier allocator release- extraction env passthrough (
EXTRACT_PROVIDER,EXTRACT_MODEL, provider keys/URL) so deploys keep extraction enabled when set in shell or.env - embedder auto-reload env passthrough with anti-loop defaults (
EMBEDDER_AUTO_RELOAD_*)
MCP Server Environment
Automatic Memory Layer
Memories supports automatic retrieval/extraction, with client-specific behavior:
- Claude Code: full 12-hook lifecycle (session start, each prompt, after response, pre-compact, post-compact, subagent start, subagent stop, tool use, tool observe, file write guard, config change, session end)
- Cursor: same 12-hook lifecycle via Third-party skills (loads from
~/.claude/settings.json) - Codex: version-aware five- or ten-event lifecycle via
~/.codex/hooks.json+ MCP/developer instructions in~/.codex/config.toml - OpenCode: MCP plus OpenCode plugin hooks for prompt-time recall context and active-search telemetry; auto-extraction is gated until reliable end-of-turn transcript access is proven
- OpenClaw: skill-driven retrieval/extraction flow
Claude Code / Cursor Hook Lifecycle
Cursor compatibility note: Cursor sends workspace_roots[] (not cwd) and transcript_path (not inline messages) in hook payloads. The hook scripts handle both formats automatically — no separate configuration needed.
Codex Lifecycle
The npm installer runs codex --version and chooses the profile without
assuming the version installed on a particular machine. Codex >= 0.146.0
gets ten events; older or unparseable versions get five:
Codex uses ~/.codex/hooks.json for these hooks and ~/.codex/config.toml for
MCP + developer instructions. The installer auto-approves six read-only tools;
memory_is_useful is a feedback write and remains prompt-gated. It does not
infer or mutate optional native Codex Memories settings.
v5.10-v5.12 reliability parity: Codex hooks use the same activation and configuration gates (including payload cwd and resolved backend files), per-backend breaker isolation and routed reachability checks, end-to-end deadlines that preserve partial results, and actionable 401 credential guidance. Materially short timeout budgets do not falsely trip a backend; ordinary network and HTTP failures still do.
Codex hook searches send client, session, and hook-invocation attribution to the usage tracker. The dashboard can filter usage by session id and retains unknown-source operations instead of silently dropping them.
Codex and native Memories coexistence
External Memories is the durable, searchable cross-client authority. Native Codex Memories is an optional local derived cache. The npm installer never sets either one. Users who want to avoid duplicate context may optionally set:
[memories]
disable_on_external_context = true
This setting is a recommendation only; it is reported when explicitly present and is not written by the installer.
OpenCode Lifecycle
OpenCode searches exact project prefixes first: opencode/{project}, claude-code/{project}, codex/{project}, learning/{project}, wip/{project}. OpenCode-authored extracted memories should use opencode/{project} when extraction is added; the first implementation does not auto-extract by default.
Quick setup
Recommended: published npm installer
npx -y memories-mcp@latest init
Auto-detects Claude Code, Codex, and Cursor (or restrict with --claude / --codex / --cursor / --generic); prompts for backend URL/API key (or --url / --api-key); checks backend health and, interactively, offers to bootstrap it with Docker (~/.config/memories/docker-compose.yml) if it's not reachable; wires hooks/skills/MCP config per target. --yes skips all prompts (including the bootstrap offer — an unreachable backend is logged and skipped, not auto-provisioned); --dry-run previews without writing. Companion commands: memories doctor (status + backend health + version check), memories update (re-wire after upgrading), memories uninstall. Windows has no bash, so init restricts to the generic target there. Other MCP clients: use the generic snippet from memories init --generic, or see GETTING_STARTED.md.
For Codex local stdio setup use npx -y memories-mcp@latest init --codex.
For direct remote OAuth use npx -y memories-mcp@latest init --codex --mcp-url https://... --yes, then run codex mcp login memories. The remote
path accepts no backend API key and skips REST health/bootstrap.
The package is published on npm (first release 2026-08-10), and the cloud setup
script uses the same npx memories-mcp@latest entry point. OpenCode and OpenClaw
remain on the legacy installer below.
Legacy: install.sh (deprecated, still works this release — removed next release)
Prerequisites:
jqandcurlinstalled (required by installer)- running Memories service (
curl -s http://localhost:8900/health | jq .) - if installing the legacy OpenCode integration, MCP deps installed:
npm --prefix ./mcp-server install
One-command auto-detect installer:
./integrations/claude-code/install.sh --auto
This detects and configures legacy targets on your machine:
- Claude Code hooks (
~/.claude/settings.json) - OpenCode MCP/plugin config (
~/.config/opencode/opencode.json) + Memories skill (~/.config/opencode/skills/memories/SKILL.md) - OpenClaw skill (
~/.openclaw/skills/memories/SKILL.md)
Cursor is supported via manual MCP config (~/.cursor/mcp.json or .cursor/mcp.json).
For Codex, use the portable npm installer above; the repo-local plugin's
$memories:setup skill is only setup guidance and never requires this checkout.
The installer writes runtime config to:
~/.config/memories/envfor hook vars (MEMORIES_URL, optionalMEMORIES_API_KEY, optionalMEMORIES_SOURCE_PREFIXES/MEMORIES_EXTRACT_SOURCEto override default source families)- repo
.envfor extraction vars (EXTRACT_PROVIDER, provider keys/URL)
Claude/Cursor read hooks also support an optional MEMORIES_SOURCE_PREFIXES env var in
~/.config/memories/env. It is a comma-separated list of source prefix templates and
defaults to claude-code/{project},codex/{project},learning/{project},wip/{project} for Claude Code and codex/{project},claude-code/{project},learning/{project},wip/{project} for Codex.
OpenCode plugin recall defaults to opencode/{project},claude-code/{project},codex/{project},learning/{project},wip/{project}.
Target only Claude, Cursor, or OpenCode (legacy path):
./integrations/claude-code/install.sh --claude
./integrations/claude-code/install.sh --cursor
./integrations/claude-code/install.sh --opencode
Target only OpenClaw:
./integrations/claude-code/install.sh --openclaw
LLM-assisted setup: Feed integrations/QUICKSTART-LLM.md to your AI assistant and it will configure everything automatically.
Extraction providers
Extraction is optional. Without it, retrieval still works.
By default, automatic write hooks do not store new memories when extraction is disabled.
If you want a degraded automatic-write mode, set EXTRACT_FALLBACK_ADD=true to enable a strict
heuristic + novelty-check fallback that writes at most a small number of high-confidence facts
when extraction is disabled or the configured provider fails at runtime (for example rate limits/timeouts).
AUDN in plain English
AUDN is the memory decision loop:
ADD: store a genuinely new factUPDATE: refine an existing memory that is close but outdated/incompleteDELETE: remove a stale/conflicting memoryNOOP: ignore non-useful or duplicate factsCONFLICT: flag when two memories directly contradict each other
Why it matters:
- cleaner memory store over time (less duplicate/stale data)
- better retrieval quality in later sessions
- less "memory drift" when decisions change
Recent extraction changes:
- Signal keyword filter removed — extraction now fires unconditionally on every response
- Extraction window widened to 4 message pairs / 8K chars (up from previous defaults)
- Assertive injection framing: recalled memories include "IMPORTANT: MUST be considered" prefix to strengthen recall adherence
Cost vs quality
- Anthropic/OpenAI extraction: small usage cost (typically around ~$0.001/turn), full AUDN quality.
- ChatGPT Subscription extraction: no additional API cost (uses your existing subscription), full AUDN quality.
- Ollama extraction: no API cost, full AUDN quality (with JSON format constraint).
- Retrieval only (
EXTRACT_PROVIDERunset): no extraction model cost. - Optional fallback writes (
EXTRACT_FALLBACK_ADD=true): add-only, heuristic extraction path (no AUDN update/delete) used when extraction is disabled or provider calls fail at runtime.
Cost control knobs
Use these to keep extraction spend bounded:
MAX_EXTRACT_MESSAGE_CHARS: hard cap on transcript size per requestEXTRACT_MAX_FACTS: limits facts considered from each extractionEXTRACT_MAX_FACT_CHARS: caps per-fact payload sizeEXTRACT_SIMILAR_TEXT_CHARSandEXTRACT_SIMILAR_PER_FACT: limit context passed into AUDN
Async extraction API
POST /memory/extract is async-first. It enqueues work and returns 202 with a job_id.
Poll GET /memory/extract/{job_id} for queued, running, completed, or failed.
If the queue is full, the API returns 429 with a Retry-After header.
When extraction is disabled and EXTRACT_FALLBACK_ADD=true, /memory/extract runs an immediate
fallback add path and still returns a job object. When extraction is configured but fails at runtime,
the queued worker also falls back to add-only mode when EXTRACT_FALLBACK_ADD=true.
Docker image targets (core / extract)
The Dockerfile publishes two runtime targets:
core(default): search/add/list endpoints, no Anthropic/OpenAI SDKsextract: includes Anthropic/OpenAI SDKs for/memory/extract
Build both images directly:
docker build --target core -t memories:core .
docker build --target extract -t memories:extract .
Use compose with either target:
# Default (core target)
docker compose up -d --build memories
# Extraction-ready target
MEMORIES_IMAGE_TARGET=extract docker compose up -d --build memories
By default, images do not bake model weights. On first run, the service downloads them into
MODEL_CACHE_DIR (/data/model-cache in Docker), so later restarts reuse the volume cache.
If you want a fully preloaded image (faster first boot, larger pull), set PRELOAD_MODEL=true:
docker build --target core --build-arg PRELOAD_MODEL=true -t memories:core .
docker build --target extract --build-arg PRELOAD_MODEL=true -t memories:extract .
Ollama uses HTTP directly and does not need the extra SDKs, so core is enough for Ollama extraction.
Extraction environment variables
Burst memory behavior
Extraction can create short-lived allocation spikes (large transcripts, large LLM JSON payloads, concurrent requests).
Mitigations built in:
/memory/extractrequest size limit (MAX_EXTRACT_MESSAGE_CHARS)- bounded in-flight extraction (
EXTRACT_MAX_INFLIGHT) - post-extract + periodic memory reclamation (
MEMORY_TRIM_ENABLED,MEMORY_TRIM_COOLDOWN_SEC,MEMORY_TRIM_PERIODIC_SEC) - optional auto-reload controller for the embedder runtime (
EMBEDDER_AUTO_RELOAD_*) - bounded AUDN payload sizes (
EXTRACT_MAX_FACTS,EXTRACT_MAX_FACT_CHARS,EXTRACT_SIMILAR_TEXT_CHARS)
Observability:
/metricsincludesembedder_reload.autoandembedder_reload.manualcounters/state- manual reload endpoint:
POST /maintenance/embedder/reload
Reference benchmark: docs/archive/benchmarks/2026-02-17-memory-reclamation.md
Uninstall
npx memories-mcp@latest uninstall
Or, for the legacy path (OpenCode/OpenClaw, or if you installed via install.sh):
./integrations/claude-code/install.sh --uninstall
Then optionally remove MEMORIES_* from ~/.config/memories/env and EXTRACT_* from repo .env.
Backup & Recovery
Memories has three layers of backup protection:
1. Auto-backup (built-in)
The service automatically saves a snapshot after every write operation. The 10 most recent auto-backups are kept in the Docker volume under data/backups/.
# List backups
curl -H "X-API-Key: $MEMORIES_API_KEY" http://localhost:8900/backups
# Create manual backup
curl -X POST -H "X-API-Key: $MEMORIES_API_KEY" http://localhost:8900/backup?prefix=manual
# Restore from backup
curl -X POST -H "X-API-Key: $MEMORIES_API_KEY" http://localhost:8900/restore \
-H "Content-Type: application/json" \
-d '{"backup_name": "manual_20260214_120000"}'
2. Scheduled local snapshots (cron)
A cron job creates timestamped copies of the Memories index every 30 minutes. Snapshots are stored outside the Docker volume (default: ~/backups/memories/) with 30-day retention.
# Install the cron job
./scripts/install-cron.sh install
# Check status
./scripts/install-cron.sh status
# Run a backup manually
./scripts/backup.sh
# Dry run (no changes)
./scripts/backup.sh --test
Environment variables (all optional, sensible defaults):
3. Off-site backup to Google Drive (optional)
If you set GDRIVE_ACCOUNT, each backup automatically uploads the latest snapshot to Google Drive as a compressed tar.gz. Uploads are throttled to once per hour. 7-day retention on Drive.
Prerequisites:
- Install gog CLI
- Authenticate:
gog auth add [email protected] --services drive - Set env var in your shell profile:
export GDRIVE_ACCOUNT="[email protected]"
Environment variables (all optional):
Manual usage:
# Setup (create Drive folder + test auth)
./scripts/backup-gdrive.sh --setup
# Upload now (skip throttle)
./scripts/backup-gdrive.sh --force
# Dry run
./scripts/backup-gdrive.sh --test
# Only clean up old backups on Drive
./scripts/backup-gdrive.sh --cleanup
Alternative: S3-compatible cloud sync
For S3/MinIO/R2 backends, build with cloud sync enabled:
ENABLE_CLOUD_SYNC=true docker compose up -d --build memories
See CLOUD_SYNC_README.md for configuration details.
Project Structure
memories/
app.py # FastAPI REST API
memory_engine.py # Memories engine (search, chunking, BM25, backups)
onnx_embedder.py # ONNX Runtime embedder (replaces PyTorch)
llm_provider.py # LLM provider abstraction (Anthropic/OpenAI/ChatGPT Subscription/Ollama)
llm_extract.py # Extraction pipeline with AUDN
chatgpt_oauth.py # ChatGPT OAuth2+PKCE token exchange helpers
key_store.py # SQLite-backed API key store (SHA-256 hashing)
event_bus.py # Event-driven architecture (SSE, webhooks)
audit_log.py # Append-only audit trail
qdrant_store.py # Qdrant vector store adapter
usage_tracker.py # Search quality and extraction metrics
auth_context.py # Request-scoped role and prefix enforcement
memories_auth.py # CLI auth tool (python -m memories auth chatgpt/status)
__main__.py # Entry point for python -m memories
Dockerfile # Multi-stage Docker build (core/extract targets)
pyproject.toml # Python dependencies (uv)
uv.lock # Locked dependency resolutions
docker-compose.snippet.yml
docs/
api.md # Complete REST API reference
architecture.md # System architecture and runtime flows
decisions.md # Key design decisions and tradeoffs
deployment.md # Self-hosted deployment guide
api-coverage.md # API/MCP/CLI coverage matrix
benchmarks/ # Reproducible benchmark notes
mcp-server/
index.js # MCP server (wraps REST API as tools)
package.json
scripts/
backup.sh # Cron backup (local snapshots)
backup-gdrive.sh # Optional Google Drive upload
install-cron.sh # Cron job installer
webui/
index.html # Memory browser entry page (/ui)
styles.css # UI styling
app.js # Browser-side pagination/filter logic
integrations/
claude-code/
install.sh # Auto-detect installer (Claude/Codex/OpenCode/Cursor/OpenClaw)
hooks/ # Claude Code 12-hook scripts + hooks.json
_lib.sh # Shared hook utilities (logging, health check)
memory-rehydrate.sh # PostCompact rehydration hook
memory-observe.sh # PostToolUse observability hook
memory-guard.sh # PreToolUse MEMORY.md write guard
memory-subagent-capture.sh # SubagentStop extraction hook
memory-config-guard.sh # ConfigChange settings watchdog
response-hints.json # Response hint patterns
codex/
memory-codex-notify.sh # Legacy Codex notify hook (compatibility/manual fallback)
opencode/
README.md # OpenCode setup and lifecycle notes
plugin/
memories.js # OpenCode prompt recall + telemetry plugin
claude-code.md # Claude Code guide
openclaw-skill.md # OpenClaw SKILL.md
QUICKSTART-LLM.md # LLM-friendly setup guide
.agents/
plugins/
marketplace.json # Repo-local Codex plugin catalog
plugins/
memories/
.codex-plugin/
plugin.json # Repo-local Codex plugin manifest
skills/
memories/
SKILL.md # Shared Memories discipline skill for Codex plugin
setup/
SKILL.md # Codex bootstrap skill that runs the canonical installer
tests/
test_memory_engine.py # Memory engine tests
test_llm_provider.py # LLM provider tests (incl. ChatGPT Subscription)
test_chatgpt_oauth.py # OAuth PKCE + token exchange tests
test_memories_auth.py # CLI auth tool tests
test_llm_extract.py # Extraction pipeline tests
test_extract_api.py # API endpoint tests
test_web_ui.py # Web UI route/static tests
skills/
memories/
SKILL.md # Claude Code skill for memory discipline
eval/
benchmarks.py # Benchmark suite runner
scenarios/benchmark/ # 6 benchmark scenarios
__main__.py # CLI entrypoint (python -m eval)
models.py # Pydantic data models (Scenario, EvalReport, etc.)
loader.py # YAML scenario loader
scorer.py # Deterministic rubric scorer
judge.py # LLM-as-judge for non-deterministic rubrics
memories_client.py # Memories API client for eval runner
cc_executor.py # Claude Code executor with project isolation
runner.py # Orchestrates with/without-memory runs
reporter.py # JSON reporter and summary formatter
config.yaml # Default eval configuration
scenarios/ # YAML test scenarios by