跳转到内容

记忆系统设计原则

进阶

内容验证于 2026-06-24

你用 AI 编程超过一周,会发现一个尴尬的问题:每次开新会话,Claude 像失忆了一样。你得重新解释“我是谁”“我在做什么项目”“上次我们约定好不要一次性丢多道题”。

这就像你雇了一个非常聪明的助理,但他每天早上来上班时完全不记得昨天的事。每天第一件事是给他重新看一遍员工手册。

记忆系统就是这本员工手册。它让 Claude 在新会话开始时自动读到关键信息,不需要你每次复述。

本文以 Jason 的真实记忆系统为原型,逐层拆解设计思路。读完你会知道为什么需要三层分类、每条记忆怎么追溯来源、以及什么该放进记忆文件而不是 CLAUDE.md。

  • 你已经读过 记忆系统,知道 CLAUDE.md 是 Claude Code 启动时自动读取的上下文文件
  • 你已经在实际项目中用过 Claude Code 至少一周,遇到过“新会话不记得之前约定”的痛点

想像你的手机通讯录。你不会记住每个朋友的电话号码,你只记住他们在通讯录里。打电话时,手机自动按名字找到号码。

Claude Code 的记忆系统完全一样。你不需要让 Claude 记住所有细节(那会塞满上下文窗口,费 token 又费钱),你只需要让它知道去哪找

更精确的类比是图书馆的索引卡片。你走进图书馆不会把所有书翻一遍,而是先查索引卡片,找到目标书所在的书架和位置,然后直接走过去拿。MEMORY.md 就是这个索引卡片柜,memory/ 下的每个文件就是书架上的一本书。

这是整个系统最核心的设计。所有记忆文件分三层:

A 层:始终加载(约 13 个文件)

Section titled “A 层:始终加载(约 13 个文件)”

每个新会话,Claude Code 都会读取 CLAUDE.md。Jason 的全局 CLAUDE.md 底部有一行强制指令:

## 强制加载的记忆索引
@~/.claude/projects/-Users-jason-intern-journal/memory/MEMORY.md

这一行告诉 Claude:每次启动都读 MEMORY.md。而 MEMORY.md 就是 A 层索引——列出了约 13 个始终适用的记忆文件:

  • 用户背景(零基础,解释从日常类比开始)
  • 教学节奏(小段讲解 + 即时一问,禁一次性丢多题)
  • bash sigpipe 陷阱(避免 cat | head 误报失败)
  • 公开输出无业务上下文
  • 等等

共同特征:任何项目、任何任务都适用,不依赖具体技术栈或项目名。

B 层:关键词触发(约 5 个文件)

Section titled “B 层:关键词触发(约 5 个文件)”

MEMORY.md 里写了一条规则:

命中公司基建关键词(公司代码平台 / 内网 npm / 泳道 / AIGC 网关 / Bedrock / 图床 / MDM)→ Read MEMORY-COMPANY.md

Claude 读到这条规则后,不会主动去读 MEMORY-COMPANY.md。只有当前对话中出现这些关键词(比如用户说“帮我推到图床”),Claude 才会触发 Read 操作。

为什么这样设计?B 层记忆是公司的具体技术栈和流程,只在用到时才加载,不浪费通用任务上下文的 token。

C 层:项目名触发(约 7 个文件)

Section titled “C 层:项目名触发(约 7 个文件)”

类似 B 层,但触发条件是项目名:

命中项目名(prize / quanzhiping / activity-planner / 视频评价 agent 等)→ Read MEMORY-PROJECTS.md

C 层是项目专属记忆——某个项目里的特殊约定、历史决策、踩坑记录。只有在该项目的会话中才需要加载。

直观的数字对比:

  • 如果所有记忆文件每会话都加载:约 25 个文件,每个平均 30 行,总计约 750 行。这会吃掉上下文窗口的很大一部分,而且大部分内容当前任务根本用不上。
  • 三层设计后:A 层约 13 个始终加载,B 和 C 层的约 12 个文件按需加载。一个不涉及公司基建、不在特定项目的通用任务,只需要读 A 层。

核心原则:让 Claude 知道信息存在哪里,但不占用上下文空间去存储那些用不到的信息。 这一设计哲学的更多背景详见设计哲学

如果你了解一点计算机原理,这和 CPU 的 L1/L2/L3 缓存很像:

  • L1 缓存(A 层):每次都查,速度最快但容量最小
  • L2 缓存(B/C 层):按需查询,容量稍大但需要额外加载时间
  • 主内存:所有其他信息,需要时再搜索

打开 memory/ 下的任意文件,看它的 YAML frontmatter:

---
name: 避免 cat | head 类管道模式(SIGPIPE 陷阱)
description: 用 Read 工具或原生命令参数代替管道,避免 SIGPIPE 误报
type: feedback
originSessionId: a2b1fbf6-9f69-4b79-84a0-e692088aef18
---

originSessionId 是一个关键字段。它不是随便编的——它是 Claude Code 会话的唯一标识。

这意味着每条记忆都能追溯到“谁、在什么时候、因为什么”产生的。用 Jason 系统里的实际例子:

  • feedback_teaching_style.md 的 originSessionId 是 4ca6864d-...,对应那次 OpenClaw Hook 学习中 Jason 说“用带我学习然后提问的方式,而不是现在这种直接出题目”
  • feedback_bash_sigpipe.md 的 originSessionId 是 a2b1fbf6-...,对应那次 daily-learn 跑了 cat manifest.json | python3 -m json.tool | head -40 被 SIGPIPE 误导

三个月后你看到一条记忆规则“禁止 cat | head”,你可能不记得为什么。打开文件,看到 Why 段:“2026-05-08 daily-learn 跑 cat manifest.json | python3 -m json.tool | head -40 就被这个坑了——JSON 完全有效,命令‘失败’了”。

这就是记忆不是死规则,而是活教训。每条记忆都带着当时的具体场景和问题,让你(和 Claude)能理解规则背后的意图,而不只是机械执行。

这是 Jason 系统里最有创意的设计决策:记忆文件不存数据库、不用外部存储,直接放在 git 仓库里,和代码一样用 git 管理

memory/ 目录是 intern-journal 仓库的一部分(通过 symlink 暴露给 Claude Code)。每次修改记忆都走正常的 git commit。

这意味着你可以用 git 回答这些问题:

Terminal window
# "我四月份学到了什么?"
git log --after=2026-04-01 --before=2026-05-01 -- dotfiles/home-claude/memory/
# "关于 sigpipe 的记忆是什么时候加的?"
git log -- dotfiles/home-claude/memory/global/feedback_bash_sigpipe.md
# "最近一个月新增了哪些规则?"
git log --oneline --since="1 month ago" -- dotfiles/home-claude/memory/

每次 git log 的输出就是你的学习时间线。commit message 本身也是记忆的一部分——比如 026c5d9 GitHub 整理 Phase 8 复盘:写入 gh CLI 陷阱到 memory,这条 commit 告诉你这条记忆是 “Phase 8 复盘” 时产生的。

为什么不用数据库或笔记软件?

Section titled “为什么不用数据库或笔记软件?”
  • 数据库:需要额外维护,和你的日常 git 工作流脱节
  • 笔记软件:没有版本历史,改了就丢了之前的样子
  • git:你已经在用它管理代码,记忆跟代码一样待遇——有历史、可回溯、可协同

更重要的是:git blame 能告诉你每条记忆是 “谁”(哪次对话)在 “什么时候” 添加的,以及 “为什么”(commit message)。这跟 originSessionId 形成双重可追溯链。

记忆系统最大的敌人不是遗忘,而是矛盾积累

比如:你之前有一条规则 “学习类任务默认走带学+提问模式”。过了两个月,你遇到一个场景觉得 “直接出题更快”,于是加了一条 “快速学习场景可以批量出题”。这两条规则不矛盾吗?什么时候用哪条?

Jason 系统里有一条硬规则,写在了 MEMORY.md 的最底部:

新事实出现时:先 grep 三个索引文件看有没有冲突条目,再决定更新还是新增。

三个索引文件是:

  1. MEMORY.md(A 层索引)
  2. MEMORY-COMPANY.md(B 层索引)
  3. MEMORY-PROJECTS.md(C 层索引)

操作流程:

  1. grep 检查冲突:新规则的关键词是否已经出现在任何索引中?
  2. 如果已存在:打开冲突文件,更新它。可能补充例外情况,可能改写表达让它更精确,可能发现规则已过时需要删除。
  3. 如果不存在:创建新文件,在合适的索引中注册。

这防止了最常见的问题:两个互相矛盾的规则同时存在,Claude 读到时不知道该遵守哪个,用户自己也忘了哪个更“正确”。

Jason 最初有一条规则:feat 分支结束后删掉工作树。后来他发现有时需要回来查上下文,决定保留工作树。这次不是加一条”有时保留工作树”的新规则——而是直接修改了原规则文件,把”删除”改成”保留一周后再删”。更新,不叠加

这是新手最困惑的问题。两个文件 Claude 都会读,为什么要分开?

放 CLAUDE.md放 Memory
每次会话都读需要时按关键词触发
影响 Claude 的即时行为提供背景知识和长期教训
短(1-2 句)可以写 Why 和完整场景
“做什么”“为什么这样做”

以 “教学节奏” 规则为例:

CLAUDE.md 里写的(短规则):

- 教学节奏:学习类任务默认"小段讲解+即时一问+等回答",禁一次性丢多题

Memory 里写的(完整背景):

学习类任务默认走"带学+提问"模式:
1. 每轮讲一个小概念
2. 讲完立刻问一个小题
3. 等用户答完再讲下一段
Why:用户明确反馈过这个偏好(intern-journal 项目,OpenClaw Hook 学习 Step 9 时)
"用带我学习然后提问的方式,而不是现在这种直接出题目"是原话。

经验法则

  • 需要 Claude 每次启动都知道 的行为指令 → 放 CLAUDE.md
  • 需要 Claude 在特定场景下查阅 的背景知识 → 放 Memory
  • 规则本身 + 为什么有这条规则的历史 → CLAUDE.md 放规则,Memory 放 why

1. 记忆膨胀:把 A 层当万用抽屉

Section titled “1. 记忆膨胀:把 A 层当万用抽屉”

最危险的趋势:每次学到新东西就往 A 层加。A 层始终加载,文件越多,每个会话的上下文就越多。如果 A 层从 13 个膨胀到 30 个,效果反而不如没有记忆系统——Claude 被大量不相关信息淹没了。

对策:加新记忆时先问自己 “这真的是任何任务都需要知道的吗?”。大多数新知识应该进 B 层或 C 层(按关键词触发)。

不加 grep 就直接加新规则,等于在索引卡片柜里塞了一张可能跟已有卡片矛盾的卡片。几个月后 Claude 读到两条互相冲突的规则,它会遵循哪条?你不知道。

对策:把 “新事实出现时,先 grep 三个索引” 变成肌肉记忆。或者让 Claude 帮你做(在 CLAUDE.md 里写这条规则,Claude 自己就会执行)。

记忆文件的创建只是一个开始。规则会过时、会需要补充例外、会因为技术栈变化而失效。如果永远只加新文件不回头更新旧文件,记忆系统会变成垃圾场。

信号:当你看到一条记忆规则想说 “但是现在不一定了吧?”,那就是该更新它的时候了。

CLAUDE.md 里塞长篇解释(Why + 场景 + 例子),或者 Memory 里只写一句不写 why。前者浪费每次会话的 token,后者让 Memory 变成没有上下文的死规则。

对策:用上面的决策框架做判断。不确定的时候,默认放 Memory(信息更完整),CLAUDE.md 只放最简指令。

当你读完这一章,试着做这个练习:

回想今天学到的一个教训。创建一个记忆文件,包含:

  1. 规则(一句话,Claude 应该遵守什么)
  2. Why(这条规则是怎么产生的——具体的对话场景、你踩了什么坑)
  3. originSessionId(当前会话的 ID)
  4. 判断它属于哪一层(A 层始终适用?B 层关键词触发?C 层项目专属?)

做完后,把文件注册到对应的 MEMORY.md 索引中。