本文是「从零理解 Claude Code:20 个 Agent Harness 机制」系列的第 9 篇。 源码仓库:shareAI-lab/learn-claude-code 本文基于开源仓库学习整理,具体实现以仓库代码为准。
上一章处理上下文压缩时,有一个问题一直没有消失。
假设用户在任务开始时说过,项目沿用 Tab 缩进,字符串使用单引号。Agent 读了很多文件、跑了几轮测试,历史消息触发压缩。摘要通常会保留当前任务、修改过的文件和待办事项,却未必会完整保留这类细节。
新开一个会话后,情况更直接。上一段对话的摘要也不存在了。
如果 Agent 每次都要重新询问代码风格、项目背景和已经确认过的工作方式,长期使用体验会很差。反过来,把全部历史对话永久塞进上下文,又会回到上一章遇到的容量问题。
这一章引入的 Memory,负责保存那些在后续任务中仍然有用的信息,并在需要时重新放回当前上下文。
记忆文件和任务记录不是一回事
先区分两类信息。
下面这些内容通常只服务于当前任务:
修复 tests/auth/test_token.py
重新运行认证模块测试
检查 token 配置是否影响其他测试
它们适合放在 TodoWrite 或当前会话历史中。任务结束后,继续保留的价值会快速下降。
另一些信息会在后续任务里反复出现:
项目使用 Tab 缩进
不要在测试中 Mock 数据库
认证模块改造受合规要求限制
排查数据导入问题时优先查看 Linear 中的 INGEST 任务
这类内容跨任务、跨会话仍然可能有用,适合进入 Memory。
仓库将记忆分成四种类型:
| user | 用户长期偏好 | 使用 Tab 缩进 |
| feedback | 工作方式和修正意见 | 测试中不要 Mock 数据库 |
| project | 项目背景和约束 | 认证改造受合规要求限制 |
| reference | 外部入口和排查线索 | 导入问题关联 Linear 的 INGEST 任务 |
这个分类没有改变模型的能力。它的作用是帮助后续加载和整理时区分:这是一条用户偏好,还是一条可能过期的项目事实。
记忆先写入文件,再生成目录
教学代码把记忆保存在项目目录下的 .memory/:
.memory/
MEMORY.md
user-preference-tabs.md
feedback-no-mock-db.md
project-auth-compliance.md
每条记忆是单独的 Markdown 文件,文件顶部放 YAML Frontmatter:
—
name: user-preference-tabs
description: User prefers tabs for indentation
type: user
—
User prefers using tabs for indentation.
Why: Consistency with existing codebase conventions.
How to apply: Always use tabs when writing or editing files.
单独存文件有两个好处。
第一,某条记忆可以被独立更新或删除,不需要修改一份越来越长的大文件。第二,文件名、描述和类型可以组成一个轻量目录,供 Agent 先判断是否需要加载。
MEMORY.md 就承担这个目录角色:
– [user-preference-tabs](user-preference-tabs.md) — User prefers tabs for indentation
– [feedback-no-mock-db](feedback-no-mock-db.md) — Do not mock the database in tests
– [project-auth-compliance](project-auth-compliance.md) — Auth rewrite is compliance-driven
程序会把 MEMORY.md 放进 System Prompt,单个记忆文件的完整内容则留在磁盘上。这样,模型每轮都知道有哪些历史信息可以使用,却不必每次都携带全部正文。
下面这张图说明了目录和单条记忆文件之间的关系。

Agent 如何判断该加载哪几条记忆
用户发出新请求时,程序会取最近对话内容和 MEMORY.md 中的目录,发起一次轻量选择请求。
目录里只包含名称和描述,例如:
0: user-preference-tabs — User prefers tabs for indentation
1: feedback-no-mock-db — Do not mock the database in tests
2: project-auth-compliance — Auth rewrite is compliance-driven
选择模型返回相关条目的序号,程序最多读取 5 个对应文件,并将完整内容附加到当前用户消息前面。
如果用户这次说的是:
为认证模块补一个新的测试用例。
程序可能会加载 feedback-no-mock-db 和 project-auth-compliance。Tab 缩进偏好是否加载,取决于选择模型对当前任务的判断,也取决于项目里是否已经有更强的格式化规则。
这条路径包含一次额外的模型调用,因此不能把它理解成完全免费的检索。教学代码在选择请求失败时,会退回到文件名和描述的关键词匹配。关键词匹配只能提供基本兜底,复杂语义下仍然可能漏掉相关记忆。
为什么提取时要使用压缩前的消息
上一章的压缩会缩短工具结果,也可能把完整对话替换成摘要。
如果记忆提取发生在压缩之后,用户前面说过的偏好可能已经被概括成一段很模糊的话,甚至已经不在消息历史里。提取器从这种历史中工作,很容易写出信息不完整的记忆。
这一章在每一轮压缩前先保存一个 pre_compress 快照。任务结束后,extract_memories() 从这份快照的最近消息中提取候选记忆:
pre_compress = copy_messages(messages)
messages[:] = tool_result_budget(messages)
messages[:] = snip_compact(messages)
messages[:] = micro_compact(messages)
# 模型完成当前任务后
extract_memories(pre_compress)
例如,用户明确说过:
后续修改 Python 文件时统一使用 Tab,保持和仓库现有代码一致。
压缩后的摘要可能只剩下:
用户有代码风格偏好。
前者可以写成可执行的记忆文件,后者无法指导具体编辑行为。保存压缩前快照,目的就是让提取器看到更完整的原始表述。
教学代码每轮结束后会检查最近十条消息,并把已有记忆目录一并交给提取器。提取器返回新记忆时,程序会写入 Markdown 文件并重建 MEMORY.md。
这个过程依赖模型提取信息,结果并不天然可靠。用户偏好、项目事实和一次性任务指令之间的边界需要靠提取提示词约束,也需要后续整理机制修正。
记忆变多以后,合并本身也有风险
记忆文件不会永远保持干净。
用户可能先说测试中不要 Mock 数据库,后来又补充只有支付模块必须连接真实测试库。项目状态也会变化,半年前记录的入口文件、接口地址和排查路径可能已经失效。
教学代码在记忆文件达到 10 条后执行 consolidate_memories()。它将已有记忆交给模型,要求合并重复项、删除过期或互相矛盾的信息,并控制总数量。
这一步带来一个取舍。
合并能够减少重复,目录也更容易被模型选择;模型对历史信息的归纳同样可能写错。教学代码会删除旧记忆文件,再写入合并后的结果,没有版本控制、锁文件或人工确认机制。
因此,这套 Memory 更适合保存工作偏好、稳定项目背景和可验证的参考入口。权限规则、生产配置、合规要求等高风险信息仍然需要存放在受版本控制的配置或文档中,不能只依赖自动提取的记忆文件。
跑一下这章代码
进入仓库目录后执行:
python s09_memory/code.py
可以分几轮输入:
I prefer using tabs for indentation, not spaces. Remember that.
任务结束后,终端应出现类似 [Memory: extracted 1 new memories] 的提示,并在 .memory/ 下生成对应的 Markdown 文件。
接着输入:
Create a Python file called test.py.
观察 Agent 是否加载了之前的偏好,并在生成代码时遵循缩进约定。重新启动程序后,再询问之前的格式偏好,可以检查记忆是否跨会话保留。
测试时可以顺便查看 .memory/MEMORY.md。目录内容越清楚,相关记忆越容易被选择;单条描述越模糊,模型越难判断它和当前任务是否有关。
下一篇将讨论 System Prompt。
这一章的目录、记忆注入、工具说明和项目路径仍然由代码中的固定字符串拼接。System Prompt 继续增长后,如何按运行环境、项目类型和可用工具组装不同片段,会成为下一步的问题。
网硕互联帮助中心


评论前必须登录!
注册