Claude Code 长短记忆机制分析
基于
@anthropic-ai/claude-code@2.1.88源码还原项目核心文件:
services/SessionMemory/,services/extractMemories/,services/teamMemorySync/,services/compact/sessionMemoryCompact.ts,utils/attachments.ts
概述
Claude Code 的记忆系统不是单一的”记忆”功能,而是一个多层、多作用域、多生命周期的记忆体系。整体分为:
| 层次 | 系统名称 | 作用域 | 生命周期 | 文件格式 |
|---|---|---|---|---|
| 短期(秒级-分钟) | Query 内上下文 | 单轮对话 | 一次 API 调用 | 消息数组 |
| 短期(会话级) | Session Memory | 单次会话 | 会话结束 | .claude/session-memory.md |
| 中期(项目级持久) | Auto Memory / Durable Memory | 项目目录 | 持久化到磁盘 | .claude/projects/<path>/memory/*.md |
| 长期(团队级持久) | Team Memory | Git 仓库(全组织) | 云端同步+本地持久化 | ~/.claude/team-memory/<repo>/* |
| 系统级(配置级持久) | CLAUDE.md | 项目/用户目录 | 静态文件,每次加载 | CLAUDE.md |
整个系统的核心设计思想是:不同层次的信息在不同时间尺度上积累,在不同的触发点被提取,按需注入到 AI 的上下文中——而不是一次性全塞进去。
一、短期记忆:Session Memory(会话记忆)
Session Memory 是会话级别的临时笔记,记录当前会话进行到了哪里、做了什么、遇到了什么问题。它本质上是一个结构化的 Markdown 文件,由后台 AI 定期更新。
1.1 存储位置与格式
.claude/
└── session-memory.md # 会话记忆文件
文件模板(9 个固定段落):
# 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?_
- 每个段落有上限:
MAX_SECTION_LENGTH = 2000字符/tokens - 文件总上限:
MAX_TOTAL_SESSION_MEMORY_TOKENS = 12000tokens - 超过上限时提取 AI 会自动压缩过长段落
用户可以自定义模板:~/.claude/session-memory/config/template.md
1.2 触发机制
Session Memory 作为 Post-Sampling Hook 运行——每次 AI 响应完成后异步触发。但不是每次都触发,有三重门槛:
// 默认配置
DEFAULT_SESSION_MEMORY_CONFIG = {
minimumMessageTokensToInit: 10_000, // 上下文达到 10K token 时才初始化
minimumTokensBetweenUpdate: 5_000, // 每次更新后至少增长 5K tokens
toolCallsBetweenUpdates: 3, // 至少 3 次工具调用
}
触发条件(满足以下任一即可):
const shouldExtract =
(hasMetTokenThreshold && hasMetToolCallThreshold) || // 条件 1: token 和工具调用都超标
(hasMetTokenThreshold && !hasToolCallsInLastTurn) // 条件 2: token 超标 + 最后一轮无工具调用(对话自然停顿点)
设计精妙之处:
- Token 阈值 始终 是硬性要求——即使工具调用很多,如果上下文没增长也不触发
- 条件 2 捕获 “对话自然停顿点”——当 AI 最后一轮没有调用工具(可能是回答了用户),这是提取记忆的好时机
- 只在
repl_main_thread(主 REPL 线程)运行,不在子 agent 中运行
1.3 更新机制
更新由一个 forked agent(分叉 AI 代理) 执行——完美拷贝父级对话的上下文,共享 prompt cache,但拥有独立的工具调用权限:
// 提取代理的权限极其严格:
// 只允许 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' } // 其他一切拒绝
}
}
更新流程:
- 检查是否满足触发条件(token 门槛 + 工具调用次数)
- 读取当前的 session memory 文件
- 启动 forked agent,发送更新 prompt
- Forked agent 用 Edit 工具更新文件
- 记录
lastSummarizedMessageId——标记记忆覆盖到了哪条消息
1.4 关键数据流
每次 AI 响应 (PostSamplingHook)
│
▼
shouldExtractMemory(messages)?
│ No → 直接返回
│ Yes ↓
▼
runForkedAgent(
prompt: "请基于最新对话更新 session notes",
tools: 只允许 Edit → session-memory.md
)
│
▼
更新 .claude/session-memory.md
│
▼
记录 lastSummarizedMessageId ← 知道记忆覆盖到了哪里
二、中期记忆:Auto Memory / Durable Memory(自动持久记忆)
Auto Memory 是项目级别的持久化知识,记录跨会话的经验教训、用户偏好、代码结构理解等。与 Session Memory 不同,它不会在会话结束后丢失。
2.1 存储位置与格式
.claude/projects/<project-path>/
└── memory/
├── MEMORY.md # 索引文件(每次启动必加载)
├── user_role.md # 用户角色记忆
├── feedback_testing.md # 反馈记忆
├── project_auth_flow.md # 项目特定知识
└── ... # 按主题组织的独立文件
** MEMORY.md —— 索引文件(非记忆内容):**
- [User Role](user_role.md) — 用户是数据科学家,专注于 observability
- [Feedback on Testing](feedback_testing.md) — 集成测试必须用真实数据库
- [Project Auth Rewrite](project_auth_flow.md) — 认证重写由法律合规驱动
- 每行 ~150 字符以内
- 超过 200 行会被截断
- 只存储指针,不存储内容
单个记忆文件格式:
---
name: feedback_testing
description: 集成测试必须用真实数据库
type: feedback
---
集成测试必须访问真实数据库,而不是 mock。
**Why:** 上季度因为 mock/生产不一致导致通过的测试在生产迁移时失败
**How to apply:** 编写涉及数据库的测试时,使用真实连接
2.2 记忆的四种类型
enum MemoryType {
'user', // 用户角色、偏好、知识背景
'feedback', // 用户给出的指导(做什么/不做什么)
'project', // 项目目标、里程碑、技术决策
'reference' // 外部资源指针(Linear 项目、Grafana 面板等)
}
每种类型有不同的 when_to_save 和 how_to_use 指南,训练 AI 判断何时保存、如何使用。
2.3 提取机制:Extract Memories
与 Session Memory 的主动更新不同,Auto Memory 是通过 后台提取代理 自动积累的:
// 注册时机:每个完整 query loop 结束后(AI 没有 pending 工具调用时)
// 通过 handleStopHooks 触发
export function initExtractMemories() {
// ...
}
export async function executeExtractMemories(context) {
// fire-and-forget,不阻塞主流程
}
节流控制:
// 不是每轮都提取,默认每隔 N 轮提取一次(N 由 GrowthBook 远程配置)
const turnsBetweenExtraction = getFeatureValue('tengu_bramble_lintel', 1)
// 如果 AI 自己已经写了记忆文件(main agent 直接执行了 save 操作),
// 则跳过 forked agent 的提取(避免重复劳动)
if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
return // 跳过,推进游标
}
提取代理的权限模型(比 Session Memory 宽松得多):
| 工具 | 权限 | 说明 |
|---|---|---|
| Read / Grep / Glob | 完全开放 | 只读工具,无风险 |
| Bash | 仅只读命令 | ls, find, grep, cat, stat, wc, head, tail |
| Edit / Write | 仅限 memory 目录 | 只能写 memory/*.md |
| 其他所有工具 | 拒绝 | MCP, Agent, rm 等 |
互斥设计: 主 agent 和提取代理不会同时写入记忆——如果主 agent 已经写了记忆(用户说”记住这个”时 AI 直接保存),提取代理会跳过该轮,推进游标到主 agent 写入之后。这避免了两者竞争。
// 提取代理只在主代理没有写记忆时才运行
if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
// 跳过,推进游标——下一轮只考虑这之后的新消息
lastMemoryMessageUuid = lastMessage.uuid
return
}
2.4 提取策略:两阶段并行写入
提取代理被设计为最少 turn 数的策略:
Turn 1: 并行读取所有可能需要更新的文件 (Read × N)
Turn 2: 并行写入所有需要更新的文件 (Write/Edit × N)
禁止在多个 turn 中交错读写 —— 浪费上下文
提取代理 prompt 中明确指示:
“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.”
同时限制 maxTurns: 5——超过 5 轮强制终止。
三、长期记忆:Team Memory(团队记忆)
Team Memory 是组织级别的知识共享,通过 Git 仓库识别,同一个仓库的所有认证成员共享同一套记忆文件。
3.1 存储位置
~/.claude/team-memory/<owner>/<repo>/
├── MEMORY.md
├── coding_standards.md
├── deployment_process.md
└── ...
3.2 同步协议
Team Memory 在本地文件和服务器 API 之间同步:
本地文件系统 服务器 API
│ │
│ GET /team_memory?repo= │ 拉取(下载全量)
│◄─────────────────────────│
│ │
│ PUT /team_memory?repo= │ 推送(增量上传)
│────────────────────────▶│
│ │
同步关键点:
- 拉取:服务器内容覆盖本地(server wins)
- 推送:只上传内容哈希不同的文件(delta upload)
- 删除:本地删除不会传播到服务器,下次拉取会恢复
- 冲突重试:最多 2 次(基于 ETag
lastKnownChecksum)
同步状态管理:
type SyncState = {
lastKnownChecksum: string | null // ETag,用于条件请求
serverChecksums: Map<string, string> // 每个文件内容的 sha256 哈希
serverMaxEntries: number | null // 服务器最大条目数(从 413 响应学习)
}
3.3 隐私保护:密钥扫描
推送前会自动扫描密钥,防止敏感信息泄露到团队空间:
// 扫描正则模式
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 权限控制
提取代理可以同时向私有记忆和团队记忆写入,但团队记忆有更严格的约束:
- 绝不保存 API key、凭证等敏感信息
- 每个文件类型有
<scope>指导该写到哪个目录 - 推送时用 OAuth token 认证
四、系统级配置记忆:CLAUDE.md
CLAUDE.md 是静态配置文件,不是 AI 自动生成的,而是用户/团队手动维护的:
.claude/CLAUDE.md # 项目级
~/.claude/CLAUDE.md # 用户级
这是每次对话启动时必加载的内容,作为 system prompt 的一部分注入。与动态生成的 Session Memory 和 Auto Memory 不同,CLAUDE.md 是确定性的、版本可控的。
五、记忆如何注入到 AI 上下文
记忆不是全部塞进 prompt 的,而是按需、分层、通过 attachment 机制注入的。
5.1 加载时机
系统启动时扫描所有记忆目录:
1. 加载 CLAUDE.md (静态配置)
2. 加载 memory/MEMORY.md (索引 → 按需加载具体文件)
3. 加载 session-memory.md (当前会话笔记)
4. 如果是团队模式,加载 team memory
5.2 压缩后的记忆再注入
当 Auto Compact 触发时,所有消息被替换为摘要但记忆不会丢失:
// compactConversation() 成功后:
// 1. 清除 fileStateCache
// 2. 重新注入关键上下文:
postCompactFileAttachments = [
...最近修改的文件快照(最多5个),
...异步Agent状态,
...plan文件,
...已调用的技能内容(25K token预算),
...MCP工具增量,
// 记忆通过 attachment 机制自动重新注入
]
5.3 Session Memory Compact —— 轻量级压缩
当记忆存在且有实质内容时(不只是模板),系统会尝试基于记忆的压缩,这比完整的 AI 摘要压缩更经济:
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
}
这比完整的 compactConversation(启动 forked agent 做全对话摘要)要便宜得多,因为:
- 不需要额外的 AI 调用生成摘要(session memory 已经是现成的摘要)
- Token 从 ~167K 压缩到 ~10-40K(保留最近的对话 + 用 session memory 替代旧对话的摘要)
六、完整记忆生命周期:信息如何流动
用户在对话中提供信息
│
├── 显式指令 ("记住这个") ──→ 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.1 记忆与压缩的协同
Session Memory 最初是为了替代 Auto Compact 而设计的——如果有了 session memory 摘要,就不需要再浪费一次 AI 调用来做全对话摘要:
// autoCompactIfNeeded() 流程:
// 1. 先尝试 Session Memory Compact(不需要额外 AI 调用)
const sessionMemoryResult = await trySessionMemoryCompaction(...)
if (sessionMemoryResult) {
return { wasCompacted: true } // 成功!不需要完整压缩
}
// 2. 如果 session memory 不可用或没有内容,才做完整压缩
const compactionResult = await compactConversation(...)
熔断与回退:
- Session Memory 为空(只有模板)→ 降级到 legacy compact
lastSummarizedMessageId找不到(消息被压缩掉了)→ 降级到 legacy compact- 预算检查失败 → 降级到 legacy compact
7.2 索引(MEMORY.md)的设计
- [Title](file.md) — one-line hook
这种设计有几个关键好处:
- 低开销加载:索引文件很小(200 行 × 150 字符 ≈ 7.5K 字符),可以快速解析
- 按需加载内容:不需要一次加载所有记忆文件,只有相关的时候才 read
- 用户可审查:索引文件可读,用户可以删除不需要的条目
- 语义组织:按主题分类,不是按时间排序
- 自我修复:AI 可以更新或删除过时的条目
7.3 游标与增量提取
提取代理不是每次都扫描整个对话历史:
// 游标机制:lastMemoryMessageUuid
// 每次提取只处理游标之后的新消息
const newMessageCount = countModelVisibleMessagesSince(messages, lastMemoryMessageUuid)
// 如果游标对应的消息被压缩删除了(找不到 UUID),回退到全量扫描
if (!foundStart) {
return count(messages, isModelVisibleMessage) // 兜底策略
}
这确保提取代理每次只处理增量内容,成本可控。
7.4 并行调用合并(Coalescing)
当多个 trigger 几乎同时到达时,不会启动多个提取代理:
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 敏感信息防护
// 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' })
}
八、对低代码开发智能体团队的启示
启示 1:多时间尺度的记忆系统
Claude Code 的记忆不是”一块大脑”,而是多个不同时间尺度的系统:
| 系统 | 保留周期 | 更新频率 | 触发方式 |
|---|---|---|---|
| Session Memory | 会话期间 | 每 5K token / 3 次工具调用 | PostSampling Hook |
| Auto Memory | 永久 | 每 N 轮对话 | Stop Hook |
| Team Memory | 永久 + 云端同步 | 随 Auto Memory 一起管理 | 同上 + 定时推送 |
| CLAUDE.md | 永久 | 手动 | 启动时加载 |
应用: 低代码平台应该有类似的记忆分层——工作流中间状态(短期)、项目经验(中期)、组织最佳实践(长期)应该有不同存储和生命周期。
启示 2:后台提取 + 前台直接保存的双路径
记忆有两条写入路径:
- 前台直接保存:用户说”记住这个”时 AI 直接写入
- 后台自动提取:对话结束后提取代理分析并提取
两者通过 hasMemoryWritesSince 互斥——避免重复劳动。
应用: 低代码平台中,用户应该能直接保存知识,同时系统也应该有后台自动提取能力(从执行日志、错误模式、流程变更中学习)。
启示 3:索引与内容分离的架构
MEMORY.md(索引)与 .md 文件(内容)分离的设计:
- 索引永远加载,内容按需加载
- 索引小(200 行 × 150 字),内容按需读
- 用户可以审查和编辑索引
应用: 如果低代码平台有大量可学习的”模式”或”规则”,应该用索引+内容的两级结构,而不是一次性加载所有模式。
启示 4:记忆本身可以替代压缩
Session Memory 是 Auto Compact 的廉价替代品——它已经是 AI 生成的结构化摘要,不需要再启动一个 forked agent 生成摘要。
应用: 低代码平台如果积累了工作流执行日志/笔记,可以在需要”精简上下文”时直接使用这些笔记作为摘要,而不需要额外调用 AI。
启示 5:记忆写入的最小化权限模型
提取代理只被授予最小必要权限:
- 只读工具不受限(Read/Grep/Glob/Bash 只读命令)
- 写工具严格限定在 memory 目录
- 其他一切工具拒绝
应用: 低代码平台中自动学习/记忆的智能体应该有类似的受限权限——它可以读取上下文信息、写入记忆文件,但不能执行修改业务数据的操作。
启示 6:增量提取和游标管理
提取代理通过 lastMemoryMessageUuid 游标只处理新消息,如果游标对应消息被压缩删除了就回退到全量。这是增量消费的标准模式。
应用: 任何基于对话历史的持续学习系统都需要游标管理——记住上次处理到哪里了,以及当历史记录被修改/删除时的回退策略。
启示 7:记忆的压缩与截断
Session Memory 有硬性上限(12K tokens,每段 2K tokens),提取时 AI 会自动截断超长段落并优先保留 Current State 和 Errors & Corrections。
应用: 低代码平台的学习模块应该有容量管理——当知识库太大时,AI 应该优先保留哪些信息、淘汰哪些信息。这需要明确的内容策略和截断规则。
启示 8:团队知识的版本控制与安全
Team Memory 用 Git 仓库作为标识、Etag-based 增量同步、推送前自动扫描密钥、单次推送有 200KB 限制、大文件分批上传。
应用: 如果低代码平台有”团队级知识”概念,需要类似的:
- 基于内容哈希的增量同步(避免全量传输)
- 冲突检测与重试
- 敏感信息自动过滤
- 推送大小限制