Agent 记忆:真实项目中的分层、记录与消费
本文是本人基于实战的看法,由于项目的局限性,无法做到全而精准,大家可以批判的看看,有异议可以友好讨论。如有不认可,那肯定是本人学艺不精,你是对的。
全文约 3500 字, 预计阅读 9 分钟
读完可以:
- 区分会话记忆、项目记忆、用户记忆的职责。
- 用 4W1H 解释记忆的记录、存储与消费,并通过观测验证其效果。
问题问答
在我朋友的面试复盘样本中,约 1/3 面试官都会问到【记忆】,集中在记忆分层、记录时机、按需消费和跨项目同步。以下结合真实项目,说明记忆如何转变为后续任务可使用的上下文。
问题一:Agent 的记忆怎么分层?
面试官:你们的记忆是怎么区分的?
真实项目按作用范围划分三类:“会话记忆”承接当前任务的连续执行;“项目记忆”保存产品规格、约束和协作反馈;“用户记忆”保存跨项目复用的偏好、知识和经验。
其中会话记忆存储在当前 session 中,会受到【压缩】的影响;项目记忆和个人记忆分别维护,按任务需要进入模型上下文,不会受到【压缩】的影响;记忆和 skill 类似,都是渐进式披露的,写入到模型中的都是 catalog,模型自己判断需要对应记忆通过 tool 去 Read 即可。
问题二:Agent 如何保证历史信息能在当前 Run 被消费
如何使用用户之前关注的主题?
这里分两部分回答,一个是当前会话介绍过的主题,一个是在其他会话中提及的主题,两种记忆都是长期记忆,分别隔离存储;
整个 agent 编排中,我们都是优先让 agent 判断是否需要读取长期记忆,如果 agent 能在当前 context 信息中获取到对应信息,则不会去调记忆 catalog 去执行 Read(); 同理会话记忆的优先级会比个人记忆要高,这里主要考虑的是会话记忆在时序上会比个人记忆更加 fresh,在没有进行“同步”前,两者可能会存在差异;
如果用到了长期记忆,基本就是根据 intent, 然后确认是否有对应文件,然后读取对应文件获取【记忆】信息,然后重新写入到 context 中,然后 agent 就是知道用户之前有关注哪些主题了,至于这些主题是否会被消费,这个就看 agent 的使用,以及是否正确引导 agent 去使用这些主题了;
需要注意的是,这里是假设【关注的主题】都被认可写入到了【记忆】中,但是具体是否真实触发【记忆】写入,以及写入的内容是否准确,涉及到【记忆】的其他设计,不在本轮进行介绍。
问题三:不同会话的记忆如何进行交换使用
会话 A 更新记忆后,会话 B 怎么同步?
不同会话的记忆共享,主要存储到【用户记忆】中,这里涉及到了记忆的存储时机和介入时机;
对于任意一个会话,都会存储一份【用户记忆】的副本,而这个副本更新时机是每一轮真实对话后,模型执行前触发的,它会去调用接口查看记忆hash是否有更新,确认是否要更新记忆副本。因此,如果 A 更新记忆时,B 已经处于会话判断后,则 B 在本轮(Run)会话中,确认不会拿到最新的 A 更新后的记忆信息;而只会在下一轮会话时,才能拿到最新的更新信息。
4W1H 简单介绍一下我知道的记忆
一、What:记忆存什么,如何组织?
不同业务需要保存的信息不同。客服 Agent 关注服务记录和用户诉求,开发 Agent 关注产品规格与技术约束,学习 Agent 关注知识背景和表达偏好。结构上,可以按作用范围划分为会话记忆、项目记忆和用户长期记忆。
会话记忆:保存当前任务的过程。
比如用户说“把报名按钮改成确认报名”,随后 Agent 读取页面、修改代码、收到工具结果。这些消息构成连续执行所需的上下文。说人话,就是传给 Model 的 messages 信息。我们在一轮对话中,从 user 提供第一条信息(system 是第 0 条),然后会有 agent 回复的 assistant 信息, 工具调用返回的 tool 信息,共同组成了** 会话记忆 **
原始记录可以持久保存,模型每次调用使用的是当前有效的消息视图;发生压缩后,这个视图会包含摘要和需要保留的近期消息。
项目记忆:保存当前项目的稳定状态与约束。
内容:“这是一个只面向移动端的活动报名工具”“报名无需登录”“已经确认的布局需要保留”。
在一个会话执行过程中,会有策略触发记录关键信息,这些信息属于某一个 session, 会在这一个 session 中长久保存起来,不会由于【压缩】而丢失;这一块记忆会作为 basic 信息,写入到用户的 context 中,用户在某些情况下需要进行查找时,可以读到并拉取完整的信息。
工作区/
├── memory/ 项目记忆,可读写
│ ├── MEMORY.md 项目记忆索引
│ ├── feedback_visual_style.md 设计或协作反馈
│ └── reference_brand_assets.md 外部资料的用途与入口
当然, 如果没有过多的 memory 信息时,可能就只是单纯的一个文件信息即可,这里主要看 memory 写入策略。
用户长期记忆:保存跨项目可复用的信息。
比如用户偏好简洁话语、习惯先看流程图,或者持续关注某个领域以及已经存储的项目记忆目录。换一个项目后,这些信息仍可能帮助 Agent 理解需求。
工作区中的文件结构可以表达为:
工作区/
├── .memory/ 用户长期记忆的只读投影
├── hot.md 简短概览与重要提示
├── index.md 主题、标签与文件路径
├── log.md 记忆整理记录
└── wiki/
├── concepts/ 概念与领域知识
├── entities/ 对象及相关事实
├── references/ 外部资料与引用
├── synthesis/ 综合分析与结论
└── journal/ 有保留价值的阶段记录
【用户记忆】写入 llm 和我们正常阅读 wiki 是一样的,主要是查看 index.md 和 hot.md, 需要更详细内容时,再去读取对应的 wiki/* 信息;index 的内容大概长下面👇的样子
二、When / Where:什么时候加载,又在哪里进入模型?
记忆的准备发生在用户触发一轮对话之后、Agent 调用模型之前。这样,模型第一次判断任务时,就有机会结合已有约束和用户背景。
例如,用户只说“之前在别的项目中,做过需求澄清的分析,这一次也按照之前的方式来进行需求澄清”。如果项目记忆记录到了[需求澄清] 这个记忆,那么 agent 有可能调用 Read(xxx) 读取这个记忆信息,然后加载到当前 context 中,然后进行后续的操作。
这里需要区分两个动作:
- 同步文件:每轮请求开始时,运行时取得用户长期记忆,并更新项目中的只读投影。
- 注入消息:运行时将需要的记忆导览组织成独立的
user消息,放在本轮真实用户输入之前。
实际代码并不是每轮都重复追加完整导览。项目记忆与用户 Wiki 的静态导览主要在首次进入、上下文压缩后注入;其余轮次继续消费上下文中保留的信息,必要时再读取文件。
这一设计让已有知识能够参与最初的任务判断,也减少了重复注入的 token 开销。代价是同步和读取需要时间,导览会占用上下文,而且不同会话之间可能存在版本延迟。
以同一用户的会话 A 和会话 B为例:
- B 开始执行时,读取到的偏好是“使用浅色界面”。
- A 随后更新偏好,并完成长期记忆整理:“今后优先使用深色界面”。
- B 已经在执行,本次输入不会被中途替换,仍可能沿用旧偏好。
- B 下一次请求可以同步新版文件,但上下文中已有的旧概览,还需要通过重新读取或再次注入才能更新。
因此,文件已经同步到最新版本,不等于模型正在使用的记忆也立即更新了。
对于偏好和历史背景等变化较慢的信息,短暂延迟通常可以接受;需要立即生效的当前要求,应通过本轮明确输入或有效业务状态传递。
三、Why:为什么需要记忆?
记忆让已经确认的信息在后续任务中继续有效。
同一个项目经过多轮修改,用户不应该反复解释目标人群、页面范围和设计约束;用户开启另一个项目时,也不应该重新说明所有稳定偏好。项目记忆维护前一种连续性,用户长期记忆支持后一种复用。
压缩进一步体现了这种分工。对话摘要用于承接当前任务,但不适合承担全部长期知识。完整规格、决策依据和主题知识保存在独立文件中,压缩后重新提供入口,Agent 就能按需取回细节。
例如,历史聊天已经压缩,但 MEMORY.md 仍保留整个 session 的核心信息。Agent 需要获取 session 的某些核心信息,可以重新读取 MEMORY.md,查看摘要和 catalog;如果需要详情,可以读取到对应的文档,而不至于丢失核心模块的全部信息。
四、How 1:会话中的信息怎样成为长期记忆?
这条链路经历两次筛选:从对话中提取项目事实,再从项目事实中整理跨项目知识。
第一步发生在正常对话中。消息和工具结果形成会话记录;当用户确认需求、纠正做法或明确要求记住某件事时,Agent 判断哪些信息值得持久保存,再通过文件工具更新 memory/。
例如,用户说:
后续所有学习专题会话都要简洁专业,不要有情绪价值的信息输出。
适合保存的不只是这句话,还包括适用范围和使用方式:
事实:用户偏好简洁专业
适用范围:后续项目的信息输出
使用方式:在学习专题对话中,输出前确认一下信息输出是否过度使用情绪化信息
正文写入独立主题文件,MEMORY.md 增加一条简短入口。若内容已经存在,就更新原主题,避免反复生成同义记录。
第二步由独立的记忆整理任务完成。真实项目中的一种触发策略是:项目会话静默一段时间后,再处理发生变化的记忆文件。
这么做可以减少对尚在反复讨论的信息进行频繁整理,但也意味着个人长期记忆不会在每次对话后立即更新。
整理任务读取项目记忆快照,判断哪些内容应新增、哪些应合并、哪些与旧结论冲突,再更新 Wiki 页面、索引和概览,并保留来源信息。
因此,个人长期记忆的直接原料是经过筛选的项目记忆与用户提供的资料,而非完整聊天记录的无差别复制。
五、How 2:进入模型的是索引,还是另外生成的 Catalog?
源码中的做法是:读取已有的索引和概览内容,再加上固定使用引导,组成记忆导览消息。 Catalog 是这组导航信息的统称,并没有另外生成一份独立的语义目录。
具体包含三部分。
第一部分是项目记忆索引:读取 memory/MEMORY.md,将索引内容放入消息,并限制长度。模型据此知道当前项目有哪些记忆文件。
第二部分是用户记忆概览与索引:读取 .memory/hot.md 和 .memory/index.md。hot.md 提供简短背景,index.md 提供主题到正文路径的映射。
目录较小时,索引页表会一起进入上下文;目录较大时,只注入索引前言和文件入口,引导模型先读取完整 index.md,再定位正文。索引位于 .memory/index.md,wiki/* 存放主题页面。
第三部分是固定使用引导:说明这些信息来自跨项目记忆、当前目录只读、内容可能与本轮任务无关,以及需要时如何寻找对应页面。
记忆相关输入可以抽象为:
user:运行时记忆导览
├── memory/MEMORY.md 的项目索引
├── .memory/hot.md 的简短概览
├── .memory/index.md 的页表,或索引入口
└── 路径、相关性、只读边界等使用说明
user:本轮真实用户问题
这条路径不依赖额外的向量检索服务。知识在写入端已经按主题组织,读取端由模型结合当前问题选择页面。它降低了默认注入量,但增加了按需读取的工具开销,也要求索引足够准确、及时。
观测时应分别检查:模型是否看到了目录、是否选择了正确页面、读取是否成功,以及最终回答或行动是否正确使用了正文。“目录已注入”只证明记忆可被发现,不能单独证明记忆已经生效。
小结
当对话、工具结果和已展开的记忆正文不断增长,接下来的问题就是:如何通过【压缩】控制上下文规模,同时保留任务状态,并恢复这些记忆的消费入口。这一块我们下一次再讲