Claude Code 记忆系统深度解析

目录

  1. 总体架构
  2. 存储位置与路径解析
  3. 文件格式
  4. 记忆类型(taxonomy)
  5. 什么不该存
  6. 注入路径一:系统提示词(始终加载)
  7. 注入路径二:动态相关记忆(按需注入)
  8. 记忆查询机制
  9. 记忆写入方式
  10. 后台自动提取 Agent
  11. CLAUDE.md 与记忆的关系
  12. 容量限制与截断策略
  13. 开关与环境变量
  14. 安全机制
  15. Agent Memory(子 Agent 独立记忆)
  16. Session Memory(会话摘要记忆)
  17. 关键源文件速查

总体架构

记忆系统由两条并行的注入管道组成:

会话启动时
     
     ├─► [静态加载] loadMemoryPrompt()
              生成一段"记忆使用说明"文本
               MEMORY.md 的内容也读进来
              一起塞进系统提示词里
              (只在会话开始时算一次,之后就缓存着,除非用户执行 /clear  /compact
     
每次用户发消息
     
     ├─► [后台预取] startRelevantMemoryPrefetch()
               Claude 回复的同时,后台偷偷运行
               Sonnet 模型看看哪些记忆文件跟当前对话相关
              最多挑 5 
     
     ├─► [主回复] Claude 正常回复用户
     
     └─► [动态注入] 把刚才挑出来的记忆文件内容
                <system-reminder> 的形式插入到对话里
                Claude 能看到这些相关记忆

Claude 回复完毕后
     
     └─► [后台分析] extractMemories Agent
               开个分身 Agent 分析刚才的对话
               判断有没有值得保存的新记忆
               有的话就写到记忆文件里

存储位置与路径解析

默认路径

~/.claude/projects/<sanitized-git-root>/memory/
  • ~/.claude = getClaudeConfigHomeDir(),可被 CLAUDE_CONFIG_DIR 覆盖
  • <sanitized-git-root> = 当前项目的规范 git 根目录路径,经过路径消毒处理
  • 使用规范 git 根目录findCanonicalGitRoot)而非 CWD,保证同一仓库的多个 worktree 共享同一份记忆

路径解析优先级(高到低)

优先级 来源 说明
1 CLAUDE_COWORK_MEMORY_PATH_OVERRIDE env Cowork 完整路径覆盖
2 settings.json autoMemoryDirectory 用户/策略级设置(支持 ~/ 展开)
3 ~/.claude/projects/<slug>/memory/ 默认,基于 git 根目录

安全注意projectSettings.claude/settings.json,提交进仓库)中的 autoMemoryDirectory 被故意排除,防止恶意仓库将写入路径重定向到 ~/.ssh 等敏感目录。

目录结构示例

~/.claude/projects/-home-user-myrepo/memory/
  MEMORY.md               ← 索引文件(始终注入系统提示词)
  user_role.md            ← 各类记忆条目
  feedback_terse.md
  project_deadline.md
  reference_linear.md
  logs/                   ← KAIROS 模式下的日志目录(append-only)
    2026/
      04/
        2026-04-09.md

文件格式

MEMORY.md(索引文件)

纯索引,无 frontmatter,每行一条指针:

- [User Role](user_role.md) — 用户是数据科学家,关注日志可观测性
- [Feedback: Terse Response](feedback_terse.md) — 不要在回复末尾总结
- [Project: Auth Rewrite](project_auth.md) — 合规驱动的中间件重写

约束: - 每行建议 ≤150 字符 - 最多加载 200 行25,000 字节(先到者截断) - 超出限制时,系统在末尾追加警告而不是静默丢弃

各记忆文件(Topic files)

---
name: 记忆名称
description: 一行描述(用于相关性判断,要具体)
type: user | feedback | project | reference
---

记忆正文内容。

对于 feedback/project 类型,推荐结构:
主要规则或事实

**Why:** 原因(用户给出的理由、历史事件、偏好)
**How to apply:** 适用场景(什么时候/哪里触发此规则)

记忆类型(taxonomy)

系统强制约束为四种类型(memoryTypes.ts),无效类型被静默降级为 undefined

user — 用户画像

存什么: 用户的角色、目标、职责、技能水平、偏好

何时存: 了解到用户的任何个人信息时

如何使用: 回答问题时调整表达方式和深度(对 Go 专家解释 React 时类比后端概念)

示例:

用户有 10  Go 经验但第一次接触这个仓库的 React 前端
 用后端类比来解释前端概念

注意:不要保存对用户的负面评价,只记录有助于协作的信息。


feedback — 行为反馈

存什么: 用户对 Claude 工作方式的纠正和确认

何时存: - 纠正:用户说"不要这样"、"别再 X 了"、"停止 X" - 确认:用户说"对就这样"、"完美",或对非常规做法不提异议地接受

关键洞察:只存纠正会让模型过于保守;确认同样重要,防止偏离已验证的方向。

body_structure 1. 规则本身(直接陈述) 2. **Why:** 原因(历史事件、强偏好) 3. **How to apply:** 适用场景

示例:

不要在集成测试中 mock 数据库。

**Why:** 上个季度 mock 测试全过但 prod 迁移失败,因为 mock/prod 行为不一致。
**How to apply:** 所有涉及数据库操作的测试必须使用真实数据库连接。

project — 项目上下文

存什么: 进行中的工作、目标、截止日期、事故、决策背后的原因——这些无法从代码或 git 历史中推断出来

何时存: 了解到"谁在做什么、为什么、截止到什么时候"时

注意:必须将相对日期转为绝对日期("周四" → "2026-04-10"),因为记忆会跨越多次会话。

body_structure 1. 事实或决策 2. **Why:** 动机(约束、截止日期、利益相关方需求) 3. **How to apply:** 如何影响建议

示例:

auth 中间件重写由合规要求驱动,而非技术债清理。

**Why:** 法务部门标记了旧实现存在不合规的 session token 存储方式。
**How to apply:** 范围决策优先满足合规性,不要为了 DX 或性能牺牲合规。

project 记忆过期很快,**Why:** 帮助判断该记忆是否仍然有效。


reference — 外部系统指针

存什么: 外部系统中信息的位置(Linear 项目、Grafana 看板、Slack 频道等)

何时存: 了解到某个外部资源的用途时

如何使用: 当用户提到外部系统或需要查找相关信息时

示例:

流水线 bug 追踪在 Linear 项目 "INGEST"
oncall 监控看板在 grafana.internal/d/api-latency,修改请求路径时检查它

什么不该存

以下内容被明确排除,即使用户明确要求保存也不存

排除类型 原因
代码模式、架构、文件路径、项目结构 可以通过读取当前代码推导
git 历史、最近变更、谁改了什么 git log / git blame 是权威来源
调试解法、修复配方 修复在代码里,提交信息有上下文
CLAUDE.md 中已有的内容 避免重复
临时任务状态、当前会话上下文 仅当前会话有效,无需跨会话持久化

当用户要求"记住本周的 PR 列表"时,应追问"这里有什么让你觉得特别值得记住的地方?"——列表本身不值得存,但其中非显而易见的洞察值得。


注入路径一:系统提示词(始终加载)

代码调用流程

src/constants/prompts.ts
  └── getDefaultSystemPrompt()   构建系统提示词
        └── dynamicSections = [
              systemPromptSection('memory', () => loadMemoryPrompt()),   记忆部分
              ...
            ]

缓存机制src/constants/systemPromptSections.ts): - 第一次调用时计算结果,存到 STATE.systemPromptSectionCache 里 - 之后每次直接用缓存,不重复计算 - 用户执行 /clear/compact 时清空缓存 - 下次会话开始时重新读取 MEMORY.md

loadMemoryPrompt() 生成的内容

这个函数(src/memdir/memdir.ts:419)会生成一大段文本,告诉 Claude 怎么使用记忆系统:

普通模式下生成的内容:

调用 buildMemoryLines() 拼接出以下文本:

# auto memory

你有一个持久化的记忆系统,路径在 `~/.claude/projects/.../memory/`
这个目录已经存在了,直接用 Write 工具写文件就行...

你应该逐步建立这个记忆系统,让未来的对话能了解用户是谁...

如果用户明确要求你记住某事,立即保存...

## Types of memory
<types>
  <type>
    <name>user</name>
    <description>用户的角色、目标、职责、知识水平...</description>
    <when_to_save>什么时候保存...</when_to_save>
    <how_to_use>怎么使用...</how_to_use>
    <examples>示例...</examples>
  </type>
  ... (feedback, project, reference 同理)
</types>

## What NOT to save in memory
- 代码模式、架构、文件路径...(这些读代码就能知道)
- Git 历史...(git log 是权威来源)
...

## How to save memories

保存记忆分两步:

**第 1 步** — 写一个单独的记忆文件(比如 `user_role.md`),格式如下:
```markdown
---
name: 记忆名称
description: 一行描述(用来判断相关性的,要写具体)
type: user | feedback | project | reference
---
记忆内容

第 2 步 — 在 MEMORY.md 里加一行指针:- [标题](file.md) — 一句话描述

  • MEMORY.md 是索引文件,每次对话都会加载
  • 超过 200 行会被截断,所以要保持简洁
  • 按主题组织,不要按时间顺序
  • 过时的记忆要更新或删除
  • 不要写重复的记忆...

When to access memories

  • 当记忆看起来相关时,或者用户提到之前的工作
  • 用户明确要求"记住"、"回忆"时必须访问记忆
  • 如果用户说"忽略记忆",就当 MEMORY.md 是空的...
  • 记忆可能会过时...

Before recommending from memory

记忆里提到的函数、文件、标志可能已经改名、删除或从未合并... 推荐之前先验证一下...

Memory and other forms of persistence

  • 什么时候用 plan 而不是 memory:...
  • 什么时候用 tasks 而不是 memory:...

MEMORY.md

[这里会把 MEMORY.md 的实际内容读进来,最多 200 行或 25KB]

**简单说**:这段文本就是一份"记忆系统使用手册",告诉 Claude:
- 记忆文件放哪里
- 有哪几种类型的记忆
- 怎么保存(写文件 + 更新索引)
- 什么时候该用记忆
- 最后把 MEMORY.md 的内容也附上

### MEMORY.md 的注入方式

MEMORY.md 通过 `getUserContext()` → `claudemd.ts` 的 `getMemoryFiles()` 加载,跟 CLAUDE.md 文件合并后一起注入到对话里,最终以 `<system-reminder>` 的形式出现。

---

## 注入路径二:动态相关记忆(按需注入)

### 完整流程

用户发消息 │ ▼ startRelevantMemoryPrefetch() # attachments.ts:2362 - 拿到用户刚发的消息(跳过系统注入的消息) - 如果消息太短(≤1 个词)就跳过 - 检查这次会话已经注入了多少记忆(超过 60KB 就不再注入) - 启动后台任务(如果用户取消了,这个任务也会停) │ ▼ (后台运行,不影响 Claude 正常回复) getRelevantMemoryAttachments() # attachments.ts:2197 │ ├── scanMemoryFiles() # memoryScan.ts:35 │ 扫描记忆目录里的所有 .md 文件 │ - 递归读取目录 │ - 排除 MEMORY.md(索引文件不算) │ - 每个文件只读前 30 行,提取 frontmatter(name、description、type) │ - 按修改时间排序,只看最新的 200 个文件 │ - 返回文件列表:[ { 文件名, 路径, 修改时间, 描述, 类型 } ] │ ├── findRelevantMemories() # findRelevantMemories.ts:39 │ 用 AI 模型挑选相关的记忆 │ - 过滤掉这次会话已经注入过的文件 │ - 把文件列表格式化成清单: │ "- [feedback] feedback_terse.md (2026-04-09): 不要总结" │ - 调用 Sonnet 模型(开个小号): │ 给它看用户的消息 + 可用的记忆清单 │ 让它挑最多 5 个相关的记忆文件 │ 返回 JSON 格式:{ selected_memories: ["file1.md", "file2.md"] } │ - 返回挑选出来的文件路径 │ ├── 去重(避免重复注入已经读过的文件) │ └── readMemoriesForSurfacing() # attachments.ts:2280 读取挑选出来的记忆文件内容 - 并发读取所有文件(提高速度) - 每个文件最多读 200 行或 4096 字节 - 超出限制就截断,并提示"用 Read 工具可以看完整内容" - 给每个文件加个时间戳: "Memory (saved 3 days ago): /path/to/file.md:" - 返回文件内容列表 │ ▼ { type: 'relevant_memories', memories: [...] } # 打包成 Attachment 以 的形式插入到对话里,让 Claude 能看到

### 相关性选择器的 System Prompt(原文)

You are selecting memories that will be useful to Claude Code as it processes a user's query. You will be given the user's query and a list of available memory files with their filenames and descriptions.

Return a list of filenames for the memories that will clearly be useful to Claude Code as it processes the user's query (up to 5). Only include memories that you are certain will be helpful based on their name and description. - If you are unsure if a memory will be useful in processing the user's query, then do not include it in your list. Be selective and discerning. - If there are no memories in the list that would clearly be useful, feel free to return an empty list. - If a list of recently-used tools is provided, do not select memories that are usage reference or API documentation for those tools (Claude Code is already exercising them). DO still select memories containing warnings, gotchas, or known issues about those tools — active use is exactly when those matter.

> **注意**:此选择器调用受 `tengu_moth_copse` feature gate 控制,在本仓库的反编译版本中 `feature()` 始终返回 `false`,即**该功能默认关闭**

### 注入形式

注入后在 API 调用中以 `<system-reminder>` 出现,紧邻用户消息:

Memory (saved 2 days ago): /home/user/.claude/projects/.../memory/feedback_terse.md:


name: Terse Response description: 用户不希望在回复末尾总结已完成的工作 type: feedback


不要在每次回复末尾总结刚完成的工作。

Why: 用户说"我能看懂 diff"。 How to apply: 所有回复。

### 去重机制

| 机制 | 说明 |
|------|------|
| `alreadySurfaced` Set | 扫描历史消息中所有 `relevant_memories` attachment,收集已注入路径 |
| `readFileState` | 主模型通过 FileReadTool 已读取的文件不再重复注入 |
| 会话总量上限 | 累计注入超 **60KB** 后停止预取 |
| context compaction | `/compact` 后历史消息清空,两个 Set 自然重置,可重新注入 |

---

## 记忆写入方式

### 方式一:主模型直接写入(主动)

主模型在对话中直接使用 `Write` 工具写文件,系统提示词中已包含完整的写入说明和格式要求:

1. 写记忆文件(带 frontmatter)
2. 更新 `MEMORY.md` 索引(追加一行指针)

### 方式二:用户显式要求

用户说"记住..."时,模型立即保存,优先级高于自动判断。

### 方式三:后台自动提取(详见下节)

---

## 后台自动提取 Agent

每轮主模型输出完毕后,`handleStopHooks` 触发 `executeExtractMemories()``src/services/extractMemories/extractMemories.ts`)。

### 触发条件

```typescript
// 必须全部满足才运行
isAutoMemoryEnabled()              // 记忆系统开启
feature('EXTRACT_MEMORIES')        // build flag(本仓库为 false,即禁用)
getFeatureValue('tengu_passport_quail', false)  // Growthbook gate
!getIsRemoteMode()                 // 非远程模式
!context.toolUseContext.agentId   // 只在主 agent 运行,不在子 agent 中
!inProgress                        // 没有正在进行的提取(队列化)

互斥逻辑

如果主模型在本轮已经写入了记忆文件(通过检查消息中的 Edit/Write tool_use 是否指向记忆目录),则跳过后台提取,游标推进到最新消息:

if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
  // 跳过,推进游标
  return
}

Forked Agent 模式

提取 Agent 是主对话的"完美分叉"(runForkedAgent):

  • 共享 prompt cache:与主对话使用相同的系统提示词前缀,命中 cache 的 token 极大减少成本
  • 最多 5 轮:标准流程是"并发读取所有可能修改的文件 → 并发写入",共 2 轮
  • 工具权限受限
  • 允许:ReadGrepGlob、只读 Bash(ls/find/cat/stat/wc/head/tail)
  • 允许:Edit/Write 但仅限记忆目录内的路径
  • 拒绝:MCP 工具、Agent 工具、写入非记忆目录

提取 Agent 的 Prompt(简化)

You are now acting as the memory extraction subagent. Analyze the most recent
~N messages above and use them to update your persistent memory systems.

Available tools: Read, Grep, Glob, read-only Bash, and Edit/Write for paths
inside the memory directory only.

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

You MUST only use content from the last ~N messages to update your persistent
memories. Do not waste any turns investigating or verifying content further.

## Existing memory files

- [feedback] feedback_terse.md (2026-04-09T...): 不要总结

Check this list before writing  update an existing file rather than creating
a duplicate.

[以下是 TYPES_SECTION + WHAT_NOT_TO_SAVE + How to save 的完整说明]

并发控制

if (inProgress) {
  // 保存到 pendingContext(只保留最新的,旧的被覆盖)
  pendingContext = { context, appendSystemMessage }
  return
}
// 运行完毕后的 finally 块检查 pendingContext,若有则执行 trailing run

节流机制

tengu_bramble_lintel feature gate 可配置每 N 轮才运行一次提取(默认 1,即每轮都运行)。

结果通知

提取完成后,如果写入了新的记忆文件(排除 MEMORY.md 本身),系统向对话注入一条 system 类型消息告知用户:

[记忆已保存:feedback_terse.md, project_auth.md]

关闭时排空

进程退出前,drainPendingExtraction() 等待所有 in-flight 提取完成(最多 60 秒),防止 Agent 被强制杀死前未完成写入。


CLAUDE.md 与记忆的关系

MEMORY.md 通过 claudemd.tsgetMemoryFiles() 函数与 CLAUDE.md 系列文件一起加载,统一注入为用户上下文中的 claudeMd 字段。

加载优先级(低 → 高,后加载的优先级更高)

1. Managed memory      /etc/claude-code/CLAUDE.md         全局所有用户
2. User memory         ~/.claude/CLAUDE.md                个人全局
3. Project memory      CLAUDE.md, .claude/CLAUDE.md,      随仓库提交
                       .claude/rules/*.md每目录
4. Local memory        CLAUDE.local.md每目录          私有不提交
5. Auto memory index   ~/.claude/projects/<slug>/memory/MEMORY.md  自动记忆索引

越晚加载的文件优先级越高(模型更关注后出现的内容)。

MEMORY.md 的截断处理

// claudemd.ts 中对 MEMORY.md 的特殊处理
const t = truncateEntrypointContent(rawContent)
// 截断至 200行 / 25KB,并追加警告信息

@include 指令

CLAUDE.md 类文件支持 @path 包含其他文件: - @./relative/path@~/home/path@/absolute/path - 循环引用保护:追踪已处理文件集合 - 只支持文本文件(.md.ts.py 等),忽略二进制文件


容量限制与截断策略

限制对象 上限 截断行为
MEMORY.md 索引 200 行 25,000 字节 截断后追加 WARNING,提示索引过长
动态注入单个文件 200 行 4,096 字节 截断后追加提示,建议用 FileReadTool 完整读取
动态注入文件数 ≤5 个/轮 选择器硬限 5 个
会话总注入量 60,000 字节 超限后停止预取,不再注入新记忆
扫描文件数 200 个 按 mtime 降序,只处理最新 200 个
frontmatter 读取 前 30 行 仅读前 30 行提取 frontmatter,减少 I/O
单个 CLAUDE.md 40,000 字符 MAX_MEMORY_CHARACTER_COUNT

开关与环境变量

变量/设置 作用
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 完全禁用记忆系统
CLAUDE_CODE_SIMPLE=1 / --bare 禁用记忆系统(同时禁用其他次要功能)
CLAUDE_CODE_REMOTE=1 且无 CLAUDE_CODE_REMOTE_MEMORY_DIR 禁用(远程模式无本地存储)
CLAUDE_CODE_REMOTE_MEMORY_DIR=/path 覆盖记忆目录(Cowork/SDK 使用)
CLAUDE_COWORK_MEMORY_PATH_OVERRIDE=/path Cowork 完整路径覆盖
CLAUDE_COWORK_MEMORY_EXTRA_GUIDELINES 注入额外的记忆策略文本
settings.json autoMemoryEnabled: false 项目级关闭
settings.json autoMemoryDirectory: "~/custom" 自定义记忆目录

Feature Gates(Growthbook)

Gate 名称 控制的功能
tengu_moth_copse 动态相关记忆注入(skipIndex 模式)
tengu_coral_fern "Searching past context" 提示词节(grep 记忆目录)
tengu_passport_quail 后台自动提取 Agent 是否运行
tengu_slate_thimble 非交互模式下是否运行提取
tengu_bramble_lintel 提取节流(每 N 轮运行一次,默认 1)
EXTRACT_MEMORIES 编译时 build flag,本仓库为 false

在本仓库的反编译版本中,所有 feature('FLAG') 调用均返回 false,因此后台提取 Agent 和动态记忆注入均处于禁用状态


安全机制

路径校验(validateMemoryPath

拒绝以下危险路径: - 相对路径(非 isAbsolute) - 根目录或近根目录(//a) - Windows 驱动器根(C:) - UNC 路径(\\server\share) - 包含空字节的路径

写入权限白名单

后台提取 Agent 的 canUseTool 函数: - Edit/Write 只允许写入 isAutoMemPath() 返回 true 的路径 - 其他所有写操作被拒绝并记录到 telemetry

路径穿越防护

isAutoMemPath() 调用 normalize() 后再做前缀匹配,防止 ../ 穿越攻击。

projectSettings 排除

已提交进仓库的 .claude/settings.json 不允许设置 autoMemoryDirectory,防止恶意仓库劫持写入路径。


记忆查询机制

除了被动注入,系统还提供主动查询记忆的能力。

主动搜索历史记忆

tengu_coral_fern 功能开关打开时,系统提示词里会加一段话,教 Claude 怎么主动搜索历史信息:

## Searching past context

想找之前的信息时:
1. 先搜记忆文件:

grep -rn "搜索词" ~/.claude/projects/.../memory/ --include="*.md"

2. 实在找不到再搜会话记录慎用文件很大很慢):

grep -rn "搜索词" /path/to/project/ --include="*.jsonl"

用具体的搜索词(错误信息、文件路径、函数名),别用太宽泛的关键词。

可以搜两个地方:

搜索目标 路径 说明
记忆文件 <autoMemDir>/*.md 搜索已保存的记忆(推荐)
会话记录 <projectDir>/*.jsonl 搜索历史对话的完整记录(文件大,慢,最后手段)

实现方式:

  • 根据运行环境选择工具:
  • 命令行模式:用 shell 的 grep -rn 命令
  • 标准模式:用 Grep 工具
  • 强调用具体的搜索词(比如错误信息、文件路径、函数名),别用太宽泛的词
  • .jsonl 会话记录文件很大,搜起来慢,是最后手段

源码位置: src/memdir/memdir.ts:375 (buildSearchingPastContextSection)


Agent Memory(子 Agent 独立记忆)

子 Agent(通过 AgentTool 启动)拥有完全独立的持久记忆系统,与 auto-memory 分离。

三种 Scope

Scope 路径 说明
user ~/.claude/agent-memory/<agentType>/ 用户级,跨项目共享
project .claude/agent-memory/<agentType>/ 项目级,随仓库提交
local .claude/agent-memory-local/<agentType>/ 本地级,不提交到 VCS

路径解析规则:

  • <agentType> 中的冒号 : 被替换为 -(Windows 兼容性)
  • local scope 在 CLAUDE_CODE_REMOTE_MEMORY_DIR 存在时会持久化到挂载点
  • 使用规范 git 根目录(findCanonicalGitRoot)保证同仓库多 worktree 共享

加载机制

loadAgentMemoryPrompt(agentType: string, scope: AgentMemoryScope): string

调用 buildMemoryPrompt() 生成完整的记忆提示词,包含: - 记忆目录路径声明 - 四种类型的完整说明 - 两步保存流程 - Scope 特定提示: - user: "keep learnings general since they apply across all projects" - project: "tailor your memories to this project, shared with your team via version control" - local: "tailor your memories to this project and machine (not checked into VCS)"

与 auto-memory 的区别

特性 Auto Memory Agent Memory
触发者 主对话 子 Agent
路径 ~/.claude/projects/<slug>/memory/ ~/.claude/agent-memory/<agentType>/
动态注入 有(Sonnet 选择器) 无(始终全量加载 MEMORY.md)
后台提取 有(extractMemories)
Scope 选项 user / project / local

安全机制: isAgentMemoryPath() 检查写入路径是否在允许的 agent-memory 目录内,防止路径穿越。

源码位置: src/tools/AgentTool/agentMemory.ts


Session Memory(会话摘要记忆)

这是第三套完全独立的记忆系统,跟前面的 auto-memory 和 agent-memory 都不一样。

它是干什么的?

Session Memory 不是用来跨会话保存信息的,而是给当前会话做笔记

当对话太长需要压缩(auto-compact)时,这份笔记能保留关键信息,避免 Claude 忘记之前聊了什么。

文件位置: <projectDir>/<sessionId>/session-memory/summary.md

文件结构(9 个固定 Section)

# Session Title
_A short and distinctive 5-10 word descriptive title for the session_

# Current State
_What is actively being worked on right now? Pending tasks not yet completed_

# Task specification
_What did the user ask to build? Any design decisions or other explanatory context_

# Files and Functions
_What are the important files? In short, what do they contain and why are they relevant?_

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

# Errors & Corrections
_Errors encountered and how they were fixed. What did the user correct?_

# Codebase and System Documentation
_What are the important system components? How do they work/fit together?_

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

# Key results
_If the user asked a specific output, repeat the exact result here_

# Worklog
_Step by step, what was attempted, done? Very terse summary for each step_

关键约束: - 每个 section 的标题(# ...)和斜体描述行(_..._不可修改 - 只更新描述行之后的实际内容 - 每个 section ≤ 2000 tokens - 全文 ≤ 12,000 tokens

什么时候更新笔记?

shouldExtractMemory(messages: Message[]): boolean

第一次创建笔记: 对话内容达到 10,000 tokens 时

之后更新笔记的条件(满足其一): 1. 对话又增加了 5,000 tokens,并且 Claude 调用了至少 3 次工具 2. 对话又增加了 5,000 tokens,并且 最后一轮没有调用工具(说明是个自然的对话断点)

注意:token 数量是硬性要求,就算工具调用次数够了,也得等 token 增长够了才会更新。

更新流程

shouldExtractMemory() 判断需要更新

setupSessionMemoryFile()
  准备笔记文件
  - 第一次就创建 summary.md
  - 之后就读取现有内容

buildSessionMemoryUpdatePrompt()
  构建更新指令
  - 读取自定义 prompt(如果用户配置了的话)
  - 检查各个章节的大小,如果超限就生成警告
  - 把变量替换成实际内容(比如 {{currentNotes}} 替换成当前笔记内容)

runForkedAgent()
  开个分身 Agent 去更新笔记
  - 跟主对话共享缓存(省钱)
  - 只允许用 Edit 工具修改 summary.md
  - 不允许做其他操作

markExtractionCompleted()
  标记完成
  - 记录这次更新时的 token 数
  - 记录更新到哪条消息了

与 auto-compact 的集成

isAutoCompactEnabled() 为 true 时,Session Memory 才会初始化(initSessionMemory())。

Compact 时,系统会: 1. 检查 summary.md 是否为空(与模板相同) 2. 若非空,将其内容注入到 compact 后的首条消息中 3. 若超限,按 section 截断(truncateSessionMemoryForCompact

自定义配置

用户可自定义两个文件:

文件 路径 用途
模板 ~/.claude/session-memory/config/template.md 自定义 section 结构
Prompt ~/.claude/session-memory/config/prompt.md 自定义更新指令

变量替换语法: {{variableName}}(单次替换,避免双重替换和 $ 反向引用问题)

关键配置参数

DEFAULT_SESSION_MEMORY_CONFIG = {
  minimumMessageTokensToInit: 10000,      // 初始化阈值
  minimumTokensBetweenUpdate: 5000,       // 更新间隔(token)
  toolCallsBetweenUpdates: 3,             // 更新间隔(工具调用)
}

可通过 Growthbook dynamic config tengu_sm_config 远程覆盖。

Feature Gate

  • 主开关: tengu_session_memory(默认 false)
  • 依赖: isAutoCompactEnabled() 必须为 true

与其他记忆系统的对比

特性 Auto Memory Agent Memory Session Memory
生命周期 跨会话持久 跨会话持久 当前会话
主要用途 用户偏好、项目上下文 子 Agent 专属记忆 Compact 时保留上下文
触发方式 主动写入 + 后台提取 子 Agent 主动写入 周期性后台更新
文件结构 自由 markdown + frontmatter 自由 markdown + frontmatter 9 个固定 section
动态注入 有(Sonnet 选择器) 无(仅在 compact 时注入)

源码位置: - src/services/SessionMemory/sessionMemory.ts — 主逻辑 - src/services/SessionMemory/prompts.ts — 模板与 prompt 构建 - src/services/SessionMemory/sessionMemoryUtils.ts — 配置与状态管理


关键源文件速查

文件 职责
src/memdir/memdir.ts loadMemoryPrompt()buildMemoryLines()truncateEntrypointContent()
src/memdir/memoryTypes.ts 四种类型定义、提示词各节内容(WHAT_NOT_TO_SAVE、WHEN_TO_ACCESS 等)
src/memdir/paths.ts getAutoMemPath()isAutoMemoryEnabled()validateMemoryPath()
src/memdir/memoryScan.ts scanMemoryFiles()formatMemoryManifest()
src/memdir/findRelevantMemories.ts Sonnet 选择器、findRelevantMemories()
src/utils/attachments.ts (L2197-2425) 完整 recall 管道:startRelevantMemoryPrefetch()getRelevantMemoryAttachments()readMemoriesForSurfacing()
src/services/extractMemories/extractMemories.ts 后台提取 Agent、executeExtractMemories()、互斥逻辑
src/services/extractMemories/prompts.ts 提取 Agent 的 prompt 构建函数
src/utils/claudemd.ts CLAUDE.md 与 MEMORY.md 联合加载
src/context.ts getUserContext()(claudeMd 注入)、getSystemContext()(git status)
src/constants/prompts.ts (L495) systemPromptSection('memory', ...) 注册入口
src/constants/systemPromptSections.ts 系统提示词 section 缓存机制