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

作者：Hex4C59 · 发布于 2026-03-28

原文：https://hex4c59.cc/agent/memory-types-and-design/


## 一个问题先于设计

你让 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」问题——模型对上下文中间位置的信息注意力显著下降。因此，短期记忆需要主动管理：哪些内容应该保留、哪些可以压缩摘要、哪些应该外化存储到其他记忆层。

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

**工程上需要关注的点：**

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

```python
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 检索产品文档，用语义记忆保存用户偏好，用情景记忆记录历史交互。

**工程上需要关注的点：**

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

```python
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 记忆系统的基本结构，展示四种记忆如何在一次任务执行中协作：

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

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

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

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

---

## 与已有文章的关系

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

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

[什么是 RAG：让大模型用上你自己的知识库](https://hex4c59.cc/agent/what-is-rag/) 则深入了向量检索的实现细节，本文提到的「语义记忆」在实现层面与 RAG 高度重叠，可以结合来看。

---

## 总结

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

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

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

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

---

*上一篇：[什么是 RAG：让大模型用上你自己的知识库](https://hex4c59.cc/agent/what-is-rag/)*

