Learn Claude Code
s09

Memory System

System Hardening

Keep Only What Survives Sessions|414 LOC|5 tools

Memory gives direction; current observation gives truth.

s00 > s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > [ s09 ] > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > s18 > s19

不是所有信息都该进入 memory;只有跨会话仍然有价值的信息,才值得留下。

这一章在解决什么问题

如果一个 agent 每次新会话都完全从零开始,它就会不断重复忘记这些事情:

  • 用户长期偏好
  • 用户多次纠正过的错误
  • 某些不容易从代码直接看出来的项目约定
  • 某些外部资源在哪里找

这会让系统显得“每次都像第一次合作”。

所以需要 memory。

但先立一个边界:memory 不是什么都存

这是这一章最容易讲歪的地方。

memory 不是“把一切有用信息都记下来”。

如果你这样做,很快就会出现两个问题:

  1. memory 变成垃圾堆,越存越乱
  2. agent 开始依赖过时记忆,而不是读取当前真实状态

所以这章必须先立一个原则:

只有那些跨会话仍然有价值,而且不能轻易从当前仓库状态直接推出来的信息,才适合进入 memory。

建议联读

  • 如果你还把 memory 想成“更长一点的上下文窗口”,先回 s06-context-compact.md,重新确认 compact 和长期记忆是两套机制。
  • 如果你在 messages[]、摘要块、memory store 这三层之间开始读混,建议边看边对照 data-structures.md
  • 如果你准备继续读 s10,最好把 s10a-message-prompt-pipeline.md 放在旁边,因为 memory 真正重要的是它怎样重新进入下一轮输入。

先解释几个名词

什么是“跨会话”

意思是:

  • 当前对话结束了
  • 下次重新开始一个新对话
  • 这条信息仍然可能有用

什么是“不可轻易重新推导”

例如:

  • 用户明确说“我讨厌这种写法”
  • 某个架构决定背后的真实原因是合规要求
  • 某个团队总在某个外部看板里跟踪问题

这些东西,往往不是你重新扫一遍代码就能立刻知道的。

最适合先教的 4 类 memory

1. user

用户偏好。

例如:

  • 喜欢什么代码风格
  • 回答希望简洁还是详细
  • 更偏好什么工具链

2. feedback

用户明确纠正过你的地方。

例如:

  • “不要这样改”
  • “这个判断方式之前错过”
  • “以后遇到这种情况要先做 X”

3. project

这里只保存不容易从代码直接重新看出来的项目约定或背景。

例如:

  • 某个设计决定是因为合规而不是技术偏好
  • 某个目录虽然看起来旧,但短期内不能动
  • 某条规则是团队故意定下来的,不是历史残留

4. reference

外部资源指针。

例如:

  • 某个问题单在哪个看板里
  • 某个监控面板在哪里
  • 某个资料库在哪个 URL

哪些东西不要存进 memory

这是比“该存什么”更重要的一张表:

不要存的东西为什么
文件结构、函数签名、目录布局这些可以重新读代码得到
当前任务进度这属于 task / plan,不属于 memory
临时分支名、当前 PR 号很快会过时
修 bug 的具体代码细节代码和提交记录才是准确信息
密钥、密码、凭证安全风险

这条边界一定要稳。

否则 memory 会从“帮助系统长期变聪明”变成“帮助系统长期产生幻觉”。

最小心智模型

conversation
   |
   | 用户提到一个长期重要信息
   v
save_memory
   |
   v
.memory/
  ├── MEMORY.md        # 索引
  ├── prefer_tabs.md
  ├── feedback_tests.md
  └── incident_board.md
   |
   v
下次新会话开始时重新加载

这一章最关键的数据结构

1. 单条 memory 文件

最简单也最清晰的做法,是每条 memory 一个文件。

---
name: prefer_tabs
description: User prefers tabs for indentation
type: user
---
The user explicitly prefers tabs over spaces when editing source files.

这里的 frontmatter 可以理解成:

放在正文前面的结构化元数据。

它让系统先知道:

  • 这条 memory 叫什么
  • 大致是什么
  • 属于哪一类

2. 索引文件 MEMORY.md

最小实现里,再加一个索引文件就够了:

# Memory Index

- prefer_tabs: User prefers tabs for indentation [user]
- avoid_mock_heavy_tests: User dislikes mock-heavy tests [feedback]

索引的作用不是重复保存全部内容。
它只是帮系统快速知道“有哪些 memory 可用”。

最小实现步骤

第一步:定义 memory 类型

MEMORY_TYPES = ("user", "feedback", "project", "reference")

第二步:写一个 save_memory 工具

最小参数就四个:

  • name
  • description
  • type
  • content

第三步:每条 memory 独立落盘

def save_memory(name, description, mem_type, content):
    path = memory_dir / f"{safe_name}.md"
    path.write_text(frontmatter + content)
    rebuild_index()

第四步:会话开始时重新加载

把 memory 文件重新读出来,拼成一段 memory section。

第五步:把 memory section 接进系统输入

这一步会在 s10 的 prompt 组装里系统化。

memory、task、plan、CLAUDE.md 的边界

这是最值得初学者反复区分的一组概念。

memory

保存跨会话仍有价值的信息。

task

保存当前工作要做什么、依赖关系如何、进度如何。

plan

保存“这一轮我要怎么做”的过程性安排。

CLAUDE.md

保存更稳定、更像长期规则的说明文本。

一个简单判断法:

  • 只对这次任务有用:task / plan
  • 以后很多会话可能都还会有用:memory
  • 属于长期系统级或项目级固定说明:CLAUDE.md

初学者最容易犯的错

错误 1:把代码结构也存进 memory

例如:

  • “这个项目有 src/tests/
  • “这个函数在 app.py

这些都不该存。

因为系统完全可以重新去读。

错误 2:把当前任务状态存进 memory

例如:

  • “我现在正在改认证模块”
  • “这个 PR 还有两项没做”

这些是 task / plan,不是 memory。

错误 3:把 memory 当成绝对真相

memory 可能过时。

所以更稳妥的规则是:

memory 用来提供方向,不用来替代当前观察。

如果 memory 和当前代码状态冲突,优先相信你现在看到的真实状态。

从教学版到高完成度版:记忆系统还要补的 6 条边界

最小教学版只要先把“该存什么 / 不该存什么”讲清楚。
但如果你要把系统做到更稳、更像真实工作平台,下面这 6 条边界也必须讲清。

1. 不是所有 memory 都该放在同一个作用域

更完整系统里,至少要分清:

  • private:只属于当前用户或当前 agent 的记忆
  • team:整个项目团队都该共享的记忆

一个很稳的教学判断法是:

  • user 类型,几乎总是 private
  • feedback 类型,默认 private;只有它明确是团队规则时才升到 team
  • projectreference,通常更偏向 team

这样做的价值是:

  • 不把个人偏好误写成团队规范
  • 不把团队规范只锁在某一个人的私有记忆里

2. 不只保存“你做错了”,也要保存“这样做是对的”

很多人讲 memory 时,只会想到纠错。

这不够。

因为真正能长期使用的系统,还需要记住:

  • 哪种不明显的做法,用户已经明确认可
  • 哪个判断方式,项目里已经被验证有效

也就是说,feedback 不只来自负反馈,也来自被验证的正反馈。

如果只存纠错,不存被确认有效的做法,系统会越来越保守,却不一定越来越聪明。

3. 有些东西即使用户要求你存,也不该直接存

这条边界一定要说死。

就算用户说“帮我记住”,下面这些东西也不应该直接写进 memory:

  • 本周 PR 列表
  • 当前分支名
  • 今天改了哪些文件
  • 某个函数现在在什么路径
  • 当前正在做哪两个子任务

这些内容的问题不是“没有价值”,而是:

  • 太容易过时
  • 更适合存在代码、任务板、git 记录里
  • 会把 memory 变成活动日志

更好的做法是追问一句:

这里面真正值得长期留下的、非显然的信息到底是什么?

4. memory 会漂移,所以回答前要先核对当前状态

memory 记录的是“曾经成立过的事实”,不是永久真理。

所以更稳的工作方式是:

  1. 先把 memory 当作方向提示
  2. 再去读当前文件、当前资源、当前配置
  3. 如果冲突,优先相信你刚观察到的真实状态

这点对初学者尤其重要。
因为他们最容易把 memory 当成“已经查证过的答案”。

5. 用户说“忽略 memory”时,就当它是空的

这是一个很容易漏讲的行为边界。

如果用户明确说:

  • “这次不要参考 memory”
  • “忽略之前的记忆”

那系统更合理的处理不是:

  • 一边继续用 memory
  • 一边嘴上说“我知道但先忽略”

而是:

在这一轮里,按 memory 为空来工作。

6. 推荐具体路径、函数、外部资源前,要再验证一次

memory 很适合保存:

  • 哪个看板通常有上下文
  • 哪个目录以前是关键入口
  • 某种项目约定为什么存在

但在你真的要对用户说:

  • “去改 src/auth.py
  • “调用 AuthManager
  • “看这个 URL 就对了”

之前,最好再核对一次。

因为命名、路径、系统入口、外部链接,都是会变的。

所以更稳妥的做法不是:

memory 里写过,就直接复述。

而是:

memory 先告诉我去哪里验证;验证完,再给用户结论。

教学边界

这章最重要的,不是 memory 以后还能多自动、多复杂,而是先把存储边界讲清楚:

  • 什么值得跨会话留下
  • 什么只是当前任务状态,不该进 memory
  • memory 和 task / plan / CLAUDE.md 各自负责什么

只要这几层边界清楚,教学目标就已经达成了。

更复杂的自动整合、作用域分层、自动抽取,都应该放在这个最小边界之后。

学完这章后,你应该能回答

  • 为什么 memory 不是“什么都记”?
  • 什么样的信息适合跨会话保存?
  • 为什么代码结构和当前任务状态不应该进 memory?
  • memory 和 task / plan / CLAUDE.md 的边界是什么?

一句话记住:memory 保存的是“以后还可能有价值、但当前代码里不容易直接重新看出来”的信息。