Notes · architecture

Where Claude’s memories go: Cortex, traced through its source

Every Claude Code session leaves two kinds of traces in Cortex. Memories go into one database table, memories, each row with a 384-dimension vector and a heat value that the database recomputes at every read. Decisions get an identity of their own: a numbered ADR file on disk that the database only points to. This note follows both, from the hook that captures them to the recall that brings them back into context. Every statement links to the lines of the v4.22.0 release that implement it, so you can check each one instead of taking it on trust.

StoragePostgreSQL with pgvector, or SQLite with sqlite-vec and FTS5; the same memories schema on both4
Embeddingsall-MiniLM-L6-v2, 384 dimensions, on CPU4
Tools52 MCP tools, 55 with the optional upstream integrations; a test holds every tool’s client schema equal to its handler’s20
RetrievalLongMemEval-S R@10 0.980, MRR 0.906 · LoCoMo R@10 0.889, MRR 0.78019

The retrieval figures come from one run of benchmarks/reproduce.sh --no-regression on 15 September 2026, in an ephemeral PostgreSQL container, on the code released as v4.22.0; they are retrieval scores, not answer accuracy.

Exhibit 01 · architecture

Two doors, one store

Nothing enters Cortex except through one of two doors. The plugin hooks fire on their own at fixed points of a session and talk to the infrastructure layer directly3. The MCP tools (remember, recall, wiki_adr and the rest) go through the server, where only the handlers may combine the pure core with infrastructure1. The core performs no I/O: environment flags, the reranker’s model cache and the wiki filesystem reach it through seams that raise when unwired, and one function wires them all at every process entry point2.

Claude Code sessionyou, Claude,the subagentsDOOR 1 · AUTOMATICPlugin hooksmcp_server/hooks/7 session eventswire_composition_root()DOOR 2 · EXPLICITCortex MCP serverserver · tool_registry_*handlers/ · compositioncore/pure, no I/Oshared/stdlib onlyinfrastructure/pg_store · sqlite_storeembedding_engineMiniLM-L6-v2, 384 dOPT-INPostgreSQL cortexmemories · entitiesrelationshipswiki.* · checkpointspgvector, pg_trgmSQLite memory.dbplugin install defaultsqlite-vec · FTS5~/.claude/methodology/wiki/**/*.mdprofiles.jsonsession-log.jsonAuto-memory~/.claude/projects/*/memory/*.mdeventstool callsimportscomposesSQLfilesscanner: read for profiles, never copied into memories
Backend. A plugin install runs on SQLite unless a PostgreSQL URL or the installer’s marker says otherwise; an explicit DATABASE_URL that cannot be reached raises instead of falling back silently4. The dashed edge is read-only: Claude Code’s own memory files feed the cognitive profile and are never copied into the store18.
Exhibit 02 · hooks

What the hooks read and write

You only see part of the work: the “Cortex Memory Context” block at the top of a session5. Six other hooks read or write without asking. One, decision_gate, never touches the database: it refuses an edit that adds a long run of comment prose, because decision prose belongs in the wiki6.

SESSION TIMESessionStartsession_startONCE AT STARTUserPromptSubmitauto_recallEVERY PROMPTPreToolUsedecision_gateBEFORE EDIT|WRITEPostToolUsepost_tool_captureAFTER EVERY TOOLSubagentStartagent_briefingEVERY SUBAGENTNotificationcompaction_checkpointON COMPACTIONSessionEndsession_lifecycleONCE AT ENDinjects: anchored,team decisions,hot ≥ 0.4 (8 max)fast full text,protected first“Cortex context”blocks (exit 2) at≥ 8 comment linesin a rowNO DATABASEINSERT, classwrite_class=autotag auto-capturedbriefing: itsagent_context,then decisionsINSERT intocheckpointsbefore compactionprofile + log,then consolidationby turn countPostgreSQL cortexmemories · checkpoints · entities · relationships · prospective_memories · injection_receiptsrecall writes too: every injected memory leaves an injection receiptprofiles.jsonsession-log.jsonmethodology/
Automatic capture. post_tool_capture keeps the output of Edit, Write, Bash, MultiEdit and NotebookEdit, and only file paths for Read, Glob and Grep; CORTEX_CAPTURE_MODE accepts full, writes-only or off6. The save before compaction goes through the “compacted” notification; SessionEnd consolidates in proportion to the session’s length, and SessionStart spawns a background cycle that also maintains the wiki7.
Exhibit 03 · write

A memory has to get past a novelty gate

Storage has a gatekeeper: it keeps what contradicts its expectations. Four novelty signals are weighted and compared with a threshold of 0.48. The gate mostly filters automatic capture: a deliberate remember, the default class, bypasses it, as do decisions, errors and an important tag9. A decision written deliberately under an agent topic, from a trusted origin, is also marked global so the whole team of agents sees it; a fetched page or an automatic capture that merely contains a decision cue is not10.

bypass: deliberate (default) · decision · error · important tag · forceInputremember(…)content hardenedclass, origindomain: git rootSignalsimportanceentities (regex, code)MiniLM embedding384 d, L2-normalisedvalenceNovelty gate0.40 embedding0.25 entities0.20 temporal0.15 structuralstores if score ≥ 0.4Curation3 nearest neighbours≥ 0.85 → merge≥ 0.60 → linkelse → createconflict → supersedeScope + INSERTstage = labileheat = h₀+0.3·scoreprotected if decisionglobal if team decisionor cross-project signalAfter insertentity graphprospective triggerssynaptic taggingengram slotwiki page if classifiedscore < thresholdreject below_thresholdnear duplicateUPDATE in place
The threshold is not fixed. After 20 samples it drifts per domain toward 50 % acceptance, bounded between 0.05 and 0.95. Temporal novelty is 1 − e−h/24; embedding novelty is one minus the highest similarity among the five nearest neighbours8.
Exhibit 04 · row

One row of the memories table

A row carries much more than its text. The most misleading column is heat: no heat column is stored. The database keeps heat_base and the time it was set, and the SQL function effective_heat() applies the decay when the row is read; protected and anchored rows skip it11.

Content and vectors

content
the text, hardened on input
embedding
vector(384), cosine index
content_tsv
generated full text
tags
JSONB: auto-captured, adr…

Scope

domain
project, from the git root
directory_context
working directory
agent_context
the agent_topic argument
is_global
visible from every project

Thermodynamics

heat_base
0–1, where decay starts
heat_base_set_at
the decay clock
no_decay
turns decay off (anchors)
surprise_score
the gate’s novelty score

Life cycle

store_type
episodic by default, or semantic
consolidation_stage
labile when written
access_count
+1 on every recall
is_protected
decision or anchor

Provenance

write_class
auto · deliberate · derived · mechanical
capture_origin
deliberate · local_action · network · unknown
source_attribution
who said it
tag prov:<grade>
provenance grade

Supersession

supersedes_id
the row it replaces
superseded_by_id
the row replacing it
current_memories
view hiding replaced rows
compressed
original_content kept
labile0–1 h · decay ×2.0early_ltp1–6 h · decay ×1.2late_ltp6–24 h · ×0.8 · floor 0.05consolidated> 24 h · ×0.5 · floor 0.10CASCADECASCADECASCADEreconsolidatingfloor 0.05mismatched recall≥ threshold + 0.3·stabilityrestabilisationEFFECTIVE HEAT00.05recall default min_heat0.4“Hot Memories” at start1.0anchor: heat_base 1.0 and no_decay
Around the row. entities and relationships form the knowledge graph, linked to memories through memory_entities; triggers live in prospective_memories, rules in memory_rules, saves in checkpoints. On SQLite, vectors live in a vec0 virtual table and full text in FTS511. Stage durations, decay multipliers and floors are in the cascade table12.
Exhibit 05 · read

Recall is also a write

A recall first looks for an exact identity (ADR-0042, a memory id), then fuses five signals in a single database function17. On PostgreSQL the signals are max-normalised and summed with per-intent weights; on SQLite the fusion is rank-based, w/(k + rank), without the trigram signal13. Hopfield completion, hyperdimensional similarity and spreading activation rerank the result, then a cross-encoder has the last word14. The memories that come back do not leave untouched: their heat, their counters and the graph edges between their entities change on the way15.

Queryrecall(query, …)ADR-NNNN or an id?→ exact lookup,before any fusionIntentregex classificationvector 1.0fts 0.5heat 0.3ngram 0.3adjusted per intentSQL fusionpgvector cosinets_rank_cdpg_trgm trigramseffective_heat()recencyweighted sumReranksfamiliarityHopfield · HDCspreading activationemotion, moodRRF k = 60FlashRankms-marco-MiniLM-L-12-v20.30·fusion+ 0.70·cross-enc.Answerneuro-symbolicrules“lost in themiddle” orderinjection receiptwritesSIDE EFFECTS ON THE RECALLED ROWSaccess_count +1replay_count +1heat reconsolidation:+0.02 · +0.05×emotion · −0.10Hebbian co-activation:entity edges strengthenedprotected rows are not reconsolidated
unified_search runs the same recall, adds wiki pages and code, and fuses them by RRF k = 60, so it too updates the memories it returns.
Exhibit 06 · decisions

A decision has an identity: ADR-NNNN

The wiki page id is the only decision index. Code keeps a pointer, and recall resolves that pointer before any fuzzy search17. A decision can take three paths; each leaves its trace in a different place and comes back a different way16.

PATHWHERE IT LIVESHOW IT COMES BACKA decision“we keep X,because Y”wiki_adr(…)next number, under lockwrites the page andrewrites the decision index~/.claude/methodology/wiki/adr/NNNN-slug.mdindex: ADR-NNNN → file pathwith project_root: <repo>/wiki/adr/<project>/recall("ADR-0042")exact identity first:the index gives the file,no fuzzy searchremember(…)decision content detectedbypasses the gatedeliberate + agent_topicmemoriesis_protected = TRUEis_global = TRUE (team)no decay, no compressionADR pointer: class mechanicalTeam Decisionsat SessionStart, 3 at most:protected ∧ is_global∧ agent_context ≠ ''and not supersededCode editdecision_gaterefuses ≥ 8 commentlines in a rowYour repositorythe source file keeps one line:# source: ADR-NNNNnot counted in the limit of 8Reading the codethe pointer leads to the page;decision prose stays inthe wiki, not in the codepointer
Team Decisions. A decision stored by remember becomes protected automatically. Since v4.22.0 a deliberate one written under an agent topic is also global, which is the condition the SessionStart block reads; rows stored before the fix are backfilled once10. With project_root, wiki_adr writes the ADR into the repository and nothing into the database16.

Four memories, side by side

What Claude learns in a session does not land in one place. Claude Code has its own file memory, separate from Cortex; Cortex reads it for the cognitive profile and never copies it into the store18.

StoreLocationWritten byRead backDecay
Cortex memoriesPostgreSQL or SQLitethe hooks and rememberat session start, on every prompt, on recallyes, computed at read time; protected rows exempt
Wiki ADR~/.claude/methodology/wiki/adr/wiki_adrrecall on an exact ADR-NNNNno, it is a file
Claude Code auto-memory~/.claude/projects/<project>/memory/Claude, through file writesevery session: Claude Code loads MEMORY.mdno
CLAUDE.mda “Memory Insights” section between markerssync_instructionsevery session, with CLAUDE.mdno

Cortex · source on GitHub · Start a pilot