Finding signal on Twitter is more difficult than it used to be. We curate the best tweets on topics like AI, startups, and product development every weekday so you can focus on what matters.
how does Claude Code keep track of user preferences, manage it's memory or choose what to keep and what to forget? the leaked source code is here to tell us.
yesterday, @himanshustwts wrote a post about the overall architecture of Claude Code's memory system. I'm here to go more into detail about it.
Why Memory?
because it's a critical part of a an agent's both identity and performance. a great deal of my work at @CamelAIOrg has been about memory, so naturally I'm curious to know if Claude's approach to memory is fundamentally different.
Claude's five-layer Memory Architecture
Claude doesn't have a memory system. it has five, each operating with a different purpose, cost, and end-user.
The conversation history is the most trivial one. it's a jsonl of all the user prompts and Claude's responses in a conversation which you can export this memory by the /export command. it's capped at 100 entries per project. Large pastes are hashed and stored separately to keep the log lean. COST: ZERO.
Session memory is a structured markdown memory that's built and updated by a background agent WHILE your conversation is growing. its end-consumer isn't the user, but it's a compaction strategy. currently when the conversation grows, once it reaches ~85% of the context window, your conversation gets summarized. with the session memory, this would happen on the go. however, this is gated behind tengu_session_memory, one of the many mysterious Claude tengu flags. i'm not sure this is a released feature, or a future one. however, it exists in the code.
CLAUDE.md which you must be familiar with. this is the human-controlled level of coding style, preferences, things to keep in memory, etc. When you start a session with Claude, the contents of your Claude.md files get added right away. multiple Claude.md files can be loaded as Claude traverses the hierarchical folder structure to discover them.
Auto-memory is what we mean when talking about Claude Code's memory. This is where Claude Code learns about users from multiple conversations, things like their role, preferences, project context, and pointers to external systems. These are stored in `~/.claude/projects/<slug>/memory/`.
The focus of this article is on Auto-memory. These are pretty much all of the memory modules Claude keeps on you, all local and visible to the user.
Writing Memory
for better understanding, I separated what's related to writing memory and retrieving + using memory.
How is Auto-Memory managed
Claude's auto-memory isn't sloppy or append-only, it's pretty well-managed. it goes through three management phases.
Per-turn extraction
a background agent goes through the last N messages, decides what's worth remembering, and then decides either to add new memory files or update existing ones.
it receives all the memories before it starts its work, this is to make sure it updates the existing memories rather than create new duplicate ones and explore the local memory.
the writing procedure is also strict, as a new topic file is created for memory, a reference/index of them is recorded in a MEMORY.md.
This is a good approach if you have multiple local files for context, to keep a table of content + a one line summary, this would halp the agent to later find the right memory file.
→ There are Four memory types 'user' | 'feedback' | 'project' | 'reference' these would help the agent to later retrieve the right memory, essentially a label for the selection agent.
user: who you are (role, expertise, preferences) feedback: how you want Claude to work (corrections and confirmed approaches) project: what's happening that code/git can't tell you (deadlines, decisions, motivations) reference: where to look outside the repo (Linear boards, Slack channels, dashboards)
The Forked Agent Pattern
“doesn't all this background work slow things down?”
no. the extraction agent runs as a “forked subagent” that shares the parent conversation's prompt cache. the system prompt prefix is between parent and fork, so most input tokens are cache hits. the user never waits for memory operations.
the fork is also sandboxed. it can read anything on the filesystem, but it can only write to memory paths (`isAutoMemPath()`).
MCP tools, the Agent tool, and write-capable Bash are all denied.
it's capped at 5 turns to prevent rabbit-holes, and the prompt explicitly tells it: “Do not waste turns attempting to verify content. No grepping source files, no reading code.”
there's also a deduplication mechanism. if you tell Claude “remember that we use bun, not npm” and Claude writes the memory directly during conversation, the background extraction agent detects this (`hasMemoryWritesSince()`) and skips entirely.
the main agent and the background agent are mutually exclusive per turn window. they never double-write.
Periodic consolidation
this is handled by a feature called autoDream. it's a background process that fires when enough time and sessions have accumulated (defaults: 24 hours and 5 sessions since the last consolidation) when triggered, it runs a four-phase pass over the entire memory directory.
First, it reads `MEMORY.md` and skims existing topic files.
Second, it gathers recent signal from daily logs and session transcripts (via narrow grep, never full reads).
Third, it continues to merging new information into existing files, converting relative dates to absolute, and deleting facts that contradict the current codebase.
Fourth, it prunes the memory by removing stale pointers, shortening verbose entries, and resolving contradictions between files. The consolidation prompt is explicit: “if two files disagree, fix the wrong one.”
AutoDream is itself gated behind a flag (`tengu_onyx_plover`).
It runs as a forked subagent with the common read/write tools and a lock file that prevents concurrent consolidation across sessions.
Memory Deletion: not Automatic
there's no expiration or scheduled cleanup. the only way for a memory to be deleted, is through an agent deciding if it's no longer relevant to the codebase, contradicts with other memories, etc. memories are continuously refined but never silently removed unless done by Claude Code agents.
Retrieving Memory
how does Claude decide which memories to load, and how does it treat what it finds?
How memories are recalled obviously, Claude doesn't load all memories into the context; that would defeat the whole purpose of the intricacies of memory writing.
`MEMORY.md` (the index, capped at 200 lines / 25KB) is always loaded into the system prompt but individual memory files are not.
the system uses Sonnet (even if your main model was Opus) as a faster relevance filter. when you fire a request, before the main model starts thinking, a non-blocking process:
scans all memory file frontmatter (up to 200 files, sorted newest-first) formats a manifest: `[type] filename (timestamp): description` sends this manifest + your query to Sonnet Sonnet returns the top 5 most relevant filenames only those 5 files get loaded into context this is why the `description` field in frontmatter matters so much as it's the only thing Sonnet sees when deciding relevance.
the selector is prompted: “if you are unsure if a memory will be useful, do not include it.” this would eliminate the false-positives, only allowing the most relevant memories to be fetched.
it also filters out documentation for tools Claude is already using but selecting warnings and gotchas about those tools.
files already shown in prior turns are excluded too, to keep the 5-slot budget on fresh memories that matter more.
Older memories are added with skepticism memories older than one day get an explicit warning injected alongside their content: “This memory is 47 days old. Memories are point-in-time observations, not live state. claims about code behavior or file:line citations may be outdated. Verify against current code before asserting as fact.”
the system prompt also reinforces this with a section titled “Before recommending from memory”: if a memory names a file path, check the file exists; if it names a function, grep for it; if the user is about to act on the recommendation, verify first.
Memory Path Security
this is an interesting security detail. the `autoMemoryDirectory` setting lets you customize where memories are stored. but it can only be set in the global claude settings ~/.claude/settings.json and not project-level settings .claude/settings.json.
the reason is that if a malicious repo has this path set to a directory with sensitive files (such as ~/.ssh), it would give silent write access to those files. paths are also validated against traversal attacks (`..` segments), root paths, and null bytes.
the extraction agent's sandbox adds another layer. even if something weird happens, it can only write to paths that pass `isAutoMemPath()`. everything else gets denied.
Wrap-up: The Harness Matters
It was interesting to see that even for managing the memory of a powerful model such as Opus 4.6, there are strict limitations and boundaries set.
the model isn't left to decide how to format the memory, or to retrieve it.
the format is enforced (YAML frontmatter, one-line index entries, four fixed types). retrieval is handled by a separate, cheaper model (Sonnet) that the main model doesn't even control.
deletion has no automatic trigger. staleness warnings are injected by the harness to emphasize their date. the sandbox restricts where the extraction agent can write.
even the decision of what NOT to save is hardcoded into the prompt.
the model is powerful, but the harness doesn't trust it to manage its own memory unsupervised. every step has constraints.