# Complete Analysis of the Memory System

> **Source Version**: Claude Code 2.1.88 (community-released source code)
> **Analysis Depth**: File-by-file, function-by-function
> **Files Covered**: About 30 core files across `src/memdir/`, `src/services/`, `src/utils/`, `src/components/memory/`

---

## 💡 Plain English

Imagine you have an extremely meticulous personal secretary. Every time you meet, the secretary does three things:

1. **Review the notebook before the meeting** — Before the meeting starts, the secretary flips through old notes to recall your preferences, what you mentioned last time, and projects that are still unfinished. This is **memory loading**.
2. **Quietly take notes during the meeting** — While the conversation is happening, the secretary quietly writes down key points beside you: mistakes you corrected, new preferences you mentioned, important project decisions. But the secretary does not record every trivial detail — only things that will still be useful "next time we meet." This is **memory extraction (`extractMemories`)**.
3. **Organize and archive on the weekend** — Every so often, the secretary organizes scattered shorthand notes into categorized documents: one for "boss preferences," one for "project progress," one for "important references." This is **memory consolidation (Dream consolidation)**.

Claude Code's memory system is the secretary's full workflow.

### 🌍 Industry Background

Cross-session memory is one of the key differentiators among AI coding tools. The gap in memory capabilities between tools is large:

- **Cursor**: Supports `.cursorrules` project-level instruction files (similar to `CLAUDE.md`) and a Notepad feature for storing notes across sessions. But it has no automatic memory extraction — users must maintain context manually. Its 2025 Memory feature can automatically remember user preferences, but its granularity is still far behind Claude Code's multi-layer memory system centered on Auto Memory.
- **Windsurf**: Supports `.windsurfrules` instruction files and Cascade Memory (automatic learning of user preferences), but its memory structure is relatively flat, without a type taxonomy like user/feedback/project/reference.
- **GitHub Copilot**: Supports project-level instructions through `.github/copilot-instructions.md`, but has no cross-session persistent memory or automatic memory extraction.
- **Aider**: Supports `.aider.conf.yml` configuration and `--read` for loading context files, but lacks automatic memory extraction or consolidation (Dream). All context management must be done manually by the user.
- **CodeX（OpenAI）**: Supports `AGENTS.md` instruction files and an open-source Skills Library, conceptually similar to `CLAUDE.md`, but has no automatic memory or cross-session persistence.
- **OpenCode**: Supports deep YAML-based customization of sub-agent execution logic and system prompts, with a declarative plugin architecture for MCP and local tools, but likewise lacks Claude Code-level automatic memory extraction.

Claude Code's memory system is one of the most complex implementations in the industry: a multi-store system centered on Auto Memory, automatic background extraction, Dream consolidation, relevance retrieval (Sonnet side queries), and team-shared memory. The tradeoff is high system complexity and increased API-call cost, but the payoff is much stronger context continuity.

> 📚 **Academic Perspective: The Tulving Memory Framework**
>
> Cognitive psychologist Endel Tulving proposed three categories of memory systems in 1972, and they happen to map neatly onto Claude Code's memory architecture:
>
> | Tulving Memory Type | Claude Code Implementation | Explanation |
> |---|---|---|
> | **Episodic Memory** | JSONL conversation storage + real-time distillation by SessionMemory | Records "what happened" — the full context of each conversation, plus key summaries extracted from it by Session Memory |
> | **Semantic Memory** | forked sub-agent (`extractMemories`) extraction + background consolidation by autoDream | Abstracts general knowledge from specific experiences — background agents extract reusable facts and patterns from conversations, and the Dream system further consolidates and deduplicates them |
> | **Procedural Memory** | `feedback` memory type (covering both positive and negative feedback) | Records "how to do things" — user corrections and confirmations shape behavioral patterns, teaching the Agent how a particular user likes to work |
>
> It is worth noting that most AI memory systems focus only on negative feedback (correction), while Claude Code's `feedback` type explicitly requires recording positive confirmations as well ("yes, do it exactly like this"). This prevents the Agent from becoming overly conservative after repeated correction — a phenomenon repeatedly validated in human memory research as well.

---

## 1. Architectural Overview

Claude Code's memory system is not a single module. It is a composite system made up of **one core system plus four auxiliary/specialized stores**.

> **Important clarification**: The following five subsystems **do not have a layered dependency relationship**. They work independently, not as a stack where "the first layer calls the second, and the third depends on the fourth." They are more like several coworkers in the same office, each handling a different area. **Auto Memory is the core of the entire memory system** — it has the most sophisticated extraction pipeline, type taxonomy, Dream consolidation, and relevance retrieval. The other four subsystems operate independently to solve storage needs for specific scenarios: `CLAUDE.md` is a user-managed configuration layer, Session Memory is a single-session compression aid, Agent Memory is a companion store for AgentTool, and Team Memory is simply the `team/` subfolder under Auto Memory.

| Subsystem | Name | Storage Location | Lifecycle | Core File |
|:---:|:---:|:---:|:---:|:---:|
| **Core** | Auto Memory (memdir) | `~/.claude/projects/<slug>/memory/` | Permanent (automatically managed) | `src/memdir/memdir.ts` |
| Configuration Layer | `CLAUDE.md` instruction file | Project root / `~/.claude/` | Permanent (manually managed by user) | `src/utils/claudemd.ts` |
| Auxiliary | Session Memory | `~/.claude/session-memory/` | Single session | `src/services/SessionMemory/` |
| Auxiliary | Agent Memory | `~/.claude/agent-memory/` or `.claude/agent-memory/` | Permanent (Agent-level) | `src/tools/AgentTool/agentMemory.ts` |
| Auxiliary | Team Memory | `<memdir>/team/` | Permanent (team-shared) | `src/memdir/teamMemPaths.ts` |

### Why must they be separated?

The fact that the five subsystems are independent is not accidental. If they were mixed together, three serious problems would appear:

1. **Rules and facts would contaminate each other.** If hard constraints in `CLAUDE.md` ("never delete the `.env` file") and recallable background facts in Auto Memory ("we deleted `.env` once before and it caused an incident") were stored together, the model could not tell which items are **rules that must be obeyed** and which are **historical facts for reference only**. `CLAUDE.md` is fundamentally **instruction memory** ("how things should be done"), while Auto Memory is fundamentally **factual memory** ("what has happened before") — their semantics are completely different and should not enter the same retrieval pipeline.

2. **Session endurance and long-term accumulation would conflict.** If Session Memory's working notes ("the user just mentioned wanting to refactor the auth module") were written into long-term memory, it would create huge amounts of stale garbage — once the refactor is done, that note becomes wrong information. Session Memory exists to improve endurance within the current session and should be discarded when the session ends.

3. **The sharing boundary would become dangerous.** A user's personal Auto Memory may contain sensitive information (API key fragments, private paths). Without physical isolation from Team Memory, `teamMemorySync` could leak content that should never be synced when it uploads — this is also why Team Memory has its own secret scanner (`secretScanner.ts`).

> 💡 **Plain English**: This is like how you would never write your diary (private facts), company regulations (institutional rules), today's TODO list (temporary notes), and shared team documentation (collaboration materials) all into the same notebook — if you mix them together, you cannot tell what is safe to show other people, what is outdated, and what must be obeyed.

### 1.1 Load Priority

The load order for `CLAUDE.md` instruction files is strictly defined (see the header comment in `src/utils/claudemd.ts`):

> 💡 **How do these paths work?** The `~` symbol below represents the "home directory" on your computer — on macOS it is `/Users/your-username/`, and on Windows it is `C:\Users\your-username\`. So `~/.claude/` means a hidden folder named `.claude` inside your home directory.

```
1. Managed memory (/etc/claude-code/CLAUDE.md) — global managed instructions (configured centrally by company IT)
2. User memory (~/.claude/CLAUDE.md) — user's private global instructions (your personal global preferences)
3. Project memory (CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md) — project-level instructions (rules for the current project)
4. Local memory (CLAUDE.local.md) — private project instructions (your personal rules for the current project, not committed to version control)
```

**Later-loaded files have higher priority** — the model pays the most attention to the last content injected. This follows the standard "closest wins" rule used in software configuration: local config overrides global config, just like CSS cascade rules or Git's `--local > --global > --system` priority chain.

---

## 2. Core Subsystem One: Auto Memory (memdir)

Auto Memory is the core of Claude Code's memory system and implements **persistent cross-session memory**.

### 2.1 Path Resolution

The path-computation logic lives in `src/memdir/paths.ts`. The purpose of the following code is to **decide which folder on disk stores the memory files** — you do not need to understand it line by line. You only need to know that it checks three possible locations in priority order (Chinese explanation follows the code):

```typescript
// src/memdir/paths.ts (lines 223-235)
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
    ).normalize('NFC')
  },
  () => getProjectRoot(),
)
```

The resolution priority is:
1. `CLAUDE_COWORK_MEMORY_PATH_OVERRIDE` environment variable (used by Cowork SDK)
2. `autoMemoryDirectory` in `settings.json` (trusted only from policy/local/user sources, **not from project sources** — to prevent a malicious repository from pointing the memory directory at `~/.ssh`)
3. Default path `~/.claude/projects/<sanitized-git-root>/memory/`

**Security hardening**: `validateMemoryPath()` (lines 109-150) performs seven security checks on the path. The core purpose is to **prevent a malicious project from secretly writing memory files into sensitive locations** (for example your SSH key directory `~/.ssh`, which stores the "keys" used to log into remote servers):
- Reject relative paths (the path must be absolute, like `/Users/xxx/...`; values like `../../` that can "jump upward" into other directories are not allowed)
- Reject root and near-root directories (length < 3)
- Reject Windows drive roots
- Reject UNC paths (Windows network share paths such as `\\server\share`)
- Reject null bytes (an attack technique that exploits low-level language behavior to "truncate" file paths)
- Reject `~` paths that expand to `$HOME` or its parent directory
- Finally normalize Unicode to NFC (unify character encoding so visually identical but differently encoded paths cannot bypass validation)

### 2.2 Memory Enablement Check

`isAutoMemoryEnabled()` (`src/memdir/paths.ts`, lines 30-55) uses a **six-level decision chain** (first-defined-wins):

```typescript
// src/memdir/paths.ts (lines 30-55)
export function isAutoMemoryEnabled(): boolean {
  const envVal = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY
  if (isEnvTruthy(envVal)) {       // "1", "true" → disabled
    return false
  }
  if (isEnvDefinedFalsy(envVal)) { // "0", "false" → force-enabled!
    return true
  }
  // ... subsequent checks
}
```

1. `CLAUDE_CODE_DISABLE_AUTO_MEMORY` is truthy (`"1"` / `"true"`) → disabled
2. `CLAUDE_CODE_DISABLE_AUTO_MEMORY` is falsy (`"0"` / `"false"`) → **force-enabled** (short-circuits all later checks)
3. `--bare` / `CLAUDE_CODE_SIMPLE` flag → disabled
4. Remote-mode detection (without `CLAUDE_CODE_REMOTE_MEMORY_DIR`) → disabled
5. `autoMemoryEnabled` in `settings.json` → follow the configured value
6. Enabled by default

> **Counterintuitive behavior**: `CLAUDE_CODE_DISABLE_AUTO_MEMORY=0` will **force-enable** memory and short-circuit all remaining checks — a variable named "DISABLE" set to `0` ends up force-enabling the feature. This happens because the source handles the environment variable with two branches, `isEnvTruthy` (disable) and `isEnvDefinedFalsy` (force-enable), making it a **bidirectional switch** rather than a one-way gate. The practical effect is that if you disable memory in `settings.json` but set `CLAUDE_CODE_DISABLE_AUTO_MEMORY=0`, the environment variable overrides the config file and forcibly turns memory back on.

### 2.3 Memory Type Taxonomy

Auto Memory strictly constrains memory into **four types**, defined in `src/memdir/memoryTypes.ts`:

```typescript
// src/memdir/memoryTypes.ts (lines 14-21)
export const MEMORY_TYPES = [
  'user',       // User profile: role, preferences, expertise level
  'feedback',   // Behavioral feedback: corrections + positive confirmations
  'project',    // Project context: goals, decisions, deadlines
  'reference',  // External references: Linear projects, Grafana dashboards
] as const
```

Each type has precise guidance for `<when_to_save>`, `<how_to_use>`, and `<examples>`.

**Key design decision**: The system explicitly forbids saving the following content (`WHAT_NOT_TO_SAVE_SECTION`, lines 183-195):
- Code patterns, architecture, file paths — these can be obtained through grep/git
- Git history — `git log` / `git blame` are the authoritative sources
- Debugging plans — the fix is already in the code, and the commit message contains the context
- Content already present in `CLAUDE.md`
- Temporary task details

> Even if the user explicitly asks to save these things, the system will steer extraction toward the parts that are "surprising or non-obvious." This prevents the memory system from degenerating into an activity log.

### 2.4 Memory File Format

Each memory is an independent Markdown file with YAML frontmatter:

```markdown
---
name: {{memory name}}
description: {{one-line description — used for future relevance matching, be as specific as possible}}
type: {{user, feedback, project, reference}}
---

{{memory body — for feedback/project, recommended structure: rule, followed by **Why:** and **How to apply:** lines}}
```

**`MEMORY.md` is an index, not a memory itself** — each index entry is capped at roughly 150 characters, using the format `- [Title](file.md) — one-line hook`. The total index is limited to 200 lines or 25,000 bytes (`MAX_ENTRYPOINT_LINES` = 200, `MAX_ENTRYPOINT_BYTES` = 25,000).

### 2.5 Memory Entrypoint Truncation

`truncateEntrypointContent()` (`src/memdir/memdir.ts`, lines 57-103) implements **double truncation**: first by line count (200 lines), then by byte count (25KB, cut at the nearest newline so lines are not split). The byte cap exists because some users have `MEMORY.md` files that are under 200 lines but reach 197KB (extremely long index entries on each line). The double check on both line count and bytes covers both failure modes.

---

## 3. Core Subsystem Two: Memory Scanning and Relevance Retrieval

### 3.1 Scanning Engine

The `scanMemoryFiles()` function (`src/memdir/memoryScan.ts`, lines 35-77) performs a single-pass scan:

```typescript
// src/memdir/memoryScan.ts (lines 35-77)
export async function scanMemoryFiles(
  memoryDir: string,
  signal: AbortSignal,
): Promise<MemoryHeader[]> {
  try {
    const entries = await readdir(memoryDir, { recursive: true })
    const mdFiles = entries.filter(
      f => f.endsWith('.md') && basename(f) !== 'MEMORY.md',
    )
    const headerResults = await Promise.allSettled(
      mdFiles.map(async (relativePath): Promise<MemoryHeader> => {
        const filePath = join(memoryDir, relativePath)
        const { content, mtimeMs } = await readFileInRange(
          filePath, 0, FRONTMATTER_MAX_LINES, undefined, signal,
        )
        const { frontmatter } = parseFrontmatter(content, filePath)
        return {
          filename: relativePath,
          filePath,
          mtimeMs,
          description: frontmatter.description || null,
          type: parseMemoryType(frontmatter.type),
        }
      }),
    )
    return headerResults
      .filter((r): r is PromiseFulfilledResult<MemoryHeader> =>
        r.status === 'fulfilled')
      .map(r => r.value)
      .sort((a, b) => b.mtimeMs - a.mtimeMs)  // newest first
      .slice(0, MAX_MEMORY_FILES)  // cap at 200 files
  } catch { return [] }
}
```

**Design choice**: single-pass (read-then-sort) rather than two-pass (stat-sort-read). For the common case (`N <= 200`), this cuts the number of syscalls in half. The tradeoff is that when `N > 200`, it reads a few extra small frontmatter sections.

> 📚 **Course connection**: The memory retrieval pattern of "scan all frontmatter → let the LLM choose relevant items" maps to the **two-stage query optimization** model in database systems — first use a cheap full scan (frontmatter parsing, like an index scan) to narrow the candidate set, then use an expensive precise match (Sonnet side query, like a table lookup) to pick the final result. The `MAX_MEMORY_FILES = 200` cap acts like a **cardinality-estimation ceiling** in a database optimizer, preventing the I/O cost of a full scan from growing out of control.

Each memory reads only the first 30 lines (`FRONTMATTER_MAX_LINES = 30`), which is enough to parse the YAML frontmatter.

### 3.2 Intelligent Relevance Retrieval

The `findRelevantMemories()` function (`src/memdir/findRelevantMemories.ts`) implements a **two-stage retrieval pipeline**:

**Stage 1**: Scan the frontmatter of all memory files and build a summary manifest.

**Stage 2**: Use the Sonnet model (`sideQuery`, an API call independent of the main conversation) to select up to 5 relevant memories from that manifest. The selector uses JSON Schema to force output of `{ selected_memories: string[] }`, with `max_tokens: 256`.

**Key details**:
- The selector is **conservative** — the prompt says to select memories only if it is sure they will help
- The `alreadySurfaced` parameter filters out memories that have already been shown, so they do not waste one of the 5 slots
- The `recentTools` parameter suppresses recommendations for reference documents about tools that are already in active use — but it **still selects** memories containing warnings and known issues

### 3.3 Memory Freshness Awareness

`memoryAge.ts` provides human-readable age labels and stale-memory warnings:

```typescript
// src/memdir/memoryAge.ts (lines 33-42)
export function memoryFreshnessText(mtimeMs: number): string {
  const d = memoryAgeDays(mtimeMs)
  if (d <= 1) return ''
  return (
    `This memory is ${d} 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.`
  )
}
```

Memories older than one day get a staleness warning. The motivation is that users reported old memories containing `file:line` references that had since changed, yet were still being asserted as facts. Outdated claims with citations can feel even more "authoritative" than unsupported claims.

---

## 4. Core Subsystem Three: Background Memory Extraction (`extractMemories`)

This is the most sophisticated part of the memory system — a **background sub-agent (forked subagent)** that automatically extracts information worth remembering from the conversation after each turn.

**Economic foundation: shared prompt cache**

The extraction agent runs in `runForkedAgent` mode (`src/utils/forkedAgent.ts`). The source comment (`extractMemories.ts`, lines 8-9) explicitly says this is "a perfect fork of the main conversation that shares the parent's prompt cache." This design is the **economic foundation** that makes "one forked-agent call per turn" affordable for the entire memory system.

The concrete mechanism is that `forkedAgent.ts` defines the `CacheSafeParams` type (lines 57-68), which includes the system prompt, user context, system context, tool-use context, and fork-context messages. These parameters must be **identical** to the main conversation in order to hit Anthropic API prompt cache:

```typescript
// src/utils/forkedAgent.ts (lines 46-55)
/**
 * Parameters that must be identical between the fork and parent API requests
 * to share the parent's prompt cache. The Anthropic API cache key is composed of:
 * system prompt, tools, model, messages (prefix), and thinking config.
 */
```

After each main-loop `handleStopHooks` execution, the current cache-safe parameters are saved via `saveCacheSafeParams()`. Later forked agents — including memory extraction, prompt suggestion, post-turn summary, and so on — all reuse the same parameter set. Cache hit rate is continuously monitored through the `hitPct` field in the log template.

**Cost impact**: Without cache sharing, each extraction turn would need to resend the full system prompt + tool definitions + conversation history as input tokens, making the cost roughly comparable to the main conversation itself (Sonnet input-token pricing × full context length). With cache sharing, the extraction call only pays for incremental messages not already covered by cache (cached portions are billed at a 10% rate). That turns "automatic extraction every turn" from economically unacceptable into entirely viable.

### 4.1 Trigger Conditions

The extraction call is fire-and-forget from `stopHooks.ts`. Before it triggers, all of the following must be true:
- The current agent is the main agent (not a sub-agent)
- The `tengu_passport_quail` feature gate is enabled
- Auto Memory is enabled
- Not in remote mode

### 4.2 Cursor Mechanism and Graceful Degradation

The extractor uses the `sinceUuid` cursor to track "which message was processed last time." The `countModelVisibleMessagesSince()` function (lines 82-110) counts new messages after the cursor so the system can decide whether there is enough new content to justify extraction.

```typescript
// src/services/extractMemories/extractMemories.ts (lines 82-110)
function countModelVisibleMessagesSince(
  messages: Message[],
  sinceUuid: string | undefined,
): number {
  if (sinceUuid === null || sinceUuid === undefined) {
    return count(messages, isModelVisibleMessage)
  }
  let foundStart = false
  let n = 0
  for (const message of messages) {
    if (!foundStart) {
      if (message.uuid === sinceUuid) { foundStart = true }
      continue
    }
    if (isModelVisibleMessage(message)) { n++ }
  }
  // Degradation when the cursor is lost: fall back to a full count instead of returning 0
  if (!foundStart) {
    return count(messages, isModelVisibleMessage)
  }
  return n
}
```

**Graceful degradation design**: When `sinceUuid` cannot be found because context compaction deleted it, the system **does not return 0 (which would permanently disable extraction for the rest of the current session)**. Instead, it falls back to counting all visible messages. The source comment explicitly states the reason for this decision — "prevent permanently disabling extraction for the rest of the current session."

This is a classic tradeoff in handling lost state: in distributed systems, cursor invalidation is common (Kafka offset expiry, database cursor timeouts, and so on). There are usually three strategies — (a) reprocess everything, (b) skip directly, or (c) return an error. Claude Code chooses (a), preserving the **completeness** of memory extraction (so important content is not missed), at the cost of possibly reprocessing content that was already handled. In practice that cost is acceptable because the extraction agent itself is idempotent: identical content does not create duplicate memory writes when the memory already exists.

### 4.3 Frequency Control

`extractMemories.ts` uses **configurable throttling** (`tengu_bramble_lintel`, default: once every turn) plus **overlap protection**:

```typescript
// src/services/extractMemories/extractMemories.ts (lines 376-386)
if (!isTrailingRun) {
  turnsSinceLastExtraction++
  if (
    turnsSinceLastExtraction <
    (getFeatureValue_CACHED_MAY_BE_STALE('tengu_bramble_lintel', null) ?? 1)
  ) {
    return
  }
}
turnsSinceLastExtraction = 0
```

### 4.4 Mutual Exclusion with the Main Agent

When the main agent has written memory itself, the background extractor skips that range and advances the cursor:

```typescript
// src/services/extractMemories/extractMemories.ts (lines 121-148)
function hasMemoryWritesSince(
  messages: Message[],
  sinceUuid: string | undefined,
): boolean {
  // ... check every assistant message after sinceUuid
  // to see if any Write/Edit tool_use points at an auto-memory path
  for (const block of content) {
    const filePath = getWrittenFilePath(block)
    if (filePath !== undefined && isAutoMemPath(filePath)) {
      return true
    }
  }
  return false
}
```

This guarantees that the main agent and the background agent are **mutually exclusive per turn**, avoiding duplicate writes.

### 4.5 Concurrency Handling: The Stash-and-Trail Pattern

If a new extraction request arrives while the previous run is still in progress, it is not dropped. Instead, it is stashed and executed as a trailing run after the first one completes:

```
Request arrives --> inProgress? --> yes --> stash context (overwrite previous stash)
                                no --> start runExtraction
runExtraction completes --> pending? --> yes --> runExtraction(trailing)
                                          no --> idle
```

The trailing run recalculates `newMessageCount` using the already-advanced cursor, so it processes only the messages added between the two invocations.

> 📚 **Course connection**: The Stash-and-Trail pattern is a variation of the **producer-consumer problem** from operating systems — the producer (conversation turns) keeps generating new messages, while the consumer (the extractor) processes them at its own pace. Unlike a classic bounded buffer, the stash retains only the newest pending request (overwriting older stash content), which resembles the idea of a **write-combining buffer** in computer architecture — multiple writes are merged into a single processing event.

### 4.6 Tool Permission Sandbox

The background extraction agent is tightly restricted (`createAutoMemCanUseTool()`, lines 171-222):

| Tool | Permission |
|:---:|:---:|
| FileRead, Grep, Glob | Unrestricted |
| Bash | Read-only commands only (`ls`, `find`, `grep`, `cat`, `stat`, `wc`, `head`, `tail`, etc.) |
| FileEdit, FileWrite | **Only** paths inside the memory directory |
| MCP, Agent, writable Bash | All denied |

### 4.7 Extraction Prompt

The extraction prompt (`src/services/extractMemories/prompts.ts`) gives the agent explicit efficiency guidance:

```
Your turn budget is limited. FileEdit requires FileRead on the same file first.
Efficient strategy:
  turn 1 — issue all FileReads you may need in parallel
  turn 2 — issue all FileWrites/FileEdits in parallel
Don't alternate reads and writes across multiple turns.
```

The hard limit is 5 turns (`maxTurns: 5`) to prevent the agent from disappearing into a verification rabbit hole.

### `extractMemories` vs `autoDream`: division of labor between two memory mechanisms

Readers can easily confuse these two. The table below makes the difference explicit:

| Dimension | `extractMemories` (§4) | `autoDream` (§6) |
|------|----------------------|-----------------|
| **What it does** | Extracts durable signal from the current transcript | Consolidates / deduplicates / reorganizes existing memories |
| **When it runs** | At the end of every conversation turn (high frequency) | On a low-frequency background schedule (24h + 5 sessions) |
| **How it triggers** | Automatically invoked during `queryLoop` shutdown | Starts only after `isGateOpen()` passes all three gates |
| **What it operates on** | Conversation history (`messages`) | Existing `MEMORY.md` + memory files |
| **Output** | New memory files (`user` / `feedback` / `project` / `reference`) | Consolidated topic files (merge / dedupe / prune) |
| **Analogy** | The secretary taking notes after every meeting | The secretary organizing the entire week's notes over the weekend |

> 💡 **Plain English**: `extractMemories` is "writing in a diary every day," while `autoDream` is "reviewing the diary at the end of the month, deleting repeats, merging related entries, and crossing out outdated ones." You need both — if you only write and never organize, the system becomes messier and messier; if you only organize and never write, there is no new material to work with.

---

## 5. Core Subsystem Four: Session Memory

Session Memory is **note-taking at the single-session level**. During the conversation, it automatically maintains a Markdown file that records key information from the current exchange.

### 5.1 Trigger Thresholds

Defined in `src/services/SessionMemory/sessionMemoryUtils.ts`:

```typescript
// src/services/SessionMemory/sessionMemoryUtils.ts (lines 32-36)
export const DEFAULT_SESSION_MEMORY_CONFIG: SessionMemoryConfig = {
  minimumMessageTokensToInit: 10000,     // First trigger: context reaches 10K tokens
  minimumTokensBetweenUpdate: 5000,      // Update interval: context grows by 5K tokens
  toolCallsBetweenUpdates: 3,            // Update interval: at least 3 tool calls
}
```

Trigger conditions (`shouldExtractMemory()`, `sessionMemory.ts`, lines 134-181):
1. First time: context-window tokens >= 10,000
2. For every update after that, both of the following must hold:
   - **Token threshold** (required): context has grown by >= 5,000 tokens since the last extraction
   - **And** at least one of the following: tool-call count >= 3, or the last assistant reply had no tool call (a natural conversation breakpoint)

### 5.2 Integration with Compaction

One core use of Session Memory is to replace traditional context compression. `sessionMemoryCompact.ts` defines the retained range after compaction: at least 10K tokens / 5 text messages, and at most 40K tokens (`DEFAULT_SM_COMPACT_CONFIG`, lines 57-61).

`calculateMessagesToKeepIndex()` (lines 324-397) expands forward starting from `lastSummarizedMessageId` until the minimum requirements are met. A key detail is that `adjustIndexToPreserveAPIInvariants()` (lines 232-314) ensures tool_use / tool_result pairs are never split, preventing orphaned `tool_result` blocks from appearing in API calls.

### 5.3 Synchronous Waiting for Extraction

Before compaction, the system waits for any in-flight extraction to finish via `waitForSessionMemoryExtraction()` (lines 89-105). It uses a double-timeout design: a 15-second wait timeout plus a 60-second expiration threshold (with `sleep(1000)` polling), preventing deadlock.

---

## 6. Core Subsystem Five: Dream Consolidation (`autoDream`)

Dream is the "organize and archive" phase of the memory system — it periodically consolidates accumulated session memories into structured topic files.

### 6.1 Trigger Gating

`autoDream.ts` uses a three-level gate, ordered from low cost to high cost:

```
1. Time gate: time since last consolidation >= minHours (default 24h)  [one stat call]
2. Session gate: sessions since last consolidation >= minSessions (default 5)  [directory scan]
3. Lock gate: no other process is currently consolidating  [file lock]
```

The configuration comes from GrowthBook remote config (`tengu_onyx_plover`, lines 73-93), with defensive field-by-field type checks so sensible defaults are still used if the GB cache returns values of the wrong type.

### 6.2 KAIROS Mode: Daily Logs

When `feature('KAIROS')` is enabled and the system is in assistant mode, memory switches into **daily log mode**:

```typescript
// src/memdir/memdir.ts (lines 327-370)
function buildAssistantDailyLogPrompt(skipIndex = false): string {
  const memoryDir = getAutoMemPath()
  const logPathPattern = join(memoryDir, 'logs', 'YYYY', 'MM', 'YYYY-MM-DD.md')
  // ...
  // The agent only appends; it never rewrites or reorganizes.
  // A separate nightly process distills logs into MEMORY.md and topic files.
}
```

Log path format: `<autoMemPath>/logs/YYYY/MM/YYYY-MM-DD.md`

This is an interesting paradigm shift: in standard mode, the agent maintains `MEMORY.md` as a live index. In KAIROS mode, the agent only appends, and a `/dream` skill handles index maintenance overnight.

> **Key exclusivity relationship: KAIROS mode and automatic Dream are mutually exclusive.** `isGateOpen()` in `autoDream.ts`, lines 95-96, explicitly checks this condition:
> ```typescript
> function isGateOpen(): boolean {
>   if (getKairosActive()) return false // KAIROS mode uses disk-skill dream
>   // ...
> }
> ```
> When KAIROS mode is active, automatic Dream consolidation is fully disabled. That means KAIROS users **do not** get the automatic scheduled consolidation described in section 6.1, and instead rely on the manual `/dream` skill (disk-skill dream) to trigger consolidation. This design makes sense because KAIROS mode's "append-only + offline consolidation" pattern overlaps completely with `autoDream`'s "automatic scheduled consolidation" responsibility — if both ran at the same time, they would create state conflicts between log files and topic files.

### 6.3 The Dream Agent's Four-Phase Workflow

After passing all gates, the Dream agent receives a structured four-phase prompt, with explicit operational instructions at each phase:

| Phase | Name | Operation |
|:---:|:---:|:---|
| Phase 1 | **Orient** | `ls` the memory directory + read `MEMORY.md` + browse existing topic files to build a full picture of the current memory state |
| Phase 2 | **Gather Recent Signal** | Inspect daily logs (for example `logs/YYYY/MM/YYYY-MM-DD.md` in KAIROS mode), search session transcripts in a narrow scope — with the explicit constraint "do not exhaustively read transcripts; only look for content you already suspect is valuable" |
| Phase 3 | **Consolidate** | Write/update memory files, merge new signal into existing topic files (avoiding near-duplicates), convert relative dates into absolute dates (`yesterday` → `2026-04-04`), remove contradictory facts |
| Phase 4 | **Prune and Index** | Update the `MEMORY.md` index (keeping the 200-line / 25KB cap), remove stale pointers, shorten overly verbose entries, resolve contradictions between files |

💡 **Plain English**: This is like cleaning up your desk on the weekend — first see what is on the desk (Orient), then go through this week's sticky notes and find the important ones (Gather), then file the important ones into the right folders (Consolidate), and finally throw away expired notes and update the index (Prune).

Phase 2's "only look for things you already suspect matter" is an important **counterintuitive constraint** — it prevents the Dream agent from blowing its turn budget by scanning all transcripts. The Dream agent's tool permissions are also tightly limited: Bash can run only read-only commands (`ls`, `find`, `grep`, `cat`, `stat`), file writes can target only memory-directory paths, and MCP tools and Agent tools are disallowed.

#### The actual Dream prompt content

The following is the full output of `buildConsolidationPrompt()` from `consolidationPrompt.ts`. These are the complete instructions the Dream agent receives on every consolidation run:

**Opening frame** (role setting):

> *"You are performing a dream — a reflective pass over your memory files. Synthesize what you've learned recently into durable, well-organized memories so that future sessions can orient quickly."*

The name "Dream" is not just a metaphor — it is a deliberate mapping to the human memory-consolidation mechanism. During sleep, the human brain consolidates short-term memory into long-term memory (transfer from hippocampus to neocortex). Claude's Dream does something similar: it consolidates scattered session memories into structured topic files.

**Phase 2 priority chain** — signal gathering has an explicit source priority:

> 1. *"Daily logs (`logs/YYYY/MM/YYYY-MM-DD.md`) if present — these are the append-only stream"*
> 2. *"Existing memories that drifted — facts that contradict something you see in the codebase now"*
> 3. *"Transcript search — grep the JSONL transcripts for narrow terms"*

Key constraint: *"Don't exhaustively read transcripts. Look only for things you already suspect matter."*

This shows that Dream is not "re-understand everything from scratch." It is "search for new signal with a known direction in mind" — a **hypothesis-driven information retrieval** mode.

**Three consolidation rules in Phase 3**:

> *"Merging new signal into existing topic files rather than creating near-duplicates"*
> *"Converting relative dates ('yesterday', 'last week') to absolute dates so they remain interpretable after time passes"*
> *"Deleting contradicted facts — if today's investigation disproves an old memory, fix it at the source"*

The third rule is especially worth noticing — the Dream agent is allowed to **actively delete incorrect memories**. This is a "forgetting-as-cleanup" design: if new evidence disproves an old memory, deletion is more valuable than keeping it.

**Phase 4 index constraint** — the strict format of `MEMORY.md`:

> *"Each entry should be one line under ~150 characters: `- [Title](file.md) — one-line hook`. Never write memory content directly into it."*

`MEMORY.md` is an **index, not content** — like a library card catalog. It records only the title and location of the book, not excerpts from the book itself. This prevents the index file from bloating into an unmaintainable state.

### 6.4 Consolidation Lock Mechanism

It uses a file lock plus `mtime` fallback to implement safe concurrency control:
- `tryAcquireConsolidationLock()` — acquire the lock
- `rollbackConsolidationLock(priorMtime)` — on failure, roll back `mtime` so the time gate will still pass next time
- Scan throttling (`SESSION_SCAN_INTERVAL_MS = 10 minutes`) prevents repeated scans when the time gate passes but the session gate does not

---

## 7. Core Subsystem Six: Agent Memory

Agent Memory provides each custom Agent with an independent persistent memory space.

### 7.1 Three-Level Scope

Defined in `src/tools/AgentTool/agentMemory.ts`:

```typescript
// src/tools/AgentTool/agentMemory.ts (line 12)
export type AgentMemoryScope = 'user' | 'project' | 'local'
```

| Scope | Path | Explanation |
|:---:|:---:|:---:|
| `user` | `~/.claude/agent-memory/<agentType>/` | Global across projects |
| `project` | `.claude/agent-memory/<agentType>/` | Project-level, can be committed to VCS |
| `local` | `.claude/agent-memory-local/<agentType>/` | Local only, not committed |

### 7.2 Snapshot Sync

`checkAgentMemorySnapshot()` in `agentMemorySnapshot.ts` (lines 98-144) implements snapshot-driven initialization: it checks `.claude/agent-memory-snapshots/<agentType>/snapshot.json`, compares the sync time in `.snapshot-synced.json`, and returns one of three states (`none` / `initialize` / `prompt-update`).

---

## 8. Core Subsystem Seven: Team Memory

Team Memory is a subdirectory of Auto Memory (`<memdir>/team/`) and implements team-shared memory.

### 8.1 Path Safety

`validateTeamMemWritePath()` in `teamMemPaths.ts` (lines 228-256) performs **two-pass validation** to prevent path traversal (PSR M22186):
1. `path.resolve()` — eliminate `..` segments (quickly reject obvious traversal)
2. `realpathDeepestExisting()` (lines 109-171) — resolve the real path of the deepest existing ancestor (prevents symlink escape)

The latter also uses `lstat()` to distinguish "truly does not exist" from a "dangling symlink" (the link exists but its target does not) — dangling symlinks are an attack vector because `writeFile` will follow the link and create the target file outside `teamDir`.

### 8.2 Combined-Mode Prompt

When Auto Memory and Team Memory are both enabled, the system uses `TYPES_SECTION_COMBINED` — each memory type includes a `<scope>` tag to guide where it should be stored:

- `user` type --> always private
- `feedback` type --> default to private; store in team only for explicit project-wide conventions
- `project` type --> strongly bias toward team
- `reference` type --> usually team

---

## 9. Memory File Detection and Prompt Engineering

`src/utils/memoryFileDetection.ts` is the "gatekeeper" of the memory system. `isAutoManagedMemoryFile()` (lines 133-147) checks all automatically managed memory files (excluding user-managed `CLAUDE.md`) and is used for UI folding/badge logic. `isShellCommandTargetingMemory()` (lines 215-271) detects whether a shell command targets memory files, supporting Windows, MinGW, and POSIX path formats.

### 9.1 Recall Guidance

`TRUSTING_RECALL_SECTION` (`memoryTypes.ts`, lines 240-256) was validated through evals:

```
"A memory says X exists" does not mean "X still exists now"

- If the memory mentions a file path: check whether the file exists
- If the memory mentions a function or flag: grep for it
- If the user will take action based on your recommendation: verify first
```

Eval result: 3/3 passed when placed in `appendSystemPrompt`; 0/3 passed when used as bullet points inside other sections. **Placement matters**.

### 9.2 Ignore Instructions

```
If the user says to "ignore" or "not use" memory: behave as if MEMORY.md were empty.
Do not apply memory facts, references, comparisons, or mention memory content.
```

This fixes a specific failure mode (evals #22856, case 5): the user says "ignore the memory about X" --> Claude reads the code correctly but adds "not Y as the memory said" — interpreting "ignore" as "acknowledge it and override it" rather than "do not reference it at all."

---

> 📖 **Further Reading**: This chapter focuses on the single-user memory system. When multiple team members need to share AI memory (for example project-level knowledge synchronization), Claude Code also has a separate **team memory sync system** (2,167 lines), which implements Pull/Push sync, key scanning, ETag optimistic concurrency, and more through the Anthropic API. See **Part 3 "Complete Analysis of Team Memory Sync"** for details.

## 10. Critical Analysis

### 10.1 Design Strengths

**1. The discipline of the type taxonomy**

The four-type classification (`user` / `feedback` / `project` / `reference`), together with the reverse constraint of "what should not be saved," effectively prevents memory bloat. The decision to forbid storing derivable information (code patterns, git history) follows the database-design principle that **derived data should not be persisted** (similar to avoiding computed fields in database normalization) — those facts already have more authoritative sources.

**2. Mutual exclusion between main agent and background agent**

The `hasMemoryWritesSince()` check ensures that the same range of messages is not processed twice. This is simple and effective concurrency control.

**3. Freshness awareness**

Memory is not "truth" — it is "an observation made at a particular moment." That design philosophy runs through the whole system, from `memoryFreshnessText()` to `TRUSTING_RECALL_SECTION`, and it effectively reduces the problem of stale memories being treated as authoritative.

**4. Defense in depth for security**

Team Memory's two-pass path validation (`resolve` + `realpath`) plus dangling-symlink detection, Unicode-normalization attack defense, and trusted-setting-source chain (excluding `projectSettings` to prevent malicious repositories) reflect mature security-engineering practice. These are industry-standard defensive techniques for security-sensitive file operations (OWASP path-traversal guidance recommends similar layered validation), and Claude Code's implementation is relatively comprehensive.

### 10.2 Design Tradeoffs and Potential Problems

**1. Cost and latency of Sonnet side queries**

Every user query causes `findRelevantMemories()` to use the Sonnet model for a side query to select relevant memories. That adds:
- Latency: one extra API round trip (even with `max_tokens: 256`)
- Cost: one extra API call per conversation turn
- Single point of failure: if the side query times out or fails, no deep memory will be recalled

A possible improvement would be local vector search or an embedding cache, but that would increase client-side complexity.

**2. The fragility of the `MEMORY.md` index**

`MEMORY.md` is a manually maintained (by AI) index file, and it carries a risk of format drift. If both the background agent and the main agent update the index, inconsistencies may appear. The `skipIndex` mode behind the `tengu_moth_copse` gate suggests the team is already experimenting with a path that removes the manual index entirely.

**3. Session Memory's 1-second polling**

`waitForSessionMemoryExtraction()` uses `sleep(1000)` polling. It has double-timeout protection, but in fast-moving conversations, one-second polling may not be responsive enough. A Promise or EventEmitter approach would be cleaner.

**4. The implicit constraint of the 200-file cap**

`MAX_MEMORY_FILES = 200` is an implicit hard limit. For heavy long-term users, this may become restrictive. The system does not provide an explicit memory aging/archive mechanism (`memoryAge.ts` computes age but does not automatically clean up), so Dream consolidation is the only organizing mechanism.

**5. The opacity of feature-gate flags**

The system uses a large number of GrowthBook feature gates (`tengu_passport_quail`, `tengu_herring_clock`, `tengu_moth_copse`, `tengu_coral_fern`, etc.), and these code names are completely opaque to maintainers. That is standard for A/B testing, but it does make the code harder to understand.

**6. The filesystem vs database tradeoff**

The system chooses pure filesystem storage over SQLite or a vector database. The advantages are transparency (users can directly edit `.md` files), portability (git can track them), and zero dependencies. The downside is lack of indexing, with search efficiency relying on external API calls.

### 10.3 Predicted Evolution Directions

Several clear evolution signals appear in the source:
- `skipIndex` mode (`tengu_moth_copse`): the `MEMORY.md` index may eventually be removed in favor of pure scanning
- Separation between `isExtractModeActive()` and `isAutoMemoryEnabled()`: extraction logic is being decoupled
- The log paradigm in KAIROS mode: a shift from "real-time index maintenance" to "append-only + offline consolidation"
- Heavy investment in Team Memory security: a sign that multi-user collaboration is becoming more important

### 10.4 Deep Comparison with Competing Memory Systems

Claude Code's memory system does not exist in isolation. The following is an architecture-level comparison with mainstream AI memory frameworks:

| Dimension | Claude Code | LangMem / Mem0 | Zep | OpenClaw |
|------|------------|----------------|-----|----------|
| **Storage paradigm** | Pure filesystem (`.md` files + YAML frontmatter) | Vector database + semantic index | Knowledge graph + vector hybrid | Filesystem (CC-like design) |
| **Recall method** | LLM side query (Sonnet scans metadata → selects relevant files) | Embedding semantic search | Hybrid retrieval (semantic + graph traversal) | LLM scanning (CC-like pattern) |
| **Memory types** | Four-type taxonomy (`user` / `feedback` / `project` / `reference`) | Free tags | Entity-relationship model | Simplified type system |
| **Forgetting strategy** | Coarse-grained (24h / 5-session cycle + Dream consolidation) | TTL + importance decay | Graph pruning + decreasing node weights | CC-like mode |
| **Association network** | None (flat file list) | Yes (nearest neighbors in vector space) | Yes (knowledge-graph edges) | None |
| **Offline consolidation** | `autoDream` / KAIROS nightly consolidation | No | Background graph updates | No |

**Claude Code's distinctive positioning**: CC chooses an "execution-oriented" path — more like an engineer debugging something, looking, editing, and running as it goes. Compared with vector-database approaches (LangMem/Mem0), CC's filesystem approach sacrifices semantic recall precision (200-file cap + LLM scanning vs hundreds of thousands of vector entries), but gains full transparency (users can edit `.md` directly) and zero external dependencies. That tradeoff reflects CC's design philosophy as a terminal tool: everything should be self-contained.

**CC's weakness**: It does not support semantic recall (it uses an LLM to scan metadata, capped at 200 files), its forgetting strategy is coarse (24h / 5-session cycle), and it has no association network. As user memory volume grows, these limitations will become more visible.

---

## 11. Code Map

The following lists all key files and core line ranges covered in this chapter:

### Memory Directory (`memdir`)
| File | Core Function / Constant | Line Range |
|:---|:---|:---:|
| `src/memdir/paths.ts` | `isAutoMemoryEnabled()` | 30-55 |
| `src/memdir/paths.ts` | `getAutoMemPath()` | 223-235 |
| `src/memdir/paths.ts` | `validateMemoryPath()` | 109-150 |
| `src/memdir/paths.ts` | `isExtractModeActive()` | 69-77 |
| `src/memdir/paths.ts` | `getAutoMemDailyLogPath()` | 246-251 |
| `src/memdir/memdir.ts` | `truncateEntrypointContent()` | 57-103 |
| `src/memdir/memdir.ts` | `buildMemoryLines()` | 199-266 |
| `src/memdir/memdir.ts` | `buildMemoryPrompt()` | 272-316 |
| `src/memdir/memdir.ts` | `buildAssistantDailyLogPrompt()` | 327-370 |
| `src/memdir/memdir.ts` | `loadMemoryPrompt()` | 419-507 |
| `src/memdir/memdir.ts` | `ensureMemoryDirExists()` | 129-147 |
| `src/memdir/memoryScan.ts` | `scanMemoryFiles()` | 35-77 |
| `src/memdir/memoryScan.ts` | `formatMemoryManifest()` | 84-94 |
| `src/memdir/memoryTypes.ts` | `MEMORY_TYPES` | 14-19 |
| `src/memdir/memoryTypes.ts` | `TYPES_SECTION_COMBINED` | 37-106 |
| `src/memdir/memoryTypes.ts` | `TYPES_SECTION_INDIVIDUAL` | 113-178 |
| `src/memdir/memoryTypes.ts` | `WHAT_NOT_TO_SAVE_SECTION` | 183-195 |
| `src/memdir/memoryTypes.ts` | `TRUSTING_RECALL_SECTION` | 240-256 |
| `src/memdir/memoryTypes.ts` | `WHEN_TO_ACCESS_SECTION` | 216-222 |
| `src/memdir/memoryAge.ts` | `memoryFreshnessText()` | 33-42 |
| `src/memdir/findRelevantMemories.ts` | `findRelevantMemories()` | 39-75 |
| `src/memdir/findRelevantMemories.ts` | `selectRelevantMemories()` | 77-141 |
| `src/memdir/teamMemPaths.ts` | `isTeamMemoryEnabled()` | 73-78 |
| `src/memdir/teamMemPaths.ts` | `validateTeamMemWritePath()` | 228-256 |
| `src/memdir/teamMemPaths.ts` | `realpathDeepestExisting()` | 109-171 |

### Service Layer (`services`)
| File | Core Function / Constant | Line Range |
|:---|:---|:---:|
| `src/services/extractMemories/extractMemories.ts` | `countModelVisibleMessagesSince()` | 82-110 |
| `src/services/extractMemories/extractMemories.ts` | `initExtractMemories()` | 296-587 |
| `src/services/extractMemories/extractMemories.ts` | `hasMemoryWritesSince()` | 121-148 |
| `src/services/extractMemories/extractMemories.ts` | `createAutoMemCanUseTool()` | 171-222 |
| `src/services/extractMemories/prompts.ts` | `buildExtractAutoOnlyPrompt()` | 50-94 |
| `src/services/extractMemories/prompts.ts` | `buildExtractCombinedPrompt()` | 101-154 |
| `src/services/SessionMemory/sessionMemory.ts` | `shouldExtractMemory()` | 134-181 |
| `src/services/SessionMemory/sessionMemory.ts` | `extractSessionMemory` | 272-350 |
| `src/services/SessionMemory/sessionMemory.ts` | `initSessionMemory()` | 357-375 |
| `src/services/SessionMemory/sessionMemory.ts` | `createMemoryFileCanUseTool()` | 460-482 |
| `src/services/SessionMemory/sessionMemoryUtils.ts` | `DEFAULT_SESSION_MEMORY_CONFIG` | 32-36 |
| `src/services/SessionMemory/sessionMemoryUtils.ts` | `waitForSessionMemoryExtraction()` | 89-105 |
| `src/services/SessionMemory/sessionMemoryUtils.ts` | `getSessionMemoryContent()` | 110-126 |
| `src/services/compact/sessionMemoryCompact.ts` | `DEFAULT_SM_COMPACT_CONFIG` | 57-61 |
| `src/services/compact/sessionMemoryCompact.ts` | `calculateMessagesToKeepIndex()` | 324-397 |
| `src/services/compact/sessionMemoryCompact.ts` | `adjustIndexToPreserveAPIInvariants()` | 232-314 |
| `src/services/compact/sessionMemoryCompact.ts` | `trySessionMemoryCompaction()` | 514-630 |
| `src/services/autoDream/autoDream.ts` | `isGateOpen()` | 95-100 |
| `src/services/autoDream/autoDream.ts` | `initAutoDream()` | 122-273 |

### Agent Memory
| File | Core Function / Constant | Line Range |
|:---|:---|:---:|
| `src/tools/AgentTool/agentMemory.ts` | `AgentMemoryScope` | 12 |
| `src/tools/AgentTool/agentMemory.ts` | `getAgentMemoryDir()` | 52-65 |
| `src/tools/AgentTool/agentMemory.ts` | `loadAgentMemoryPrompt()` | 138-177 |
| `src/tools/AgentTool/agentMemorySnapshot.ts` | `checkAgentMemorySnapshot()` | 98-144 |

### Tooling Layer and UI
| File | Core Function / Constant | Line Range |
|:---|:---|:---:|
| `src/utils/forkedAgent.ts` | `CacheSafeParams` type | 57-68 |
| `src/utils/forkedAgent.ts` | `saveCacheSafeParams()` / `getLastCacheSafeParams()` | 75-81 |
| `src/utils/forkedAgent.ts` | `ForkedAgentParams` type | 83-113 |
| `src/utils/memoryFileDetection.ts` | `isAutoManagedMemoryFile()` | 133-147 |
| `src/utils/memoryFileDetection.ts` | `isShellCommandTargetingMemory()` | 215-271 |
| `src/utils/memoryFileDetection.ts` | `memoryScopeForPath()` | 106-114 |
| `src/utils/claudemd.ts` | Load-order comment | 1-25 |
| `src/commands/memory/memory.tsx` | `MemoryCommand` | 14-82 |
| `src/components/memory/MemoryUpdateNotification.tsx` | `MemoryUpdateNotification` | 21-44 |
