Analysis of Claude Code’s Short- and Long-Term Memory Mechanisms

A source-code reconstruction project based on @anthropic-ai/claude-code@2.1.88

Core files: services/SessionMemory/, services/extractMemories/, services/teamMemorySync/, services/compact/sessionMemoryCompact.ts, utils/attachments.ts


Overview

Claude Code’s memory system isn’t a single “memory” feature — it’s a memory architecture with multiple layers, multiple scopes, and multiple lifecycles. It breaks down into:

Layer System name Scope Lifecycle File format
Short term (seconds–minutes) In-query context Single turn One API call Message array
Short term (session level) Session Memory Single session End of session .claude/session-memory.md
Medium term (project-level persistence) Auto Memory / Durable Memory Project directory Persisted to disk .claude/projects/<path>/memory/*.md
Long term (team-level persistence) Team Memory Git repository (whole organization) Cloud sync + local persistence ~/.claude/team-memory/<repo>/*
System level (config-level persistence) CLAUDE.md Project/user directory Static file, loaded every time CLAUDE.md

The core design philosophy of the whole system: information at different layers accumulates on different time scales, is extracted at different trigger points, and is injected into the AI’s context on demand — rather than all being dumped in at once.


1. Short-Term Memory: Session Memory

Session Memory is a session-level scratchpad that records where the current session has gotten to, what was done, and what problems came up. It’s essentially a structured Markdown file that a background AI updates periodically.

1.1 Storage Location and Format

.claude/
└── session-memory.md         # 会话记忆文件

File template (9 fixed sections):

# Session Title
_A short and distinctive 5-10 word descriptive title_

# Current State
_What is actively being worked on right now?_

# Task specification
_What did the user ask to build?_

# Files and Functions
_What are the important files?_

# Workflow
_What bash commands are usually run and in what order?_

# Errors & Corrections
_Errors encountered and how they were fixed._

# Codebase and System Documentation
_What are important system components?_

# Learnings
_What has worked well? What has not?_

# Key results
_Exact output the user requested (table, answer, etc.)_

# Worklog
_Step by step, what was attempted, done?_
  • Each section has a cap: MAX_SECTION_LENGTH = 2000 characters/tokens
  • Total file cap: MAX_TOTAL_SESSION_MEMORY_TOKENS = 12000 tokens
  • When the cap is exceeded, the extracting AI automatically condenses overly long sections

Users can customize the template: ~/.claude/session-memory/config/template.md

1.2 Trigger Mechanism

Session Memory runs as a Post-Sampling Hook — triggered asynchronously after each AI response completes. But it doesn’t fire every time; there are three gates:

// 默认配置
DEFAULT_SESSION_MEMORY_CONFIG = {
  minimumMessageTokensToInit: 10_000,       // 上下文达到 10K token 时才初始化
  minimumTokensBetweenUpdate: 5_000,        // 每次更新后至少增长 5K tokens
  toolCallsBetweenUpdates: 3,               // 至少 3 次工具调用
}

Trigger conditions (any one of the following):

const shouldExtract =
  (hasMetTokenThreshold && hasMetToolCallThreshold) ||  // 条件 1: token 和工具调用都超标
  (hasMetTokenThreshold && !hasToolCallsInLastTurn)     // 条件 2: token 超标 + 最后一轮无工具调用(对话自然停顿点)

The clever part of the design:

  • The token threshold is always a hard requirement — even if there are many tool calls, it won’t trigger if the context hasn’t grown
  • Condition 2 captures the “natural pause point in the conversation” — when the AI’s last turn made no tool calls (it probably answered the user), that’s a good moment to extract memory
  • It only runs on repl_main_thread (the main REPL thread), not inside sub-agents

1.3 Update Mechanism

Updates are performed by a forked agent — it copies the parent conversation’s context exactly, shares the prompt cache, but has its own tool-calling permissions:

// 提取代理的权限极其严格:
// 只允许 Edit 工具操作 session-memory.md 这一个文件
function createMemoryFileCanUseTool(memoryPath) {
  return async (tool, input) => {
    if (tool.name === 'Edit' && input.file_path === memoryPath) {
      return { behavior: 'allow' }
    }
    return { behavior: 'deny' }  // 其他一切拒绝
  }
}

Update flow:

  1. Check whether the trigger conditions are met (token threshold + tool call count)
  2. Read the current session memory file
  3. Launch the forked agent and send the update prompt
  4. The forked agent updates the file with the Edit tool
  5. Record lastSummarizedMessageId — marks which message the memory has covered up to

1.4 Key Data Flow

每次 AI 响应 (PostSamplingHook)
      │
      ▼
shouldExtractMemory(messages)?
      │  No → 直接返回
      │  Yes ↓
      ▼
runForkedAgent(
  prompt: "请基于最新对话更新 session notes",
  tools: 只允许 Edit → session-memory.md
)
      │
      ▼
更新 .claude/session-memory.md
      │
      ▼
记录 lastSummarizedMessageId ← 知道记忆覆盖到了哪里

2. Medium-Term Memory: Auto Memory / Durable Memory

Auto Memory is project-level persistent knowledge that records lessons learned across sessions, user preferences, understanding of the code structure, and so on. Unlike Session Memory, it isn’t lost when a session ends.

2.1 Storage Location and Format

.claude/projects/<project-path>/
└── memory/
    ├── MEMORY.md              # 索引文件(每次启动必加载)
    ├── user_role.md           # 用户角色记忆
    ├── feedback_testing.md    # 反馈记忆
    ├── project_auth_flow.md   # 项目特定知识
    └── ...                    # 按主题组织的独立文件

MEMORY.md — the index file (not memory content):

- [User Role](user_role.md) — 用户是数据科学家,专注于 observability
- [Feedback on Testing](feedback_testing.md) — 集成测试必须用真实数据库
- [Project Auth Rewrite](project_auth_flow.md) — 认证重写由法律合规驱动
  • Each line is within ~150 characters
  • Anything beyond 200 lines gets truncated
  • It stores only pointers, not content

Format of a single memory file:

---
name: feedback_testing
description: 集成测试必须用真实数据库
type: feedback
---

集成测试必须访问真实数据库,而不是 mock。

**Why:** 上季度因为 mock/生产不一致导致通过的测试在生产迁移时失败
**How to apply:** 编写涉及数据库的测试时,使用真实连接

2.2 The Four Types of Memory

enum MemoryType {
  'user',      // 用户角色、偏好、知识背景
  'feedback',  // 用户给出的指导(做什么/不做什么)
  'project',   // 项目目标、里程碑、技术决策
  'reference'  // 外部资源指针(Linear 项目、Grafana 面板等)
}

Each type has its own when_to_save and how_to_use guidance, training the AI to judge when to save and how to use it.

2.3 Extraction Mechanism: Extract Memories

Unlike Session Memory’s active updates, Auto Memory accumulates automatically via a background extraction agent:

// 注册时机:每个完整 query loop 结束后(AI 没有 pending 工具调用时)
// 通过 handleStopHooks 触发
export function initExtractMemories() {
  // ...
}

export async function executeExtractMemories(context) {
  // fire-and-forget,不阻塞主流程
}

Throttling controls:

// 不是每轮都提取,默认每隔 N 轮提取一次(N 由 GrowthBook 远程配置)
const turnsBetweenExtraction = getFeatureValue('tengu_bramble_lintel', 1)

// 如果 AI 自己已经写了记忆文件(main agent 直接执行了 save 操作),
// 则跳过 forked agent 的提取(避免重复劳动)
if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
  return // 跳过,推进游标
}

The extraction agent’s permission model (far more permissive than Session Memory):

Tool Permission Notes
Read / Grep / Glob Fully open Read-only tools, no risk
Bash Read-only commands only ls, find, grep, cat, stat, wc, head, tail
Edit / Write memory directory only Can only write memory/*.md
All other tools Denied MCP, Agent, rm, etc.

Mutual exclusion design: the main agent and the extraction agent never write memory at the same time — if the main agent has already written memory (the AI saves directly when the user says “remember this”), the extraction agent skips that round and advances the cursor past the main agent’s write. This avoids the two competing.

// 提取代理只在主代理没有写记忆时才运行
if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
  // 跳过,推进游标——下一轮只考虑这之后的新消息
  lastMemoryMessageUuid = lastMessage.uuid
  return
}

2.4 Extraction Strategy: Two-Phase Parallel Writes

The extraction agent is designed around a minimal number of turns strategy:

Turn 1: 并行读取所有可能需要更新的文件 (Read × N)
Turn 2: 并行写入所有需要更新的文件 (Write/Edit × N)

禁止在多个 turn 中交错读写 —— 浪费上下文

The extraction agent’s prompt states explicitly:

“You have a limited turn budget. Edit requires a prior Read of the same file, so the efficient strategy is:

  • Turn 1 — issue all Read calls in parallel for every file you might update
  • Turn 2 — issue all Write/Edit calls in parallel
    Do not interleave reads and writes across multiple turns.”

It also caps maxTurns: 5 — it’s forcibly terminated after more than 5 turns.


3. Long-Term Memory: Team Memory

Team Memory is organization-level knowledge sharing, identified by Git repository: all authenticated members of the same repository share the same set of memory files.

3.1 Storage Location

~/.claude/team-memory/<owner>/<repo>/
├── MEMORY.md
├── coding_standards.md
├── deployment_process.md
└── ...

3.2 Sync Protocol

Team Memory syncs between local files and a server API:

本地文件系统                    服务器 API
     │                           │
     │  GET /team_memory?repo=   │  拉取(下载全量)
     │◄─────────────────────────│
     │                          │
     │  PUT /team_memory?repo=   │  推送(增量上传)
     │────────────────────────▶│
     │                          │

Key sync points:

  • Pull: server content overwrites local (server wins)
  • Push: only files whose content hash differs are uploaded (delta upload)
  • Deletion: a local deletion doesn’t propagate to the server and will be restored on the next pull
  • Conflict retries: at most 2 (based on the ETag lastKnownChecksum)

Sync state management:

type SyncState = {
  lastKnownChecksum: string | null           // ETag,用于条件请求
  serverChecksums: Map<string, string>       // 每个文件内容的 sha256 哈希
  serverMaxEntries: number | null            // 服务器最大条目数(从 413 响应学习)
}

3.3 Privacy Protection: Secret Scanning

Secrets are scanned automatically before pushing, preventing sensitive information from leaking into the team space:

// 扫描正则模式
const SECRET_PATTERNS = [
  /(?i)(api[_-]?key|api[_-]?secret)/,
  /(?i)(password|passwd|pwd)/,
  /(?i)(token|auth|credential)/,
  // ...
]

// 超过 250KB 的文件不上传
const MAX_FILE_SIZE_BYTES = 250_000

3.4 Permission Control

The extraction agent can write to both private memory and team memory, but team memory has stricter constraints:

  • Never save API keys, credentials, or other sensitive information
  • Each file type has a <scope> guiding which directory it should be written to
  • Pushes are authenticated with an OAuth token

4. System-Level Configuration Memory: CLAUDE.md

CLAUDE.md is a static configuration file — not generated automatically by the AI, but maintained by hand by the user/team:

.claude/CLAUDE.md                 # 项目级
~/.claude/CLAUDE.md              # 用户级

This is content that is always loaded at the start of every conversation, injected as part of the system prompt. Unlike the dynamically generated Session Memory and Auto Memory, CLAUDE.md is deterministic and version-controllable.


5. How Memory Gets Injected into the AI Context

Memory isn’t all stuffed into the prompt; it’s injected on demand, in layers, through the attachment mechanism.

5.1 Load Timing

On startup, the system scans all memory directories:

1. 加载 CLAUDE.md (静态配置)
2. 加载 memory/MEMORY.md (索引 → 按需加载具体文件)
3. 加载 session-memory.md (当前会话笔记)
4. 如果是团队模式,加载 team memory

5.2 Re-injecting Memory After Compaction

When Auto Compact triggers, all messages are replaced with a summary — but memory is not lost:

// compactConversation() 成功后:
// 1. 清除 fileStateCache
// 2. 重新注入关键上下文:
postCompactFileAttachments = [
  ...最近修改的文件快照(最多5个),
  ...异步Agent状态,
  ...plan文件,
  ...已调用的技能内容(25K token预算),
  ...MCP工具增量,
  // 记忆通过 attachment 机制自动重新注入
]

5.3 Session Memory Compact — Lightweight Compaction

When memory exists and has real substance (not just the template), the system tries memory-based compaction, which is cheaper than full AI-summary compaction:

async function trySessionMemoryCompaction(messages, agentId, threshold) {
  // 1. 等待任何正在进行的记忆提取完成
  await waitForSessionMemoryExtraction()

  // 2. 读取 session memory 内容
  const sessionMemory = await getSessionMemoryContent()
  if (!sessionMemory || isEmpty(sessionMemory)) return null // 降级到 legacy compact

  // 3. 找到 lastSummarizedMessageId 对应的消息索引
  const lastSummarizedIndex = messages.findIndex(
    msg => msg.uuid === lastSummarizedMessageId
  )

  // 4. 调整索引保护 API 不变性:
  //    - 确保保留 tool_use / tool_result 配对
  //    - 确保保留 thinking blocks 与同 message.id 的消息合并
  const startIndex = adjustIndexToPreserveAPIInvariants(
    messages, lastSummarizedIndex + 1
  )

  // 5. 保留 startIndex 之后的新消息 + session memory 摘要
  //    这样新消息有完整上下文,旧信息在 session memory 中有摘要

  // 6. 预算检查:最终 token 数必须在 [minTokens, maxTokens] 范围内
  //    默认:10K - 40K tokens
}

This is far cheaper than a full compactConversation (which launches a forked agent to summarize the whole conversation), because:

  • No extra AI call is needed to generate a summary (session memory is already a ready-made summary)
  • Tokens go from ~167K down to ~10-40K (keeping the most recent conversation + using session memory in place of a summary of the older conversation)

6. The Complete Memory Lifecycle: How Information Flows

用户在对话中提供信息
        │
        ├── 显式指令 ("记住这个") ──→ AI 直接写入 Auto Memory
        │                              同时更新 MEMORY.md 索引
        │
        ├── 隐性经验 (错误修正、偏好) ──→ extractMemories 后台代理
        │                                  每 N 轮自动扫描对话
        │                                  识别值得保存的信息
        │                                  写入对应的 .md 文件
        │
        ├── 会话进行中的状态 ──→ Session Memory (PostSamplingHook)
        │                          定期(每 5K token 或 3 次工具调用)
        │                          由 forked agent 更新笔记
        │
        └── 团队级知识 ──→ Team Memory
                             与 Auto Memory 相同的文件结构
                             但存储到 ~/.claude/team-memory/<repo>/
                             自动同步到服务器共享给团队成员
        │
        ▼
下次会话启动
        │
        ├── CLAUDE.md 必加载 ← 确定性配置
        ├── memory/MEMORY.md 索引加载 ← 持久知识(跨会话)
        ├── team/MEMORY.md 索引加载 ← 团队知识(跨成员)
        ├── session-memory.md 如果存在 ← 上次会话的临时笔记
        │
        ▼
AI 带着所有记忆开始新对话

7. Key Design Principles and Implementation Details

7.1 Memory and Compaction Working Together

Session Memory was originally designed to replace Auto Compact — if you already have a session memory summary, there’s no need to waste another AI call on a whole-conversation summary:

// autoCompactIfNeeded() 流程:
// 1. 先尝试 Session Memory Compact(不需要额外 AI 调用)
const sessionMemoryResult = await trySessionMemoryCompaction(...)
if (sessionMemoryResult) {
  return { wasCompacted: true } // 成功!不需要完整压缩
}

// 2. 如果 session memory 不可用或没有内容,才做完整压缩
const compactionResult = await compactConversation(...)

Circuit breaking and fallback:

  • Session Memory is empty (template only) → fall back to legacy compact
  • lastSummarizedMessageId can’t be found (the message got compacted away) → fall back to legacy compact
  • Budget check fails → fall back to legacy compact

7.2 The Design of the Index (MEMORY.md)

- [Title](file.md) — one-line hook

This design has several key benefits:

  1. Low-cost loading: the index file is small (200 lines × 150 characters ≈ 7.5K characters) and can be parsed quickly
  2. Content loaded on demand: there’s no need to load every memory file at once; files are only read when relevant
  3. User-auditable: the index file is readable, and users can delete entries they don’t need
  4. Semantic organization: organized by topic, not sorted by time
  5. Self-healing: the AI can update or delete outdated entries

7.3 Cursors and Incremental Extraction

The extraction agent doesn’t scan the entire conversation history every time:

// 游标机制:lastMemoryMessageUuid
// 每次提取只处理游标之后的新消息
const newMessageCount = countModelVisibleMessagesSince(messages, lastMemoryMessageUuid)

// 如果游标对应的消息被压缩删除了(找不到 UUID),回退到全量扫描
if (!foundStart) {
  return count(messages, isModelVisibleMessage) // 兜底策略
}

This ensures the extraction agent only processes incremental content each time, keeping costs under control.

7.4 Coalescing Parallel Calls

When multiple triggers arrive at nearly the same time, multiple extraction agents aren’t launched:

if (inProgress) {
  // 有提取正在进行 → 保存上下文,等当前结束后做一次 trailing extraction
  pendingContext = { context, appendSystemMessage }
  return
}

// 当前提取结束后:
const trailing = pendingContext
pendingContext = undefined
if (trailing) {
  await runExtraction({ context: trailing.context, isTrailingRun: true })
  // trailing run 使用新的游标(上次已推进),只处理间隔期的新消息
}

7.5 Sensitive Information Protection

// Team Memory 推送前扫描密钥
async function scanForSecrets(content: string): Promise<boolean> {
  // 正则匹配 API key、password、token 等模式
  // 发现疑似密钥 → 跳过该文件上传
}

// 超过 250KB 的文件不上传到团队空间
if (fileSize > MAX_FILE_SIZE_BYTES) {
  skippedFiles.push({ path, reason: 'too_large' })
}

8. Takeaways for Low-Code Development Agent Teams

Takeaway 1: A Memory System Spanning Multiple Time Scales

Claude Code’s memory isn’t “one brain” — it’s several systems on different time scales:

System Retention period Update frequency Trigger
Session Memory During the session Every 5K tokens / 3 tool calls PostSampling Hook
Auto Memory Permanent Every N conversation turns Stop Hook
Team Memory Permanent + cloud sync Managed alongside Auto Memory Same as above + scheduled push
CLAUDE.md Permanent Manual Loaded at startup

Application: a low-code platform should have a similar memory layering — intermediate workflow state (short term), project experience (medium term), and organizational best practices (long term) should have different storage and lifecycles.

Takeaway 2: Dual Paths — Background Extraction + Direct Foreground Saving

There are two paths for writing memory:

  1. Direct foreground saving: the AI writes directly when the user says “remember this”
  2. Automatic background extraction: after the conversation ends, the extraction agent analyzes and extracts

The two are mutually exclusive via hasMemoryWritesSince — avoiding duplicated work.

Application: in a low-code platform, users should be able to save knowledge directly, while the system should also have background auto-extraction capabilities (learning from execution logs, error patterns, and process changes).

Takeaway 3: An Architecture That Separates Index from Content

The design separates MEMORY.md (the index) from the .md files (the content):

  • The index is always loaded; content is loaded on demand
  • The index is small (200 lines × 150 characters) and content is read on demand
  • Users can review and edit the index

Application: if a low-code platform has a large number of learnable “patterns” or “rules,” it should use a two-level index + content structure rather than loading all patterns at once.

Takeaway 4: Memory Itself Can Replace Compaction

Session Memory is a cheap substitute for Auto Compact — it’s already an AI-generated structured summary, so there’s no need to spin up another forked agent to generate one.

Application: if a low-code platform has accumulated workflow execution logs/notes, it can use those notes directly as a summary when it needs to “trim the context,” without an extra AI call.

Takeaway 5: A Minimal-Permission Model for Memory Writes

The extraction agent is granted only the minimum necessary permissions:

  • Read-only tools are unrestricted (Read/Grep/Glob, read-only Bash commands)
  • Write tools are strictly confined to the memory directory
  • Everything else is denied

Application: an agent that does automatic learning/memory in a low-code platform should have similarly restricted permissions — it can read context information and write memory files, but it can’t perform operations that modify business data.

Takeaway 6: Incremental Extraction and Cursor Management

The extraction agent only processes new messages via the lastMemoryMessageUuid cursor; if the message the cursor points to was removed by compaction, it falls back to a full scan. This is the standard pattern for incremental consumption.

Application: any continuous learning system based on conversation history needs cursor management — remembering where it last got to, plus a fallback strategy for when the history is modified/deleted.

Takeaway 7: Compaction and Truncation of Memory

Session Memory has hard caps (12K tokens total, 2K tokens per section); during extraction the AI automatically truncates overly long sections and prioritizes keeping Current State and Errors & Corrections.

Application: a low-code platform’s learning module should have capacity management — when the knowledge base gets too large, the AI should prioritize which information to keep and which to discard. This requires an explicit content strategy and truncation rules.

Takeaway 8: Version Control and Security for Team Knowledge

Team Memory uses the Git repository as its identifier, does ETag-based incremental sync, automatically scans for secrets before pushing, imposes a 200KB limit per push, and uploads large files in batches.

Application: if a low-code platform has a concept of “team-level knowledge,” it needs similar:

  • Incremental sync based on content hashes (avoiding full transfers)
  • Conflict detection and retry
  • Automatic filtering of sensitive information
  • Push size limits