压缩完上下文后,Agent 怎么还记得你的开发习惯
本文是「从零理解 Claude Code20 个 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_compresscopy_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 继续增长后如何按运行环境、项目类型和可用工具组装不同片段会成为下一步的问题。