记忆的四种形态:Agent 如何在不同时间尺度上保住它知道的事

从「Agent 为什么会忘事」出发,系统梳理短期、情景、语义与程序四种记忆的定义、适用场景与工程实现,并厘清与 RAG、上下文管理的边界。

Hex4C59 11 分钟阅读 更新于 2026-10-17

一个问题先于设计 #

你让 Agent 帮你管理一个项目,它今天记住了你的风格偏好,明天就忘了。你跑了一个两小时的长任务,中途它开始重复之前做过的操作,好像对自己做了什么没有印象。你告诉它你是开发者,喜欢简洁的回答,下一个会话它又回到了冗长的解释模式。

这些都是记忆问题,但它们不是同一种记忆问题。

本文面向会写简单代码、还没有设计过记忆系统的读者。这里的“记忆”是程序保存并在需要时提供给模型的信息,不是模型一直在后台记住所有对话。

设计时先问:信息只供当前一步使用,还是以后要回查?它记录的是一次事件、一个事实,还是一套做事方法?本文用四类记忆分别回答这些问题,避免把所有“忘事”都归因于上下文不够长。


先给结论 #

  1. 「忘事」不全是 context 不够长:至少区分当前会话、跨会话事件、长期知识与稳定行为四类需求,否则会在错误层级上打补丁。
  2. 四类记忆对应不同保留期限与读取方式:本轮要用的信息放进模型输入(in-context);“发生过什么”进情景记忆;通用事实进语义记忆;“怎么做”进程序记忆,例如提示词、代码或按需加载的 Skill 操作说明。
  3. 语义记忆与 RAG 可以共用检索实现:语义记忆描述保存什么,RAG(Retrieval-Augmented Generation,检索增强生成)描述先查资料、再把资料交给模型回答的流程。两者可以配合使用。
  4. 修改共享的程序记忆要检查影响范围:稳定、普适的行为可以写进公共规则;某个用户的偏好应按用户保存,并在该用户的任务中提供给模型。
  5. 记忆质量 = 检索质量 + 维护策略:只写不读、只增不减、全盘向量化或全盘塞 context,都是典型失败路径。

为什么记忆的分层是必要的 #

人类记忆有一套成熟的分类框架:工作记忆处理当前正在操作的信息,情景记忆存储具体经历,语义记忆保存通用知识,程序记忆负责技能和习惯。这套框架不是偶然的,它对应的是不同类型的信息在时间维度上的衰减特征和调用方式。

Agent 面临同样的问题。一个 Agent 在执行任务时需要知道:

  • 现在:当前步骤是什么,刚才做了什么,下一步打算做什么
  • 这次:这个任务是怎么来的,用户说过什么约束,中间出过哪些错
  • 一直以来:用户是谁,有什么偏好,这个领域有哪些背景知识
  • 技能层面:遇到某类问题应该怎么处理,有哪些固定的操作模式

这四类信息可以共用数据库,但不能省略各自的读取和更新规则。例如,当前步骤应直接传给模型;一次历史失败只在相关任务中取回;用户偏好则要区分属于哪个用户、是否仍然有效。


四种记忆形态 #

1. 短期记忆(In-context Memory) #

定义: 存在于当前上下文窗口中的所有信息,包括对话历史、工具调用记录、模型的推理过程。

生命周期: 当前会话,上下文窗口满了之后旧内容会被截断或压缩。

存储位置: 模型的 context window,没有外部持久化。

典型内容:

  • 用户本轮说了什么
  • Agent 已经调用了哪些工具,返回了什么
  • 当前任务的目标和进度
  • 最近几步的推理轨迹

短期记忆是所有记忆形态中最直接、延迟最低的,但也是最脆弱的。它的容量有硬上限(以 token 计),而且没有任何跨会话的持久性。

工程上需要关注的点:

上下文窗口不是越长越好。一个塞满了无关历史的上下文会让模型在长尾信息中迷失,这就是「lost in the middle」问题——模型对上下文中间位置的信息注意力显著下降。因此,短期记忆需要主动管理:哪些内容应该保留、哪些可以压缩摘要、哪些应该外化存储到其他记忆层。

def compress_history(messages: list, threshold: int = 8000) -> list:
    """在对话过长时压缩消息:保留系统提示与最近若干轮,更早内容改为摘要。

    参数:
        messages: 对话消息列表,通常首条为 system。
        threshold: token 数量阈值,低于则原样返回。

    返回:
        压缩后的消息列表。
    """
    if token_count(messages) < threshold:
        return messages

    recent = messages[-6:]
    older = messages[1:-6]

    summary = summarize(older)
    return [messages[0], {"role": "system", "content": f"[对话摘要] {summary}"}] + recent

2. 情景记忆(Episodic Memory) #

定义: 跨会话持久保存的、与特定事件或交互绑定的记录。不是通用知识,而是「曾经发生过什么」。

生命周期: 跨会话,按需检索,可以设置 TTL(Time to Live,保存期限)或容量上限;达到期限或上限后,按规则清理旧记录。

存储位置: 外部数据库,通常是向量数据库或结构化存储(如 SQLite、PostgreSQL)。

典型内容:

  • 用户上次反馈说「回答太长了」
  • 上周执行某个任务时遇到了什么错误、怎么解决的
  • 某个代码库的结构是上次对话时分析过的
  • 历史操作的时间线记录

情景记忆让 Agent 在处理类似问题时,可以查阅上次的过程、结果和失败原因。这里的“从经验中学习”是取回旧记录作为本轮参考,不表示模型参数自动更新,也不保证旧方案适用于新任务。

工程上需要关注的点:

情景记忆的检索方式至关重要。最简单的实现是向量相似度搜索:把历史交互嵌入为向量,查询时找最相关的几条。但纯向量检索在时间敏感的场景下表现不佳——「最近发生的事」和「最相关的事」是两个不同的维度,检索策略需要同时考虑两者。

def extract_episodes(conversation: list) -> list:
    """从一轮对话中提取值得写入情景记忆的关键事件(示意:实际应对接 LLM)。

    参数:
        conversation: 完整对话消息列表。

    返回:
        事件对象列表,元素含 summary、category、importance 等字段。
    """
    prompt = f"""
    从以下对话中提取值得记录的关键事件。
    关注:用户偏好、任务结果、出现的问题、用户的明确反馈。
    以 JSON 列表返回,每条包含 summary、category、importance(1-5)。

    对话内容:{conversation}
    """
    return parse_json(llm_call(prompt))


def retrieve_episodes(query: str, top_k: int = 5) -> list:
    """检索情景记忆:语义相似度与时间衰减混合排序(示意)。

    参数:
        query: 当前查询文本。
        top_k: 返回条数上限。

    返回:
        排序后的相关事件列表。
    """
    semantic_results = vector_search(query, top_k * 3)
    scored = [
        (ep, similarity + recency_bonus(ep.timestamp))
        for ep, similarity in semantic_results
    ]
    return sorted(scored, key=lambda x: x[1], reverse=True)[:top_k]

3. 语义记忆(Semantic Memory) #

定义: 与具体事件无关的、通用性的知识和事实,包括关于用户、领域、世界的背景信息。

生命周期: 长期持久,可以随时间更新,但不会因为单次交互就发生根本变化。

存储位置: 向量数据库(用于非结构化知识)、结构化数据库或键值存储(用于结构化属性)。

典型内容:

  • 用户的职业背景、技术水平、偏好设置(「喜欢用 Python 3.10+,讨厌冗余注释」)
  • 企业内部的产品知识、术语表、流程文档
  • 领域知识库(医疗、法律、金融等专业背景)
  • Agent 自身的能力边界描述

语义记忆描述要保存的事实,例如用户偏好和项目配置;RAG 描述如何检索资料并用它辅助回答。下面比较的是“从交互中维护用户信息”和“检索预先索引文档”这两种常见用法,不是说 RAG 只能查静态文档。

语义记忆 vs RAG 的边界:

维度语义记忆传统 RAG
内容来源交互中积累预先索引的文档
更新频率动态,随时间演化静态或定期批量更新
检索粒度用户/任务粒度文档/段落粒度
典型用途用户建模、偏好管理知识问答、文档检索

在实践中,两者经常共存:Agent 用 RAG 检索产品文档,用语义记忆保存用户偏好,用情景记忆记录历史交互。

工程上需要关注的点:

语义记忆最核心的工程挑战是更新策略。用户偏好会变,领域知识会迭代,如果语义记忆只增不改,很快会出现矛盾或过时的条目。需要在写入时做去重、在读取时做新旧冲突解决,或者为每个条目附加置信度和最后更新时间。

def update_user_memory(user_id: str, new_observation: str) -> None:
    """将新观察合并进用户语义记忆:矛盾时以新为准,可补充则合并(示意)。

    参数:
        user_id: 用户标识。
        new_observation: 本轮交互得到的新信息描述。
    """
    existing = load_user_memory(user_id)

    prompt = f"""
    现有用户信息:{existing}
    新观察到的信息:{new_observation}

    请更新用户信息。如果新信息与旧信息矛盾,以新信息为准。
    如果新信息补充了旧信息,合并两者。
    以 JSON 返回更新后的用户画像。
    """
    updated = parse_json(llm_call(prompt))
    save_user_memory(user_id, updated)

4. 程序记忆(Procedural Memory) #

定义: 关于「如何做事」的知识,表现为固化的行为模式、操作规范和处理流程。

生命周期: 最稳定的记忆形态,通常不随个别交互改变,而是通过离线学习或人工维护来演化。

存储位置: 系统提示词(system prompt)、代码逻辑、工具定义、Fine-tuning 的模型权重。

典型内容:

  • Agent 的角色定义和行为准则(「你是一个代码审查助手,发现 bug 时应该……」)
  • 标准操作流程(SOP):「遇到数据库错误,先检查连接,再检查权限,再检查 SQL」
  • 工具的使用规范和边界
  • 特定任务类型的处理模板

如果操作规则已写进代码或本轮 system prompt(系统指令),执行时就不必再检索它。若规则保存在外部 Skill 中,则仍要按任务查找、读取并提供给模型;“程序记忆”这个名称本身不决定读取方式。

工程上需要关注的点:

程序记忆的修改方式影响成本和范围:修改共享 system prompt 可能影响所有使用它的用户;通过 fine-tuning(微调)改变模型行为,则需要训练。应先分清共享规则与用户专属信息,再决定改哪里,不能把某个用户的要求直接写进公共规则。

Skill 可以把操作规范整理成按需读取的说明文件,再在相关任务中提供给模型。它适合承载程序记忆中的做事方法,但具体加载位置和方式取决于宿主,不一定都放进 system prompt。


四种记忆的对比 #

记忆类型时间跨度存储位置读取方式更新频率典型失败
短期记忆当前会话Context window直接可见每次交互截断、lost in the middle
情景记忆跨会话向量/关系数据库语义检索会话结束时检索不准、相关性低
语义记忆长期向量/KV 存储语义检索知识演化时过时、矛盾
程序记忆长期,随规则维护System prompt / 权重 / 外部 Skill已在输入中可直接使用,外部说明按需读取规则变更时过度泛化、僵化

常见的设计错误 #

把所有记忆都塞进 context window。 当前上下文有容量上限,单靠追加消息无法可靠地保存长任务和跨会话信息。应把需要以后回查的内容保存在外部,再按需取回。

把所有记忆都交给向量数据库。 向量检索有延迟,有精度损耗,对于当前任务中正在使用的信息(短期记忆),应该直接在上下文中保留,而不是每次检索。

记忆只写不读。 有些 Agent 实现了情景记忆的写入逻辑,但从来没有在任务开始时检索相关历史,存进去的记忆是死的。

没有记忆清理机制。 情景记忆和语义记忆如果只增不减,条目会越来越多,检索质量下降,矛盾信息累积。需要设计 TTL(生存时间)、重要性评分、去重合并等维护机制。

程序记忆个性化。 把用户 A 的特殊要求写进所有用户共用的 system prompt,会让其他用户也受到影响。个性化信息应该进语义记忆,由运行时动态注入。


一个实际的记忆架构 #

下面是一个中等复杂度的 Agent 记忆系统的基本结构,展示四种记忆如何在一次任务执行中协作:

任务开始时:
  1. 从语义记忆中检索用户偏好和相关背景知识
  2. 从情景记忆中检索历史上相关的任务经历
  3. 将检索结果注入 context,构成「记忆前缀」
  4. 程序记忆(system prompt)已经内嵌在 Agent 定义里

任务执行中:
  5. 所有当前步骤的推理和工具调用存在短期记忆(context)里
  6. 重要的中间状态可以写入外部 KV 存储,用于任务恢复

任务结束后:
  7. 从对话中提取关键事件,写入情景记忆
  8. 如果发现新的用户偏好或领域知识,更新语义记忆
  9. 如果发现某类问题的处理方式值得固化,考虑更新程序记忆(成本最高)

这个流程里有一个隐含的取舍:检索和注入记忆会消耗 token,增加延迟。不是每个任务都值得走完整的记忆流程。简单的单轮问答可能不需要回查历史;一次性任务若没有后续复用需求,可以不保存事件记录;匿名用户也未必需要长期偏好档案。这里判断的是用途,而不是要求每次都装齐四层。记忆系统的复杂度应该与任务的复杂度和用户关系的长度匹配。


与已有文章的关系 #

这篇文章的关注点是记忆的分类与选型:什么信息该放在哪种记忆里,为什么。

上下文与记忆:Agent 为什么会在长任务中失去方向,以及如何设计状态管理 关注的是另一个问题:在长任务执行中,为什么 Agent 会出现目标漂移和重复操作,以及如何通过状态管理来解决。两篇是互补关系,一个讲「记忆的结构」,一个讲「记忆在长任务里的失效与修复」。

什么是 RAG:让大模型用上你自己的知识库 则深入了向量检索的实现细节,本文提到的「语义记忆」在实现层面与 RAG 高度重叠,可以结合来看。


总结 #

记忆是 Agent 区别于普通聊天机器人的核心能力之一,但「记忆」不是一个单一的东西。把四种记忆形态混淆,或者只用一种机制来处理所有记忆需求,是大多数 Agent 记忆设计失败的根本原因。

设计记忆系统的时候,可以从这几个问题出发:

  • 这条信息在当前任务结束后还有用吗?→ 有,考虑持久化;没有,放 context 就够了
  • 这条信息是关于特定事件的,还是通用知识?→ 前者进情景记忆,后者进语义记忆
  • 这个行为模式是稳定的、普适的吗?→ 是,考虑程序记忆;否,用情景或语义记忆动态调整
  • 我的检索能找回需要的信息吗?→ 既要查得到,也要检查记录是否准确、过时或与新信息冲突

记忆设计没有银弹,但有了清晰的分层框架,至少不会在错误的地方解决问题。


上一篇:什么是 RAG:让大模型用上你自己的知识库

一起讨论

选中正文中的文字,可以针对这段内容发起讨论。

正在读取讨论…

在 GitHub 查看与管理讨论 ↗

文章讨论