Cortex
README.md
<p align="center">
<img src="docs/assets/og.png" alt="Cortex — memoria para tus agentes IA" width="640">
</p>
<h1 align="center">Cortex</h1>
<p align="center">
<b>Local-first persistent memory for AI coding agents.</b><br>
MCP server + HTTP API · SQLite on your disk · hybrid search (FTS5 + local embeddings) · no cloud required.
</p>
<p align="center">
<a href="#quickstart-2-minutes">Quickstart</a> ·
<a href="#plug-it-into-your-agent">Clients</a> ·
<a href="#mcp-tools">Tools</a> ·
<a href="docs/self-hosting.md">Self-hosting</a> ·
<a href="docs/http-api.md">HTTP API</a>
</p>
---
## The problem
Your coding agent solves a nasty bug on Tuesday. On Wednesday it opens a fresh context
window 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 same
symptom 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](#mcp-tools)).
- **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_hint` takes 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_forget` previews (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 or
downloads a prebuilt binary on install — no other system dependency.
```bash
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:
```bash
npm install -g github:gonzalonicolasr/cortexmem
cortexmem --version
```
Smoke test it as a plain CLI:
```bash
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`.
<details open>
<summary><b>Claude Code</b></summary>
```bash
claude mcp add cortex -- node /absolute/path/to/cortexmem/bin/memoria.mjs mcp
claude mcp list | grep cortex # → ✓ Connected
```
</details>
<details>
<summary><b>Codex CLI</b> (<code>~/.codex/config.toml</code>)</summary>
```toml
[mcp_servers.cortex]
command = "node"
args = ["/absolute/path/to/cortexmem/bin/memoria.mjs", "mcp"]
```
</details>
<details>
<summary><b>Cursor / Windsurf / any client with a JSON MCP config</b></summary>
```json
{
"mcpServers": {
"cortex": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/cortexmem/bin/memoria.mjs", "mcp"]
}
}
}
```
</details>
<details>
<summary><b>pi</b> (needs an MCP extension — pi has no built-in MCP)</summary>
Install an MCP extension for pi and point it at the same command/args pair as the JSON
example above. For the HTTP transport, see [`docs/self-hosting.md`](docs/self-hosting.md).
</details>
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. Copy
[`CLAUDE.md`](CLAUDE.md) (the memory protocol) into your agent's instruction file
(`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, pi's `AGENTS.md`, ...). It's ~40 lines and tells
the 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 and
front it with a reverse proxy:
```bash
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`](docs/http-api.md).
systemd unit, bearer auth, embeddings backfill, reflection cron and backups:
[`docs/self-hosting.md`](docs/self-hosting.md).
> ⚠️ The HTTP server trusts the `X-Workspace-Id` header (multi-tenant design: an upstream
> proxy validates the user and injects it). **Never bind it to a public interface without
> `MEMORIA_AUTH_TOKEN` + a proxy in front.**
## Semantic search (optional)
```bash
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` and
`memoria_recall` become hybrid (FTS5 + KNN fused with RRF); with it off, everything still
works as pure keyword search. Non-English memories: set
`MEMORIA_EMBEDDING_MODEL=Xenova/paraphrase-multilingual-MiniLM-L12-v2` **before** the
backfill (same 384 dims) — see [semantic search](docs/self-hosting.md#4-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 of
logical text, 50 projects, 5 active sessions, 32 KB per memory. For a personal local
install, lift them:
```bash
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 backwards
compatibility with existing installs.
El servidor MCP publica y valida el mismo contrato de entrada antes de ejecutar cada tool:
campos requeridos, tipos, enums, enteros y límites numéricos. Los argumentos inválidos se
rechazan con `isError: true` y un mensaje `Error: ...`; no se coaccionan valores ni se
eliminan propiedades extra. El resource de contexto codifica el nombre completo del proyecto
con `encodeURIComponent`, por lo que espacios y caracteres como `/`, `?`, `#` y `%` hacen
round-trip sin alterar el nombre.
## 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 anything
you 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
```bash
npm test # vitest, 216 tests
npm run test:watch
```
Multilingual embedding tests are gated behind `MEMORIA_TEST_MULTILINGUAL=1` so the normal
suite never downloads a model. Changelog: [`CHANGELOG.md`](CHANGELOG.md).
## Hosted (optional)
If you'd rather not run anything, the same engine is hosted at
[cortexmem.com](https://cortexmem.com): sign up, copy the `cc_...` API key from the panel,
and point your client at the HTTP endpoint instead of the local command:
```bash
claude mcp add cortex https://cortexmem.com/api/cortex/mcp \
--transport http --header "Authorization: Bearer cc_YOUR_KEY"
```
```toml
# ~/.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](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues