# Claude Code Memory System — Implementation Details > From system prompt injection to background auto-extraction, dissecting every technical detail of the memory system.
Architecture · Path Resolution · Prompt Injection · Auto-Extraction · Intelligent Retrieval · Memory Scanning · Agent Memory · Team Sync · Constants · Data Flow
 --- ## 1. Overall Architecture The memory system is powered by 5 core modules working in concert: | Module | Source Location | Responsibility | |--------|----------------|----------------| | **Path Resolution** | `src/memdir/paths.ts` | Computes memory storage directory, handles overrides and security validation | | **Prompt Construction** | `src/memdir/memdir.ts` | Injects memory instructions into the system prompt | | **Memory Scanning** | `src/memdir/memoryScan.ts` | Scans directories, parses frontmatter, sorts entries | | **Intelligent Retrieval** | `src/memdir/findRelevantMemories.ts` | Uses Sonnet to select memories relevant to the current query | | **Auto-Extraction** | `src/services/extractMemories/` | Background forked agent that extracts memories from conversations | Auxiliary modules: | Module | Source Location | Responsibility | |--------|----------------|----------------| | **AutoDream** | `src/services/autoDream/` | Background memory consolidation ("dreaming"); see [03-autodream.md](./03-autodream.md) | | **Type Definitions** | `src/memdir/memoryTypes.ts` | Taxonomy and prompt templates for the four memory types | | **Freshness** | `src/memdir/memoryAge.ts` | Calculates memory age, generates stale warnings | | **File Detection** | `src/utils/memoryFileDetection.ts` | Determines whether a path belongs to the memory system | | **Agent Memory** | `src/tools/AgentTool/agentMemory.ts` | Three-level memory directories exclusive to sub-agents | | **Team Sync** | `src/services/teamMemorySync/` | Remote upload/download of memories | --- ## 2. Path Resolution System  ### Core Function: `getAutoMemPath()` ``` Path resolution priority (highest to lowest): 1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE <- Cowork environment variable (full path) 2. settings.json -> autoMemoryDirectory <- User setting (supports ~/ expansion) 3. {memoryBase}/projects/{sanitized-git-root}/memory/ <- Default computed path ``` **Key source** `src/memdir/paths.ts:223`: ```typescript export const getAutoMemPath = memoize( (): string => { const override = getAutoMemPathOverride() ?? getAutoMemPathSetting() if (override) return override const projectsDir = join(getMemoryBaseDir(), 'projects') return join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep }, () => getProjectRoot(), // Cache key = project root ) ``` ### Path Security Validation `validateMemoryPath()` rejects dangerous paths: | Rejected Path | Reason | |---------------|--------| | `../foo` | Relative path, CWD-dependent | | `/` or `/a` | Root path or too-short path | | `C:\` | Windows drive root | | `\\server\share` | UNC network path | | Contains `\0` | Null byte, can truncate in system calls | **Security restriction**: Project-level `.claude/settings.json` is **not allowed** to set `autoMemoryDirectory`, preventing malicious repositories from gaining write access to sensitive directories like `~/.ssh`. ### Enable Conditions The `isAutoMemoryEnabled()` check chain: ``` CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 -> Disabled CLAUDE_CODE_SIMPLE (--bare) -> Disabled Remote mode without REMOTE_MEMORY_DIR -> Disabled settings.json autoMemoryEnabled -> Follows setting Default -> Enabled ``` --- ## 3. System Prompt Injection  ### Entry Point: `loadMemoryPrompt()` This is the interface between the memory system and the system prompt. It is called once at startup (cached via `systemPromptSection`). ``` loadMemoryPrompt() |-- KAIROS mode? -> buildAssistantDailyLogPrompt() [log append mode] |-- TEAMMEM enabled? -> buildCombinedMemoryPrompt() [personal + team dual directory] |-- Normal mode -> buildMemoryLines() [single directory] |-- Disabled -> return null ``` ### Prompt Structure Built by `buildMemoryLines()` ``` # auto memory You have a persistent file-based memory system located at `{memoryDir}`... ## Types of memory <- Definitions and examples for all four types ## What NOT to save <- Exclusion rules ## How to save memories <- Two-step saving process ## When to access memories <- When to consult ## Before recommending <- Verify before citing ## Memory and other forms <- Distinction from Plan/Task ## MEMORY.md <- Index content (or "currently empty") ``` ### MEMORY.md Truncation Strategy `truncateEntrypointContent()` applies dual limits: ```typescript // First truncate by line count if (lineCount > 200) -> Truncate to 200 lines // Then truncate by byte size (handles extra-long lines) if (bytes > 25,000) -> Truncate at last newline character // Append warning "WARNING: MEMORY.md is {reason}. Only part of it was loaded." ``` ### Automatic Directory Creation `ensureMemoryDirExists()` ensures the directory exists when loading the prompt: - Recursively creates with `mkdir` (handles the entire parent chain) - Swallows `EEXIST` (idempotent) - Genuine permission errors are only logged, not thrown (the Write tool will surface the real error) The prompt explicitly tells the model the directory already exists, avoiding wasted turns on `ls` or `mkdir`. --- ## 4. Automatic Memory Extraction  ### Trigger Timing Triggered in `handleStopHooks` when the model produces a final response (no tool calls). **Key source**: `src/services/extractMemories/extractMemories.ts` ### Complete Extraction Flow ``` 1. Model finishes response (no tool_use) | 2. executeExtractMemories() is called | 3. Guard checks: - Is this the main agent? (sub-agents don't extract) - Feature gate enabled? - Auto-memory enabled? - Not in remote mode? - No parallel extraction in progress? | 4. Frequency control: turnsSinceLastExtraction++ if < tengu_bramble_lintel -> skip | 5. Mutual exclusion check: Main agent wrote memory itself? -> skip, advance cursor | 6. Scan existing memory directory (scanMemoryFiles) Generate manifest (formatMemoryManifest) | 7. Build extraction prompt (buildExtractAutoOnlyPrompt) | 8. Run forked agent (runForkedAgent) - Shares parent session's prompt cache - Max 5 turns - Restricted tool permissions | 9. Extract written file paths Advance cursor to latest message | 10. Notify user: "Memory updated in ..." ``` ### Forked Agent Auto-extraction uses `runForkedAgent` -- a perfect fork of the main session: - **Shared prompt cache**: Avoids duplicate API cache creation costs - **Isolated execution**: Does not affect the main session's message history - **Restricted tools**: Only allows Read, Grep, Glob, read-only Bash, and Edit/Write within the memory directory - **No transcript recording**: Prevents race conditions with the main thread ### Tool Permissions (`createAutoMemCanUseTool`) ``` Allowed: Read, Grep, Glob (unrestricted) Allowed: Bash (read-only commands only: ls, find, grep, cat, stat...) Allowed: Edit/Write (only within auto-memory directory) Denied: MCP, Agent, non-read-only Bash, other write operations ``` ### Mutual Exclusion Mechanism The main agent and the extraction agent are **mutually exclusive**: ```typescript function hasMemoryWritesSince(messages, sinceUuid): boolean { // Scan all assistant messages after sinceUuid // If any Edit/Write tool_use targets the auto-memory directory // -> return true (skip extraction, advance cursor) } ``` This prevents duplicate saves: when the main agent has already written a memory, the background extraction is skipped. ### Merge Mechanism If a previous extraction is still running: 1. The new context is queued (`pendingContext`) 2. After the old extraction completes, a **tail extraction** is immediately launched 3. The tail extraction only processes messages added between the two calls --- ## 5. Intelligent Memory Retrieval  ### How It Works Each time the user sends a query, `findRelevantMemories()` is triggered: ``` 1. scanMemoryFiles(memoryDir) - Recursively reads all .md files (excludes MEMORY.md) - Parses frontmatter (first 30 lines) - Sorts by modification time in descending order - Max 200 files | 2. Filter out previously surfaced memories (alreadySurfaced) | 3. Format manifest (formatMemoryManifest) - [type] filename (ISO timestamp): description | 4. Sonnet model selection (sideQuery) - System prompt: You are a memory selector... - User message: Query + Available memories + Recently used tools - Output: JSON { selected_memories: string[] } - Max 5 selections | 5. Return selected memories as { path, mtimeMs } ``` ### Sonnet Selector Prompt ``` You are selecting memories useful for Claude Code to handle the user's query. You'll receive the user's query and a list of available memory files (with filenames and descriptions). Return at most 5 memory filenames that are clearly useful. - If uncertain whether something is useful, don't select it - If nothing is clearly useful, return an empty list - If a list of recently used tools is provided, don't select usage docs for those tools (but DO select warnings/gotchas/known issues about those tools) ``` ### Freshness Warning Selected memories are injected into the context with freshness information: ```typescript function memoryFreshnessText(mtimeMs: number): string { const d = memoryAgeDays(mtimeMs) if (d <= 1) return '' // Today/yesterday: no warning return `This memory is ${d} days old. Memories are point-in-time observations... Verify against current code before asserting as fact.` } ``` --- ## 6. Memory Scanning in Detail ### `scanMemoryFiles()` **Key design**: Single-pass (read-then-sort) to avoid double stat system calls. ```typescript async function scanMemoryFiles(memoryDir, signal): Promise