你有没有遇到过这样的尴尬:跟一个Agent聊了一下午,让它帮你梳理了一份特别详细的方案,晚上关了页面,第二天再打开,它一脸无辜地反问“请问你需要什么帮助”。更尴尬的是,你问它“你的名字是什么”,它能流畅地背出一段自我说明,但你再追问“那你记得我上次让你查的那个项目吗”,它沉默了。
这个场景戳中的,正是Agent记忆能力的老大难问题。大家天天说“给Agent装上记忆”,可真到落地就会发现,记忆不是一句话能解决的问题,它是一整套信息管道。我这篇就把这个问题拆开揉碎来讲:Agent的记忆从系统设计角度看究竟有哪几种,每一种应该用什么方式存储、更新和读取,以及最容易被忽略的——一个Agent要怎么记住自己的名字,并让这个身份感反过来成为用户信任的一部分。内容主要面向正在做Agent应用开发、或是想系统理解记忆机制的工程师,也适合产品经理用来评估需求边界,读完至少能对“记忆”这个词祛魅,知道哪些钱该花在哪里。
1. 记忆不是一个“大筐”,先建立分类框架才能谈设计
在动手写代码之前,我建议先做一件事:忘掉“给Agent加记忆”这种模糊说法,改成讨论具体的信息形态。因为记忆这个词在普通语境下是一个东西,但在系统设计语境下,它至少包含五种完全不同的类型:工作记忆、语义记忆、情景记忆、程序记忆,以及经常被忽略的自传体记忆。它们的存储介质不同、读写频率不同、失效方式也不同,硬塞进同一个内存对象里只会让系统越来越乱。
打个比方。人类的记忆不是一块硬盘,更像一个办公室:
- 工作记忆是桌面上摆着的文件,做完一件事就要归档或丢掉;
- 语义记忆是书架上的百科词典,讲的是“世界是怎么回事”;
- 情景记忆是档案柜里的项目记录,讲的是“发生过哪些事”;
- 程序记忆是贴在墙上的操作手册,告诉你“事情应该怎么做”;
- 自传体记忆则是墙上的营业执照和名片,定义“我是谁”。
大多数初阶实现只做了两类事:把上下文塞进Prompt,把历史记录丢进向量数据库。这其实远远不够。上下文窗口解决的是“当下工作台”的问题,向量库解决的是“曾经发生过什么”的检索问题,但Agent对自己身份的认知、对用户关系的把握、对操作流程的内化,都没有被专门设计。
我经常在评审方案时问一个问题:如果你的Agent被用户问到“你叫什么名字”“我们上次聊到哪了”“遇到这种情况你一般怎么处理”,它分别调用的应该是哪块记忆?如果答不上来,说明设计者对记忆的划分是模糊的。而模糊的划分,到了线上一定会变成混乱的存取逻辑。
还有一个更现实的原因推动我们需要分类:成本。把什么都塞进Prompt,Token消耗会失控;把什么都写进长期存储,召回时会充满噪声。只有先分类,才能对不同记忆做不同的“保鲜策略”——有的要实时更新,有的要定期压缩,有的要永久保留,有的要定时遗忘。分类框架是所有后续架构决策的地基。
2. 工作记忆不等于上下文窗口——别再让Agent“硬背”聊天记录
先聊最容易踩坑的一层:工作记忆。在认知科学里,工作记忆是你在执行当前任务时临时保存信息的能力,容量小、存活短。在Agent系统里,它的工程对应物通常是上下文窗口里那些正在参与本轮推理的对话历史、工具返回结果、中间状态变量。
但很多人直接把上下文窗口当成了唯一的记忆,这是第一个坑。上下文窗口不是记忆,它更像一张可以随时擦写的白板。窗口里的内容虽然被模型“看到”了,但如果超出长度或者开启新会话,它就彻底消失,不会留下任何痕迹。更麻烦的是,窗口里的信息存在严重的位置偏好和注意力稀释问题:长对话塞满之后,真正关键的信息可能淹没在几千句废话里,模型的回答质量会肉眼可见地下降。
所以工作记忆的设计核心不是“尽量多装”,而是“高效筛选”。我常用的做法是把上下文分成三层:
- 系统层:承载角色设定、身份卡片、工具边界、回答风格,这部分基本固定,少改动;
- 会话层:承载最近N轮对话和本轮工具调用结果,N的选择依据模型的窗口大小和任务复杂度来定;
- 临时层:承载当前任务产生的中间结论,比如刚解析出的SQL查询结果,用完即弃。
这里给一个具体的预算参考:假设你用的是8K上下文窗口的模型,我通常这样分配:系统层1.5K,会话层4K,临时层1.5K,保留1K给模型输出。如果模型窗口更大,我会优先扩大会话层而不是系统层,因为系统层内容大多是静态的,重复填充是纯粹的浪费。
光靠“截断最近N轮”还不够,因为有些信息虽然隔了十几轮,但仍然是任务的命脉。比如用户中途随口说了一句“我预算上限是两万”,这可能在后面第20轮才需要用到。滑窗策略会把这句话挤掉,好一点的策略是边对话边做信息抽取,把关键实体、约束条件、用户意图结构化成槽位(比如budget_limit=20000),存放在一个独立的“会话状态”对象里。每次进入下一轮推理时,把槽位和最近的对话历史一起组装进Prompt。
我在实际项目中还踩过一个坑:把工具返回的巨大JSON直接塞回上下文,导致下一轮模型开始“复读”那一大段数据。后来做了个轻量处理,每次工具返回先做摘要或字段裁剪,只保留后续推理真正用到的部分。比如查了一个订单,把整个订单对象三十个字段砍到“状态、金额、预计时间”三个字段,模型表现反而更稳定。工作记忆这块,少即是多。
所以一句话总结这层的设计原则:不要让Agent背书,让它查笔记。工作记忆只保存当前推理循环必要的信息,同时维护一个轻量级的会话状态结构,让关键信息在长对话里存活。
3. 长期记忆分成三种,别全塞进同一个向量数据库
过了工作记忆这道坎,我们讨论大家最熟悉的长期记忆。但我要先说一个反直觉的观点:长期记忆不是一个筐,不是把所有历史文本Embedding之后丢进向量库就完事。严格来说,长期记忆至少分三类,分别对应“世界是什么”“发生过什么”“事情怎么做”,每一类的存储和读取方式应该不一样。
3.1 语义记忆:Agent的“百科词典”
语义记忆处理事实性、概念性知识,比如“二氧化硅是玻璃的原料”“某平台的退货政策是七天无理由”。这一层信息有两个来源:一部分已经藏在模型参数里,也就是预训练时学到的常识;另一部分是私有领域知识,必须靠外部检索来补充,这就是大家熟悉的RAG(检索增强生成)场景。
做RAG时容易犯的毛病是把所有资料都塞进向量库,却忽视了两个细节:第一,语义知识更新频率低,适合做离线索引和定期重建;第二,不是所有内容都适合向量检索,结构化事实(比如价格表、退换货规则)放在关系表里精确查询反而更可靠,向量检索的对象应该是条款、说明、文档这类“非结构化语义”。
3.2 情景记忆:Agent的“项目档案”
情景记忆存放的是和特定用户、特定时间、特定事件绑定的记录:“某用户周一反馈过登录失败”“上次给某客户生成的营销方案用了A风格”。这类记忆最大的特征是带时间线和主体,存储时必须带元信息:谁、何时、发生了什么、结果如何。
我认为这是最适合向量化检索的记忆类型,因为用户不会按时间线回放,而是会以语义的方式唤起,比如“我之前是不是提过一个关于海外市场的想法”。要检索出这条,就得靠Embedding相似度。但光有向量还不够,我一般会同时为情景记忆建一个关系表索引,记录事件类型、参与者ID、创建时间,用来做过滤。先按元信息过滤,再在候选集上做向量相似度排序,召回质量会高很多。
3.3 程序记忆:Agent的“操作手册”
程序记忆代表的是技能和流程:“遇到客户投诉,先安抚、再记录、然后升级给人工”。它不一定要写成自然语言存进向量库,更常见的形态是一组工具调用链、一个决策流程配置、甚至是一段被验证有效的提示模板。
程序记忆的核心问题是复用和更新。我见过不少团队把流程写死在代码里,结果每次调整都要发版。更好的做法是让Agent的执行轨迹沉淀下来,经过人工或模型标注后,把成功的流程固化成可配置的Workflow。举个例子,一个客服Agent干了三个月,发现80%的退款类问题走“引导用户到订单页自助申请”的链路能解决,那这个链路就应该被抽出来,成为程序记忆的一部分,下次遇到相似意图直接调用,而不是每次从头推理。
三类记忆的对比我整理了一下:
| 记忆类型 | 解决什么问题 | 推荐存储形态 | 典型读写方式 | 失效特征 |
|---|---|---|---|---|
| 语义记忆 | 世界是什么样的 | 模型参数 + 外部知识库 | 离线索引定期刷新,在线检索 | 知识过时,需重建索引 |
| 情景记忆 | 发生过什么事 | 关系表 + 向量索引 | 在线写入,语义+元信息检索 | 事件过时,需衰减或清理 |
| 程序记忆 | 事情怎么做 | 工具配置 + 流程定义 | 经验沉淀为规则 | 流程变化后旧流程失效 |
| 工作记忆 | 当前正在做的事 | 上下文窗口 + 会话状态 | 每轮读写,会话结束释放 | 会话结束立即失效 |
一个完整的Agent记忆系统,应该是这张表的组合,而不是单一组件。以客服Agent为例,用户问“你们能不能便宜点”,语义记忆提供“该品牌从不打折”的品牌政策,情景记忆显示“这位用户是会员,上次投诉过价格”,程序记忆触发“先说明会员权益、再提供赠品替代方案”的应对流程。三者配合,回答才会既有事实依据,又有上下文关联,还符合业务规范。
4. “你的名字是?”——自传体记忆才是身份感的根
标题里那句“你的名字是”,看着像一句玩笑,但实际上它触及了Agent记忆里最微妙、也最容易被忽略的类型:自传体记忆。自传体记忆是Agent关于自身的一段稳定知识,包括“我不会熬夜”“北京欢迎你”不是,包括“我的名字叫某某”“我的职责是什么”“我偏好用什么语气说话”“我和某位用户的关系进展如何”。
为什么这层记忆重要?因为信任感的起点,往往是身份一致性。用户可能不会明确说“我要一个有人格连贯性的Agent”,但在长期使用中,如果Agent每次打招呼的语气都不一样,甚至忘了自己叫什么,用户会迅速产生“这是个没有灵魂的工具”的感觉。反过来,一个稳定记住自己名字、记得用户偏好的Agent,哪怕功能有限,也会让用户愿意多聊几句。
工程上,我建议把自传体记忆拆成一个“身份卡片 + 关系档案”的结构。身份卡片是全局限定的,字段包括:
- 名字与角色设定;
- 核心人格标签(简洁、细致、严肃还是幽默);
- 固定行为准则(不允许编造数据、不评价第三方等);
- 对外自我介绍模板。
关系档案则是按用户维度存储的:用户希望被怎么称呼、用户常用的沟通风格、用户历史上的偏好变化、当前的关系阶段(新手/常规/深度信任)。这些字段可以动态更新,但更新时要设置可信度门槛,不能因为用户某一句情绪化的话就立刻改变长期设定。
这里有一个实际操作上的重要经验:身份记忆的写入优先级应该高于情景记忆。比如情景记忆里记了一条“用户喜欢晚上九点后咨询”,可以设一个较低的可信度,因为样本量不足;但“用户姓李,喜欢被称呼李工”这种用户明确表达的信息,应当立刻写进关系档案,并标记为高可信度。我在实现时会给每条自传体记忆打一个confidence分数,低于阈值的信息只进入短期观察区,连续出现多次后才晋升为长期记忆。
同时,“名字”这个锚点还有一个技术用法:当成对话开始的上下文注入项。每次组装Prompt时,把“你是某某,你的用户是谁,你们目前处于什么阶段”放在系统层最前面,而不是藏在长篇历史里。这等于在模型每次推理前都做一次身份唤醒,能够明显减少角色漂移。我自己的经验是,加了这个身份唤醒步骤之后,Agent在长对话中“忘记自己是谁”的问题大幅度下降,虽然这不是玄学,只是把信息放在了显眼的位置。
不过要提醒一下,自传体记忆是一把双刃剑。刻意构造一个过度拟人的身份,让用户误以为Agent有真实情感,在部分场景会带来伦理风险。我的建议是:记忆系统可以承载身份一致性,但产品层面要明确Agent的工具属性,让用户清楚知道对面是程序、不是人。记忆是让工具更顺手,不是让它更会骗人。
5. 落地一套记忆系统,必须想清楚的存取架构
前面把记忆分了类,但这只是纸面功夫。真正落地时,你会发现工作量集中在四个方面:写入、存储、检索、遗忘。这四件事环环相扣,哪一环偷懒,整体效果都会崩。
5.1 写入管线:什么时候记、记什么
很多人第一版的做法是“每一轮对话结束就把全文丢进向量库”,这基本是灾难。全量写入会产生海量低质量记录,稍微跑几天,召回结果就全是废话。我采用的写入策略分两条线:
- 在线写入:从每一轮对话里抽取“值得记”的结构化信息,比如用户明确表达的偏好、约束、事件。抽取可以由模型完成,但一定要给一个输出Schema,比如
{type: "preference" | "fact" | "event", content: "...", entity_id: "..."}这样,避免自由文本泛滥。 - 离线反思:定期(比如每天或每完成一个重要任务)对收集到的原始记录做一次聚合,提炼出更高层的结论。这个步骤我习惯叫它“记忆蒸馏”。比如一周内用户三次提到“不喜欢太长回复”,离线反思可以生成一条“用户偏好简洁回复”的长期记忆,并更新到关系档案。这种高质量记忆,比一百条零散记录有用地多。
在写入口还要做一次去重和冲突检测。同样一条事实,如果新记录和旧记录冲突,旧记录不应该被直接覆盖,而是进入“待确认”状态。等到新信息被多次验证后再替换,避免一次单点情绪导致长期记忆错误。
5.2 存储引擎:一张大表打天下是不行的
依据前面的分类,存储应该是分层的。最少组合是:
- 关系型数据库:存用户档案、身份卡片、事件元数据,支持精确查询,适合“谁、何时、何种类型”的过滤;
- 向量索引:存情景记忆和高层反思的向量化内容,支持语义相似检索;
- 缓存/会话状态:存当前会话槽位,负责工作记忆的快速读写。
这里有个容易被忽视的点:不同层之间的同步。正确做法是,写入时先写关系表和向量库,并保持两边的主键一致;检索时先走关系表过滤,再走向量召回;如果两边数据不一致,以关系表的内容为准做重建。一致性这点,建议用异步队列解耦,写操作立即返回,后台稍后再刷向量索引,避免让用户等待一次向量计算。
5.3 检索策略:混合召回 + 重排 + 时效衰减
检索质量直接决定“记住”有没有用。只用向量相似度是最常见的错误,因为用户的问题往往是混合信息需求:“用户希望我帮他记着上次的适配方案”这种表述里,有语义需求,也有明确的实体需求。我推荐检索链路拆成三步:
- 候选召回:向量相似度召回Top50,同时用关键词/布尔检索召回Top20,合并去重;
- 元信息过滤:根据当前会话里的用户ID、任务类型、时间范围,把候选集缩小到与当前场景真正相关的部分;
- 重排序:用一个轻量排序模型或规则,把时效性、来源可信度、与当前问题的相关度综合加权,取Top5进Prompt。
时效衰减是很多人忽略的部分。三个月前和昨天的记忆,对当前决策的参考价值肯定不一样。我会在重排阶段给每条记忆计算一个时间衰减因子,比如score = similarity * exp(-alpha * days),让旧记忆在权重上自然落后,但也不会彻底消失。如果用户主动提“当时是怎么说的”,那就取消衰减,按语义相似度正常检索。
5.4 遗忘调度:没有删除机制的记忆系统是垃圾场
最后一块是遗忘。我说句可能不太好听的话:一个没有遗忘机制的Agent记忆系统,跑三个月就会变成噪声重灾区。遗忘不是bug,是让记忆保持高信噪比的手段。
遗忘策略一般有三类:
- 显式删除:用户要求“忘掉我刚才说的那句话”,系统必须能定位并删除,这不仅是产品需求,也是隐私底线;
- 过期清理:带TTL的临时记忆,比如“用户这周出差,希望下周提醒”,到期后自动降级;
- 重要度淘汰:定期扫描长期记忆,把长期未被召回、可信度低、被冲突信息替代的条目归档或删除。
我个人的经验是,给每条记忆加一个last_access_time字段,定期把超过一段时间没有被召回的记录移入冷存储。这样可以显著降低在线检索的噪声,而且真到需要的时候还能翻出来。这个操作也解决了另一个问题:向量库无限膨胀导致的检索性能下降,冷热分离是最直接的解药。
6. 有三类记忆,我会刻意让Agent“忘记”
最后分享几个我在真实项目里总结的“遗忘清单”。如果说前面讲的是记忆系统的能力上限,那这一节讲的是它的边界和克制。
第一类要忘记的,是情绪高峰期的极端表达。用户一气之下说“你们产品就是个垃圾”,如果系统把这句话当作“用户对产品有严重不满”的长期事实写进档案,后续所有与这位用户的交互都会自带偏见。正确的做法是:识别为情绪表达,只作为短期事件记录,不进入长期偏好。我踩过这个坑,后来看到某位用户第二天正常使用产品时,系统还在那里“小心翼翼地道歉”,场面非常尴尬。
第二类要忘记的,是已经失效的上下文信息。比如用户上个月说“我还在评估阶段”,但今天已经下单了,那“评估阶段”这条记忆应该被新事实覆盖,而不是两条共存互掐。失效信息不光占空间,还会在权重计算中污染决策。我习惯的做法是给关键状态字段设置互斥组,比如purchase_status同一时刻只能有一个值,新状态写入时自动存档旧值,但不参与在线检索。
第三类要忘记的,是低质量、无法验证的碎片信息。用户随口说了一句“可能吧,我再看看”,把它记成一条记忆纯粹是给检索器添乱。这类信息我通常直接丢弃。判断标准是:这条信息是否具备可行动性、可验证性、或长期稳定性?三者一个都不沾,就不值得占一个存储槽位。
说到底,记忆系统的价值不在于“记住得多”,而在于“想起的时候刚好是需要的”。我打磨这个模块时最大的体会是,好的记忆设计永远是三个方面共同作用的结果:用对分类架构,保持写入克制,加上合理的遗忘机制。至于那句“你的名字是”,它其实是整件事的缩影——一个真正记得住自己的Agent,背后一定有一张写得清清楚楚的身份卡片;而一个让用户信赖的Agent,也一定知道什么该牢记、什么该释怀。