如果你也想弄懂 Claude Code 这类 Coding Agent 到底是怎么工作的,这个仓库也许能帮你少走一些弯路。目前我的 github 的项目(OpenAI SDK 版),我学习的课程为 learn-claude-code 的 v2 版本课程(感谢原作者),大家一起学习,共同进步。如果对你有帮助,请为我点一个 Star。

项目地址:https://github.com/peijiping/learn-claude-code-langchain


为什么 Coding Agent 需要记忆?

LLM 天生是"金鱼记忆"——每次对话都是从头再来。上次你告诉它"用 bun 不要 npm",下次它又忘了。没有记忆的 Agent 永远是新手:每次都要重新了解你的偏好、重新踩同样的坑。

Claude Code(以下简称 CC)的做法是把记忆落盘到文件,跨会话、跨进程持久化。这是它和我们平时用 Chat 的最大区别之一——它不是"聊完就忘",而是会积累关于你、关于项目的知识。

一、CC 的记忆长什么样

CC 的记忆存储在用户目录下,基于你的项目 git 根路径生成一个目录:

~/.claude/projects/<sanitized-git-root>/memory/
├── MEMORY.md                  # 索引文件(≤200行/25KB)
├── user_role.md               # 用户角色记忆
├── feedback_testing.md        # 反馈记忆
├── project_auth_rewrite.md    # 项目记忆
├── reference_linear.md        # 参考记忆
├── team/                      # 团队共享记忆
└── logs/                      # 日志模式

几个关键点:

  • MEMORY.md 是固定的"索引文件",永远存在,作用是给模型一个"我有哪些记忆"的目录视图。
  • 其他文件都是按需生成的,根据你的对话内容实时产生,不同项目、不同用户看到的完全不一样,没有固定模板。
  • 记忆文件用 Markdown + YAML frontmatter 的结构:开头几行是元数据(name / description / type),下面是正文详情。

二、四类记忆:该记什么、什么时候记

CC 把记忆分成四类,每种类型都定义了"什么时候记":

类型 含义 写入时机
user 用户的角色、偏好、知识背景 学到用户信息时(如"我习惯用 tab 缩进")
feedback 行为纠正 + 正向确认 用户纠正了你的做法,或肯定了某个非显而易见的做法时
project 项目目标、约束、deadline 了解到不可从代码推导的信息时(如"上线日期是 X")
reference 外部系统指针 知道某个外部资源的位置和用途时(如"bug 跟踪在 Linear")

同时,CC 也明确规定了什么不该记:代码模式(从代码里能读到的东西)、Git 历史(用 git log 就能查到)、调试方案(修复就写在代码里)、临时任务状态。

三、记忆怎么读

CC 读取记忆有两条路:

  1. 索引常驻 System Prompt——MEMORY.md 的索引永远跟着系统提示走,模型每轮对话都能看到"我有哪些记忆"。这条路径 token 开销极小,是记忆的"目录"。
  2. 按需读取正文——模型发现某条记忆和当前任务相关时,通过 read_file 工具读具体的记忆文件。记忆正文塞进 System Prompt,避免上下文被撑爆。

这个"索引-内容分离"的设计是精髓:轻量索引常驻,完整内容按需加载,兼顾了效率和上下文控制。

四、记忆怎么写:这是核心

这里是我学习过程中最大的认知转变。

教程版:事后分析(不推荐)

教程 s09 的做法是事后分析:每个 turn 结束后,额外调一次 LLM,把最近一段对话喂给它,让它分析"刚才聊了什么值得记",返回 JSON 再批量写盘。

# 教程版伪代码:每轮结束都要"抽取记忆"
if response.stop_reason != "tool_use":
    extract_memories(pre_compress)   # ← 额外一次 LLM 调用
    consolidate_memories()            # ← 还要整合去重

这带来了三个问题:

  1. ——每轮对话额外多 1~2 次 LLM 调用,成本翻倍。
  2. 不准——靠模型事后"猜"哪些值得记,经常记不到点子上,或者把随口一句话当成偏好。
  3. ——事后批量抽取天然会产生重复、矛盾的条目,所以教程又得写一个 consolidate_memories(做梦/Dream)来清洗数据——抽脏了再洗,等于自己给自己找事

CC 真实版:Tool 驱动(模型自己决定)

CC 的真实做法截然相反——把记忆的写入权交给模型,给模型两个工具,让它自己判断什么时候动手:

TOOLS = [
    # 写入记忆
    {"name": "write_memory",
     "description": "当用户表达偏好、纠正做法、确认方案、透露项目事实时调用",
     "input_schema": {...}},   # name / type / description / body
    # 删除记忆
    {"name": "forget_memory",
     "description": "当用户推翻某条已有记忆时调用",
     "input_schema": {...}},
]

然后在 System Prompt 里写清楚触发规则

## Memory system
调用 write_memory 的场景:
- 用户说出个人偏好("我喜欢 X"、"别用 Y")
- 用户纠正你的做法("不对,应该这样")
- 用户肯定了某个做法("对,就这样")
- 用户透露项目事实或约束("我们用 PostgreSQL"、"截止日期是 X")
- 用户明确说"记住这个"

调用 forget_memory 的场景:
- 用户推翻了某条已保存的记忆

核心变化:记忆的触发权从代码转移到了模型。 代码不再替模型做"哪些值得记"的判断,而是给模型工具和规则,让它在对话过程中实时决策。用户说"记住这个"的下一秒,模型就直接调 write_memory 落盘了。

两种方式的对比

维度 教程版(事后分析) CC 真实版(Tool 驱动)
额外 LLM 调用 每轮 1~2 次 0 次(只有文件 IO)
触发时机 turn 结束后被动批处理 对话中模型即时主动决策
写入精度 靠"猜",易漏易错 模型参与对话,知道什么值得记
数据质量 易产生重复/矛盾 源头干净
是否需要做梦 需要(清洗脏数据) 不需要

关于"做梦"(Auto Dream / consolidate)

教程版因为"事后分析"会抽出一堆重复矛盾的数据,所以发明了"做梦"来全量重写、去重、合并。但只要源头干净,清洗环节就是多余的——Tool 驱动模式下,模型不会重复抽取同一件事;用户推翻了旧记忆,模型直接调 forget_memory 删掉再写新的,根本不需要集中整合。

所以我的结论是:设计上可以砍掉 Dream。记忆系统只需要"按需更新",不需要"定期做梦"。

五、我把教程版改造成了 CC 真实版

基于上面这个认知,我把教程的 s09 代码做了一个简化重构(s09_code_cc.py),原则是只保留必要的,删掉一切为了"事后分析"而存在的代码

  • 删除select_relevant_memories / load_memories / extract_memories / consolidate_memories 四个函数——它们都是"事后分析"模式的产物,每轮浪费 1~2 次 LLM 调用。
  • 新增write_memory / forget_memory 两个工具,模型自主调用,即时落盘。
  • 重写build_system(),把触发规则写清楚。
  • 简化agent_loop(),去掉所有记忆预处理/后处理,只保留压缩管道。

整个记忆系统最后只剩三句话的核心:

  1. MEMORY.md 索引在 System Prompt 里 —— 模型永远看得到有什么
  2. write_memory / forget_memory 两个工具 —— 模型自己决定何时写、何时删
  3. System Prompt 里写清楚触发规则 —— 告诉模型什么时候该动手

干净利落,零额外 token 开销。

六、我的思考:垂直场景(医疗)下怎么设计记忆

这是我把记忆系统用到自己工作(医疗 AI)时的思考,供参考。

记忆需要基于用户的指令提炼

自动化后台生成的数据,不能直接当成用户的记忆。 记忆必须来自用户主动表达或操作指令的提炼总结。系统后台自动跑的日志、结果、对话,顶多算"数据",不算"记忆"——因为用户并没有明确表态"我喜欢这样"。

医生的记忆

  • 初始化阶段:需要医生主动写入自己的偏好(比如"我习惯看结构化报告"、"用药方案倾向保守")。
  • 运行阶段:大模型根据医生后续的操作指令去更新记忆(比如医生纠正过一次用药建议,这就是一条 feedback 记忆)。
  • 关键限制:如果只是后端自动生成的结果和对话,不可以抽取成医生的偏好和记忆——那是系统的输出,不是医生的表态。

患者的记忆 —— 其实叫"患者摘要"更准确

理论上,患者不直接和模型交互,所以患者没有"记忆"

准确地说,大模型对患者临床数据的提炼总结,应该叫患者摘要,而不是患者记忆。比如患者的性别、年龄、疾病史、过敏史、手术史——这些是"可以直接拿来给大模型参考使用的精简数据",属于摘要性质,而不是用户主动表达的偏好。

这个区分很重要:记忆属于"对话方"(用户/医生),摘要属于"被描述对象"(患者/数据)。设计系统时搞清这一层,才不会把数据当记忆用,也不会给没有交互的对象硬造记忆。

七、总结

CC 记忆系统的本质,可以浓缩成一句话:

用文件做存储,用工具做写入,用 System Prompt 做规则,让模型自己在对话中决策。

  • 存储:MEMORY.md 索引 + 按需生成的详情文件
  • 读取:索引常驻 System Prompt,正文按需 read_file
  • 写入:write_memory / forget_memory 两个工具,模型自主调用
  • 不需要"做梦":源头干净,按需更新即可

这也是一个很好的工程原则:把判断交给最懂上下文的人(模型),把执行做成最小的工具(写入/删除),把规则写进它每轮都看得到的地方(System Prompt)


如果你也想弄懂 Claude Code 这类 Coding Agent 到底是怎么工作的,欢迎到我的仓库一起交流。如果对你有帮助,请为我点一个 Star ⭐
https://github.com/peijiping/learn-claude-code-langchain

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐