Cortex
Local-first persistent memory for AI coding agents. MCP server + HTTP API · SQLite on your disk · hybrid search (FTS5 + local embeddings) · no cloud required.
Quickstart · Clients · Tools · Self-hosting · HTTP API
The problem
Your coding agent solves a nasty bug on Tuesday. On Wednesday it opens a fresh contextwindow and has no idea that bug ever existed. You paste the same explanation again.
Cortex gives the agent a memory it writes to and reads from by itself, through MCP:structured memories (bug_fix, decision, discovery, pattern, preference, ...),scoped per project, ranked by relevance, decayed over time, and re-surfaced when the samesymptom shows up again.
Everything lives in a single SQLite file on your machine (~/.memoria/memoria.db).No account, no API key, no telemetry.
What's in the box
- 20 MCP tools — save / search / context / recall / hint / feedback / sessions / reflections / forget (full list).
- Hybrid retrieval — SQLite FTS5 keyword search fused with vector KNN via Reciprocal Rank Fusion. Embeddings are optional and run locally (
@xenova/transformers, 384-dim MiniLM, ~22 MB, CPU). - Outcome-aware trust — the agent reports back whether a surfaced memory helped, was stale or misled it (
memoria_feedback); trust scores re-rank future results. - Proactive hints —
memoria_hinttakes the upcoming tool call / prompt / file path and returns up to 3 short hints to inject before acting. - Reflections — a CPU-only clustering pass groups related memories; your agent's LLM synthesizes the meta-lesson (Cortex never calls an LLM itself).
- Decay & forget — relevance decays,
memoria_forgetpreviews (dry-run by default) and soft-deletes the floor. - Privacy by default — API keys, PATs, JWTs, SSH keys and
<private>...</private>blocks are stripped before anything is written to disk. - Ops-ready — structured JSON logs, Prometheus metrics at
/api/metrics, quotas, optional bearer auth, multi-tenant workspaces.
Quickstart (2 minutes)
Requirements: Node >= 20 (verified on 22 and 26), git. better-sqlite3 compiles ordownloads a prebuilt binary on install — no other system dependency.
git clone https://github.com/gonzalonicolasr/cortexmem.git
cd cortexmem
npm install
npm test # optional: 216 tests, ~1s
Or install it without cloning — you get a cortexmem command on your PATH:
npm install -g github:gonzalonicolasr/cortexmem
cortexmem --version
Smoke test it as a plain CLI:
node bin/memoria.mjs save "Fix hydration bug" \
--type bug_fix --what "moved the fetch out of useEffect" \
--project demo --learned "SSR/CSR mismatch, not a race condition"
node bin/memoria.mjs search hydration --project demo
node bin/memoria.mjs stats
That's it — the database was created at ~/.memoria/memoria.db on first write.
Plug it into your agent
Cortex speaks MCP over stdio. If you installed globally, the command is cortexmem mcp;from a clone, use node with the absolute path to bin/memoria.mjs.
claude mcp add cortex -- node /absolute/path/to/cortexmem/bin/memoria.mjs mcp
claude mcp list | grep cortex # → ✓ Connected
Codex CLI (~/.codex/config.toml)
[mcp_servers.cortex]
command = "node"
args = ["/absolute/path/to/cortexmem/bin/memoria.mjs", "mcp"]
Cursor / Windsurf / any client with a JSON MCP config
{
"mcpServers": {
"cortex": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/cortexmem/bin/memoria.mjs", "mcp"]
}
}
}
pi (needs an MCP extension — pi has no built-in MCP)
Install an MCP extension for pi and point it at the same command/args pair as the JSONexample above. For the HTTP transport, see docs/self-hosting.md.
Restart the client after editing its config — Codex and Claude Code do not re-read it hot.
Teach the agent to actually use it
Tools alone are not enough: the agent has to know when to write. CopyCLAUDE.md (the memory protocol) into your agent's instruction file(CLAUDE.md, AGENTS.md, .cursorrules, pi's AGENTS.md, ...). It's ~40 lines and tellsthe agent to save automatically after bug fixes, decisions, discoveries and config changes,and to call memoria_context at session start.
CLI
memoria mcp Start the MCP server (stdio)
memoria serve [port] Start the HTTP API (default 7437, loopback-only)
memoria save <title> [flags] Save a memory
memoria search <query> Full-text search
memoria context [project] Print the project context block
memoria recent [flags] Recent memories
memoria stats Counts by type / project
memoria projects List projects
memoria decay Apply relevance decay
Flags: --project --type --limit --what --why --where --learned --topic.
Server mode
Want one memory shared by every machine / agent in your homelab? Run the HTTP API andfront it with a reverse proxy:
MEMORIA_HOST=127.0.0.1 MEMORIA_AUTH_TOKEN=$(openssl rand -hex 24) \
node bin/memoria.mjs serve 7437
curl -s localhost:7437/api/health
Endpoint reference: docs/http-api.md.systemd unit, bearer auth, embeddings backfill, reflection cron and backups:docs/self-hosting.md.
⚠️ The HTTP server trusts the
X-Workspace-Idheader (multi-tenant design: an upstreamproxy validates the user and injects it). Never bind it to a public interface withoutMEMORIA_AUTH_TOKEN+ a proxy in front.
Semantic search (optional)
npm install @xenova/transformers # already an optionalDependency
export MEMORIA_SEMANTIC_SEARCH=1
node bin/backfill-embeddings.mjs # embed existing memories
The model downloads once (~22 MB) and runs on CPU. With the flag on, memoria_search andmemoria_recall become hybrid (FTS5 + KNN fused with RRF); with it off, everything stillworks as pure keyword search. Non-English memories: setMEMORIA_EMBEDDING_MODEL=Xenova/paraphrase-multilingual-MiniLM-L12-v2 before thebackfill (same 384 dims) — see semantic search.
Environment variables
| Variable | Default | What it does |
|---|---|---|
MEMORIA_DATA_DIR |
~/.memoria |
Directory holding memoria.db |
MEMORIA_DB_PATH |
— | Explicit DB file (wins over DATA_DIR; :memory: supported) |
MEMORIA_PROJECT |
auto-detected from cwd | Override project detection |
MEMORIA_WORKSPACE_ID |
1 |
Workspace used by CLI/stdio |
MEMORIA_PORT / MEMORIA_HOST |
7437 / 127.0.0.1 |
HTTP bind |
MEMORIA_AUTH_TOKEN |
— | If set, HTTP requires Authorization: Bearer <token> (except /api/health) |
MEMORIA_SEMANTIC_SEARCH |
off | 1 enables embeddings + hybrid search |
MEMORIA_EMBEDDING_MODEL |
Xenova/all-MiniLM-L6-v2 |
Any 384-dim feature-extraction model |
MEMORIA_EMBEDDING_CACHE_DIR |
transformers default | Where model files are cached |
MEMORIA_REDACT_ON_READ |
off | 1 also redacts on the way out, not just on write |
MEMORIA_UNLIMITED_WORKSPACES |
— | CSV of workspace ids exempt from quotas — set it to 1 for personal self-hosting |
Quotas
Defaults are sized for the multi-tenant hosted deployment: 1 000 active memories, 10 MB oflogical text, 50 projects, 5 active sessions, 32 KB per memory. For a personal localinstall, lift them:
export MEMORIA_UNLIMITED_WORKSPACES=1 # workspace 1 = the CLI/stdio default
MCP tools
| Tool | Use it for |
|---|---|
memoria_save |
Persist a structured memory (title, type, what, why, where_at, learned, topic_key) |
memoria_search |
Hybrid/keyword search |
memoria_context |
Project context block; can open a session in the same call |
memoria_recall |
"Have I seen this error before?" — symptom → past fixes |
memoria_hint |
Proactive pre-tool-call hints (≤3, short) |
memoria_feedback |
Report helped / stale / misleading → adjusts trust |
memoria_reflections_pending · _complete · _dismiss |
Meta-lesson synthesis loop |
memoria_forget |
Hygiene: decay preview + soft-delete floor |
memoria_session_start · _end |
Session lifecycle with structured summary |
memoria_update · _delete · _timeline · _recent |
Memory maintenance & browsing |
memoria_stats · _projects · _project_describe |
Introspection & project metadata |
memoria_save_prompt |
Store what the user asked, verbatim |
Tool names keep the memoria_ prefix (the project's original name) for backwardscompatibility with existing installs.
Data, privacy, backups
- One SQLite file (WAL mode). Back it up with
sqlite3 ~/.memoria/memoria.db ".backup out.db". - Secrets are stripped before the row is written: AWS keys, GitHub/GitLab PATs,OpenAI/Anthropic/Slack/Google/Stripe/Cloudflare keys, JWTs, SSH private keys, and anythingyou wrap in
<private>...</private>. It's a safety net, not a licence to paste secrets. - Nothing leaves your machine unless you run the HTTP server and expose it.
Development
npm test # vitest, 216 tests
npm run test:watch
Multilingual embedding tests are gated behind MEMORIA_TEST_MULTILINGUAL=1 so the normalsuite never downloads a model. Changelog: CHANGELOG.md.
Hosted (optional)
If you'd rather not run anything, the same engine is hosted atcortexmem.com: sign up, copy the cc_... API key from the panel,and point your client at the HTTP endpoint instead of the local command:
claude mcp add cortex https://cortexmem.com/api/cortex/mcp \
--transport http --header "Authorization: Bearer cc_YOUR_KEY"
# ~/.codex/config.toml
[mcp_servers.cortex]
url = "https://cortexmem.com/api/cortex/mcp"
[mcp_servers.cortex.http_headers]
Authorization = "Bearer cc_YOUR_KEY"
Self-hosting stays fully featured — the hosted tier adds the web panel and the brain graph,not the memory itself.
License
MIT © Gonzalo Rocca — see LICENSE.