Project memory for AI agents. When an agent pushes to git, it registers what it did and why — not the diff, the reasoning. Any other agent on the project reads that context via MCP before touching the same code.
No external model API required. The agent writing the change is the model.
Two processes, split by what each can own:
core/— Go (Fiber v3 + pgx). One long-lived daemon per machine. Owns PostgreSQL and the full-text search. Plain HTTP+SQL — it never runs git, never speaks MCP, never calls a model. Auth via static Bearer token.mcp/— Node (MCP SDK). The per-agent shell, spawned over stdio by Claude Code, one process per conversation. Speaks MCP, runsgitlocally in the repo (the part Go can't do), forwards tool calls to the Core. Holds no state.
In register_change: the agent writes the semantic fields (what/why/decisions/
risks); the MCP layer adds the mechanical ones from git (sha/branch/files/diff);
the Core upserts by (repository_id, sha) and regenerates the FTS vector.
# 1. Postgres + Redis
docker compose up -d
# 2. Core (Go) — applies the embedded schema on startup
cd core && go run ./cmd/server # listens on 127.0.0.1:8742
# Optional multi-agent notifications:
# CORTEX_REDIS=redis://127.0.0.1:6380 go run ./cmd/server
# 3. MCP server (Node)
cd mcp && npm install && npm run build
# 4. Wire a repo (writes .cortex.yml, merges .mcp.json without clobbering other servers)
go run ./core/cmd/cortex init --project myproj --token <token>
# or by hand: .mcp.json wires `node mcp/dist/index.js` with
# CORTEX_SERVER, CORTEX_TOKEN, CORTEX_PROJECT- stdio (default):
node mcp/dist/index.js— spawned per-agent by Claude Code. - Streamable HTTP (remote / CI):
npm run start:http(portCORTEX_MCP_HTTP_PORT, default 9000, at/mcp; optionalCORTEX_MCP_TOKENbearer gate). SSE is not used — it was removed server-side from the MCP SDK.
All semantic fields (what/why/decisions/risks, knowledge, features, ADRs) are stored in English, regardless of the conversation language. Cortex is a tool for agents, so a single language keeps the FTS (and any future embedding model) on one English-tuned index — mixing languages degrades search. This is enforced where the agent actually reads it: the MCP tool schema descriptions instruct "write in English". (A Claude Code hook can't force a tool call, so the schema is the right enforcement point, not a hook.)
When CORTEX_REDIS is set, Core publishes a JSON ChangeEvent to
cortex:{project}:changes after every registered change, so other agents/services
on the project can react. No Redis configured → publishing is a no-op.
Search is FTS by default. When CORTEX_OLLAMA is set (e.g.
http://127.0.0.1:11434), Core also embeds each change/knowledge entry with a
local model (nomic-embed-text, 768-dim, via Ollama — no external API) and
fuses keyword + vector rankings with Reciprocal Rank Fusion. This surfaces
relevant prior work even when the query shares no keywords with it. docker compose up provisions Ollama and pulls the model automatically; embeddings are
generated off the write path and search degrades to FTS if Ollama is down.
CORTEX_OLLAMA=http://127.0.0.1:11434 CORTEX_REDIS=redis://127.0.0.1:6380 \
go run ./core/cmd/server.claude/skills/ ships two skills so agents use Cortex without being told:
- cortex-recall — consult context (get_project_context / search_knowledge / check_feature_exists) before starting work.
- cortex-log — record what & why (register_change / add_decision / upsert_feature / add_knowledge) after committing.
Write: register_change, add_knowledge, add_decision, upsert_feature.
Read: search_knowledge (commits + knowledge), get_recent_changes,
get_project_context, get_patterns, check_feature_exists.
Resource: cortex://context/{project} (mountable project summary).
Prompts: analyze-impact, whats-left-for-feature.
Smoke tests of the full cycle live in mcp/scripts/e2e*.mjs. The Go store has
an integration test gated on CORTEX_TEST_DSN:
CORTEX_TEST_DSN="postgres://cortex:cortex@127.0.0.1:5433/cortex?sslmode=disable" \
go test ./core/internal/store -run TestStore -v