跳转至

记忆机制

概念

  • 短期记忆:context
  • 长期记忆:存储在外部

长期记忆的要务就是:存什么、怎么存、什么时候用、用哪些。

存什么

再借用认知科学的一些词(情节、语义、程序)的词汇,长期记忆可以分出这些。

第一种是「情节记忆」(Episodic Memory),存的是具体的事件经历。比如「上周二用户让我写了一个 Python 爬虫,中间遇到了反爬问题,最后用 Selenium 解决了」,这是一段完整的任务经历,包含了时间、场景、过程和结果。情节记忆的价值在于,当 Agent 遇到类似的新任务时,可以检索出历史上的相似经历,参考上次是怎么解决的,避免重复踩坑。

第二种是「语义记忆」(Semantic Memory),存的是从多次经历中提炼出来的通用知识和规律。比如经历了好几次反爬问题之后,Agent 沉淀出一条规律:「当目标网站有 JavaScript 动态渲染时,requests 库抓不到内容,应该优先考虑 Selenium 或 Playwright」。这不再是某一次具体的事件记录,而是跨多次经验总结出来的抽象知识。语义记忆的信息密度更高,检索时也更容易命中,因为它直接存储的就是结论而不是过程。

第三种是「程序记忆」(Procedural Memory),存的是怎么做某件事的操作流程。比如「部署一个 Flask 应用的标准步骤:创建虚拟环境 -> 安装依赖 -> 配置 gunicorn -> 设置 nginx 反向代理 -> 启动服务」,这是一套可以直接复用的操作 SOP。程序记忆在处理重复性任务时特别有用,Agent 不需要每次都从头推理,直接调出对应的 SOP 执行就行,既快又稳。

三种子类型各有侧重,实际项目中通常会混合使用。情节记忆提供具体的参考案例,语义记忆提供抽象的知识规律,程序记忆提供可复用的操作流程,三者配合起来才能让 Agent 的长期记忆真正好用。

怎么存

根据信息的类型选合适的存储介质,混合存储是主流做法:结构化的偏好字段用关系数据库精确查,非结构化的知识和历史用向量数据库语义检索,两者配合使用。

什么时候用、用哪些

开始时可以做一次主动检索,把和当前任务稳定相关的规则、背景或经验加载进上下文;任务执行过程中,遇到需要专业知识、历史结论或外部事实的步骤,再让 Agent 按需检索。具体加载什么要看产品形态:个人助手可能需要用户偏好,专业系统可能更需要经过审查的经验、规则和证据。

记忆整理

去重、消解冲突

第一个环节是去重。把语义相近的多条记忆合并成一条更完整的版本。比如你存了「用户喜欢简洁的代码」「用户说过代码要精简」「用户要求不要冗余代码」三条,其实表达的是同一个意思,合并成一条「用户偏好简洁精练的代码风格,反对冗余」就够了。

第二个环节是冲突消解。当两条记忆互相矛盾时(比如「用户偏好 Python」和后来说的「最近转用 Go 了」),保留时间更新的那条,标记旧的为过期。这里时间戳就非常关键了,没有时间戳就无法判断哪条是最新的。

Claude Code 的做法

Claude Code 的记忆机制很值得参考,因为它没有把长期记忆默认做成复杂数据库,而是更接近「本地文件 + 小索引 + 按需读取」。这和 skill 的形态很像:都是外部 Markdown 材料,都是在需要时进入上下文。但两者的语义不同:skill 更像预装能力,回答「这类事一般怎么做」;memory 更像后天经验,回答「这个用户、这个项目、这段历史有什么特殊之处」。

核心结构

它的核心结构大致可以这样分:

  • CLAUDE.md:人工维护的项目规则和工作约定,比如测试命令、代码风格、架构偏好。这更像稳定的项目说明,而不是自动学习出来的记忆。
  • MEMORY.md:自动记忆的索引文件。官方文档明确说它是 memory directory 的入口和 index;启动时只加载前 200 行或前 25KB,而不是把所有历史经验都塞进 context。
  • topic files:具体主题的详细记忆,比如调试经验、用户偏好、项目决策背景。只有任务需要时再读取。

MEMORY.md 的角色不是大笔记本,而是路由表。它应该短,里面放的是「有哪些主题、每个主题大概是什么、细节去哪个文件看」。启动时 Agent 先看到这个索引,再决定是否读取某个 topic file。也就是说,长期记忆不是无限追加到上下文里,而是先常驻一个小目录,需要细节时再打开具体文件。

Transcripts 的位置

transcripts 要单独看。它们很重要,但本质上不是记忆,而是历史会话日志,是记忆的来源材料。Claude Code 会把 session 保存成本地 JSONL,这个形式很朴素但合理:会话本来就是 append-only 的事件流,一行一条消息、工具调用或元数据,方便追加、恢复、grep 和局部处理。公开分析里提到的 Auto Dream 会从 transcripts 里寻找值得沉淀的信号,但这一步发生之后,真正进入长期记忆的应该是被筛选、压缩、归类后的内容,而不是 transcript 本身。

所以更准确的边界是:transcripts 是 history/log/source material,MEMORY.md 和 topic files 才是整理后的 memory。把 transcript 直接当记忆,会把原始过程和可复用经验混在一起,最后反而让上下文变脏。

写入方式

写入长期记忆大致可以抽象成两种形态:live write 和后台整理。

live write 是当前会话里的 Agent 在工作过程中判断某条信息未来还有用,于是直接写入 memory。Claude Code 的官方 auto memory 更接近这种形态:官方文档说,当界面显示 “Writing memory” 或 “Recalled memory” 时,Claude 正在读写 ~/.claude/projects/<project>/memory/;用户明确说「remember that ...」时,Claude 也会把它保存到 auto memory。也就是说,在 Claude Code 的事实机制里,主 Agent 不只是产出候选,它可以直接写长期记忆。

后台整理是把会话日志、运行结果、历史 thread 等材料先当作来源,之后由后台流程提取、压缩、合并,再写成更稳定的 memory。Codex 的官方 memory 机制更接近这种形态:默认关闭;开启后,Codex 会从符合条件的历史 thread 中提取有用上下文,后台生成或更新 ~/.codex/memories/ 下的本地记忆文件。公开技术分析里提到的 Claude Code Auto Dream,也更像后台整理角色,但这类细节不应和官方文档确认的机制混为一谈。

这两种形态可以同时存在。live write 更及时,适合用户明确要求“记住这个”或当前 Agent 很确定的信息;后台整理更适合从 transcripts、artifacts、历史 thread 里提炼稳定经验。无论哪种方式,前提都应该是用户可审计、可回滚、可关闭。记忆不是模型权重的学习,而是 agent 在外部存储里维护的上下文材料。

存储和规则边界

从 Claude Code 和 Codex 目前公开的设计看,长期记忆不一定需要先从向量数据库做起。两者都更强调本地文件、索引和按需加载。

向量检索可以作为补充,尤其适合跨项目、跨团队、海量非结构化历史和相似案例检索。更保守的说法是:好的长期记忆通常应该尽量少量、高密度、可审计、可更新、可废弃,而不是默认把所有历史都塞进一个大检索库里。

关系数据库也不一定要叫记忆。比如订单、issue、CI 记录、用户配置这些外部事实,更像是 Agent 通过工具查询的事实源。它们可以被放进上下文参与推理,但不等于 Agent 自己沉淀出的经验。更像记忆的是用户偏好、历史纠正、项目约定、调试结论、决策背景这些东西。

进一步说,因为 Claude Code 的 system prompt 不是用户可以长期直接改写的东西,用户真正可控的持久行为层其实分散在几个地方:稳定规则放进 CLAUDE.md 或 rules;必须强制执行的规则放进 settings、permissions 或 hooks;可复用的个人工作法做成 skills;项目和用户的后天经验放进 memory。