---
title: 记忆与状态
url: https://doc.liz6.com/ai/02-Agent/03-memory-and-state
locale: zh
area: ai
tags:
- ai
- Agent
date: 2026-06-30
modified: 2026-07-16
description: API 是无状态的,agent 的"记忆"全是工程出来的。三层记忆——会话内重发历史、compaction/context editing 管理窗口、memory tool/stores 跨会话持久——各用不同机制,解决不同问题。
---

# 记忆与状态

> API 是无状态的,agent 的"记忆"全是工程出来的。三层记忆——会话内重发历史、compaction/context editing 管理窗口、memory tool/stores 跨会话持久——各用不同机制,解决不同问题。

## 概述

API 是**无状态**的(见 [上下文工程](/ai/01-models-and-context/01-context-engineering.md)):模型一轮里"知道"的全在这次请求里,它不会自动记得上次。所以 agent 的"记忆"**全是工程出来的**——是调用方维护、按生命周期分层的一套机制。按"活多久"分三层,是想清楚这件事最好的框架:

```mermaid
flowchart TD
    A["① 会话内<br/>重发完整历史 (协议自带)"] --> B["② 临近窗口上限<br/>compaction 摘要 / context editing 裁剪"]
    B --> C["③ 跨会话持久<br/>memory tool / memory stores (落到会话之外)"]
```

第一层"这轮记得",第二层"对话太长不溢出",第三层"关掉再开还记得"。三层用不同机制、解决不同问题,长 agent 常三层并用。理解它们的边界,才不会用错工具(比如想跨会话记住偏好却只靠重发历史)。

## 第一层:会话内 —— 重发历史的代价

模型记得上一轮,只是因为**整段历史又被发了一遍**。这层"免费"(协议自带),但有两个代价:

- **成本线性增长**:每轮重发全部历史,input token 随轮次累加。
- **终会溢出**:历史无限长,迟早逼近窗口上限(`stop_reason: "model_context_window_exceeded"`)。

`prompt caching` 能让重发的稳定前缀按 ~0.1x 计费(见 [上下文工程](/ai/01-models-and-context/01-context-engineering.md)),缓解成本;但溢出问题要靠第二层。

## 第二层:临近窗口的管理 —— 压缩 vs 裁剪

两个方向相反的 API 原语:

| | compaction(压缩) | context editing(裁剪) |
|---|---|---|
| 做什么 | 把早期历史**摘要**成 compaction block | 把旧 `tool_result` / thinking **清除** |
| 信息 | 概括保留 | 直接删 |
| Beta | `compact-2026-01-12` | `context-management-2025-06-27` |
| 关键坑 | 必须把 `response.content`(含 compaction block)**整块回传**,只回传 text 会丢状态 | 只删块,不改对话结构 |

记法:compaction 是"概括",context editing 是"删除"。二者都在**单次会话内**腾窗口,**不解决跨会话**——关掉进程它们就没了。

## 第三层:跨会话持久记忆

要"关掉再开还记得",得把要点**写到会话之外**。两条路径,按自管 vs 托管来选。

### 路径 A:memory tool(后端自管)

声明工具 `{"type": "memory_20250818", "name": "memory"}`,模型就获得一个 `/memories` 目录的读写能力,**六个命令**:

| 命令 | 语义 |
|------|------|
| `view` | 读文件 / 列目录 |
| `create` | 新建/覆盖文件 |
| `str_replace` | 替换文件中一处字符串 |
| `insert` | 在指定行后插入 |
| `delete` | 删除文件 |
| `rename` | 重命名/移动 |

**存储后端自行实现**(本地 FS、对象存储、DB)——模型只发命令,由实现方执行并回 `tool_result`。Python/TypeScript SDK 提供脚手架(`BetaAbstractMemoryTool` / `betaMemoryTool` + handler),只需填 `view/create/str_replace/...` 的存储逻辑。模型把"用户偏好""项目约定""踩过的坑"写进文件,下次会话先 `view` 读回来。

> 这套思路本会话现在就在用:持久记忆就落在 `~/.claude/projects/.../memory/` 下,**一条事实一文件 + 一个 `MEMORY.md` 索引**——`MEMORY.md` 常驻、每条事实按需读。这正是 memory tool 模式的落地(本轮就更新了"本站点构建方式"那条)。

### 路径 B:Managed Agents memory stores(托管)

工作区级持久记忆库,对象模型三层:

<svg viewBox="0 0 720 230" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Managed Agents memory store 三层对象模型:memory_store 挂 memory 挂 memory_version">
  <rect width="720" height="230" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">memory store 三层对象模型</text>
  <rect x="40" y="52" width="360" height="46" rx="8" fill="#4f46e5"/>
  <text x="58" y="72" font-size="13" font-weight="700" fill="#ffffff">memory_store (memstore_…)</text>
  <text x="58" y="89" font-size="11" fill="#e0e7ff">工作区级集合,挂进 session</text>
  <path d="M70,98 V112 H100" stroke="#94a3b8" stroke-width="1.6" fill="none"/>
  <rect x="100" y="112" width="380" height="46" rx="8" fill="#0d9488"/>
  <text x="118" y="132" font-size="13" font-weight="700" fill="#ffffff">memory (mem_…)</text>
  <text x="118" y="149" font-size="11" fill="#ccfbf1">一个文本文件,按 path 寻址,≤ 100KB</text>
  <path d="M130,158 V172 H160" stroke="#94a3b8" stroke-width="1.6" fill="none"/>
  <rect x="160" y="172" width="480" height="46" rx="8" fill="#e2e8f0"/>
  <text x="178" y="192" font-size="13" font-weight="700" fill="#334155">memory_version (memver_…)</text>
  <text x="178" y="209" font-size="11" fill="#475569">每次改动一个不可变快照(created / modified / deleted)</text>
</svg>

机制要点:

- **挂载为文件系统**:store 以 FUSE 挂到容器 `/mnt/memory/<store>/`,agent 用普通文件工具读写;系统提示自动注入一段说明,告诉它挂载存在。
- **访问控制**:`access: "read_only" | "read_write"`,在文件系统层强制。
- **乐观并发**:`memories.update` 支持 `precondition: {type:"content_sha256", ...}`,不匹配返回 409——读-改-写不会互相覆盖。
- **审计与回滚**:每次改动产生 `memory_version`,可列、可取、可 **redact**(抹内容但留 actor+时间戳,用于泄密/PII 清除)。
- 每 session 最多挂 8 个 store(可按"共享只读参考 + 每用户读写"分层)。

## 记忆 vs 上下文:用哪个?

新手常把"长上下文"当"记忆"。区分:

- **上下文(context)**:这一次请求里塞的东西,一轮结束即逝(除非重发)。"窗口"。
- **记忆(memory)**:跨请求/跨会话存活的东西,写在会话之外。"硬盘"。

需要"这轮看见"用上下文(RAG 把片段放进窗口);需要"以后还记得"用记忆(写文件)。compaction/context editing 是上下文层的管理,不是记忆。

## 设计与安全

好的记忆设计(也是本会话记忆的写法):

- **一条经验一文件**,顶部一行摘要,便于召回判断相关性。
- **只存非显然的**:代码/历史里已有的别存(否则是噪声),存"为什么""坑在哪"。
- **写时更新、错时删除**:更新已有条目而非堆重复;过时的删掉。

安全红线:

- **路径必须校验**:模型给的 `path` 是不可信输出。解析到规范路径(`realpath`/`Path.resolve()`),确认仍在记忆根目录内,**拒绝 `..`、符号链接、绝对越界、URL 编码的遍历(`%2e%2e%2f`)**。别直接 `open()` 原始 path。
- **绝不存 secret**:API key、密码、token 不进记忆。PII 谨慎(GDPR/CCPA);memory stores 的 `redact` 是事后补救手段。
- **多租户隔离**:每用户独立记忆目录 + 鉴权;参考实现无内建访问控制。

## 最佳实践

- **按"活多久"分三层用对工具。** 会话内重发历史、临窗口用 compaction/context editing、跨会话用 memory tool/stores——别用重发历史去"记住偏好"。
- **一条经验一文件,顶部一行摘要。** 便于召回时判断相关性;一个 `MEMORY.md` 索引常驻、每条按需读(本会话就这么做)。
- **只存非显然的。** 代码/历史里已有的别存(是噪声),存"为什么""坑在哪""定下的约定"。
- **写时更新、错时删除。** 更新已有条目而非堆重复,过时的删掉。
- **路径先规范化再校验边界。** 模型给的 `path` 不可信,`resolve()` + 确认在记忆根内,拒绝 `..`/符号链接/URL 编码遍历(见 [安全与防护](/ai/03-applications-and-production/04-security-and-protection.md))。
- **secret 绝不进记忆,多租户按用户隔离。** 参考实现无内建访问控制;PII 谨慎,`redact` 只是事后补救。

## 权衡与失败模式

- **什么都往记忆塞**:变噪声、召回受干扰 → 只存高价值非显然项。
- **把记忆当事务数据库**:它是给模型读的笔记 → 并发写用 `content_sha256` 乐观锁(memory stores)或自管版本。
- **跨会话串户**:多租户不隔离 → 按用户分目录 + 鉴权。
- **路径未校验**:目录遍历读写任意文件 → 规范化 + 边界检查。

## 参考

- **Anthropic 官方文档**: Memory Tool、Managed Agents Memory Stores(platform.claude.com)

*Keywords: stateless, 会话历史, prompt caching, compaction, context editing, model_context_window_exceeded, persistent memory, memory tool, memory_20250818, /memories, view/create/str_replace/insert/delete/rename, BetaAbstractMemoryTool, memory stores, memstore, FUSE mount, access, content_sha256, precondition, redact, memory version, path traversal, secret, PII, 多租户隔离*
