Lazy loaded image
Agent丨什么是 Memory?短期记忆在对话轮次增加时如何持续处理上下文爆炸和重复压缩?
字数 6129阅读时长 16 分钟
2026-5-22
type
Post
status
Published
date
May 22, 2026
slug
agent47
summary
tags
Agent
category
icon
password

0. 先给结论

在 Agent / RAG / LLM 应用里,Memory 不是“把历史消息全部塞回 Prompt”,而是一套“选择、压缩、索引、恢复、校验上下文”的工程系统。
短期记忆的核心问题不是“能不能记住”,而是:
随着对话轮次增加,如何让模型每一轮都看到足够有用的信息,同时不被历史噪声、工具输出、重复摘要和过期信息拖垮。
成熟系统的共同做法可以概括为五层:
  1. Recent Window:保留最近若干轮原文,保证连续性。
  1. Running Summary:把更早的历史压缩成结构化摘要。
  1. Tool Output Pruning:对旧工具输出做裁剪、占位或摘要。
  1. Persistent Memory / Project Memory:把稳定事实、偏好、项目规则沉淀到长期记忆。
  1. Retrieval / Rehydration:需要时再从原始日志、向量库、文件、任务记录中恢复细节。
Claude Code 文档明确指出,context window 会包含对话、读过的文件、命令输出等内容,随着上下文填满,模型可能开始遗忘早期指令或犯更多错误;因此上下文窗口是最重要的资源之一。(Claude Code) LangGraph / LangChain 也把短期记忆定义为 thread-scoped memory,并给出 trim、delete、summarize 等常见处理方式。(LangChain 文档)

1. Memory 到底是什么?

在 Agent 系统里,Memory 可以分成三类:
类型
作用
生命周期
典型内容
短期记忆 Short-term Memory
保持当前会话连贯性
单个 thread / session 内
最近对话、当前任务、工具调用结果、临时计划
长期记忆 Long-term Memory
跨会话复用经验
多 session / 多 thread
用户偏好、项目规则、稳定事实、历史问题解决方案
工作记忆 Working Memory
当前推理步骤的临时状态
一次 Agent loop 内
TODO、当前文件、下一步动作、假设、约束
LangGraph 文档中,短期记忆是 conversation thread 范围内的 state,通常由 message history 和其他状态组成;长期记忆则跨线程保存,可按 namespace 召回。(LangChain 文档) LlamaIndex 的 Memory 模块也把短期 chat history 和长期 memory block 组合起来,并通过 token_limit、chat_history_token_ratio、token_flush_size 控制短期历史何时 flush 到长期记忆。(Developer Documentation)
所以,一个生产级 Agent 的 Memory 更像数据库、缓存、日志系统和摘要系统的组合,而不是一个简单数组。

2. 为什么轮次增加后会出现“上下文爆炸”?

大多数 Chat API 本质上是无状态的。每一轮调用模型时,都要重新构造一次 Prompt:
轮次越多,历史消息越长;Agent 越能干,工具输出越多;RAG 越复杂,检索片段越多。最后导致三个问题:
第一,成本和延迟上升。每一轮都要重复发送更大的上下文。
第二,有效注意力下降。即使模型支持长上下文,也可能被过期、重复、无关内容干扰。LangGraph 文档特别提到,长历史即使能塞进上下文,也会带来性能下降、响应变慢和成本升高。(LangChain 文档)
第三,compaction 本身也需要空间。如果等到上下文已经完全爆掉才压缩,压缩调用本身也可能因为输入太长而失败。OpenCode 的配置文档因此提供了 compaction.reserved,用来为 compaction 保留 token buffer,避免压缩时越界。(OpenCode)

3. 成熟系统如何处理 Memory 与上下文压缩?

3.1 Claude Code:项目记忆 + Auto Memory + /compact

Claude Code 有两套互补记忆机制:CLAUDE.md 和 auto memory。前者由用户维护,适合项目规则、架构、构建命令、团队规范;后者由 Claude 根据用户纠正和偏好自动记录。两者都会在每个会话开始时加载进上下文。(Claude Code)
Claude Code 的 context window 会包含启动时加载的 CLAUDE.md、auto memory、MCP 工具名、skill 描述,以及后续文件读取、命令输出、hook 结果等。文档还说明,subagent 可以在独立 context window 中处理研究任务,主会话只接收摘要和少量元数据,从而避免大文件读取污染主上下文。(Claude Code)
更关键的是,Claude Code 的 /compact 会把当前 conversation history 替换成结构化摘要。compaction 后,project-root CLAUDE.md、unscoped rules、auto memory 会从磁盘重新注入;path-scoped rules 和 nested CLAUDE.md 则要等再次读取匹配文件时才重新加载。(Claude Code)
这个设计给我们的启发是:
不要把所有信息都当成“历史消息”处理。稳定规则应该外置并可重新注入;会话过程才需要摘要;工具输出和临时观察应该可裁剪。

3.2 OpenCode:auto compaction + prune + reserved token

OpenCode 官方 README 提到,它有 auto compact 功能,会在接近模型上下文窗口限制时自动总结 conversation,并用 summary 创建新 session 继续工作。(GitHub) 最新配置文档中,compaction 包含 autoprunereserved 三个关键选项:自动压缩、移除旧工具输出、为压缩预留 token buffer。(OpenCode)
OpenCode 还把 compaction 做成一个隐藏系统 agent:官方 agents 文档中 compaction 是 primary mode 的 hidden system agent,会在需要时自动运行,用户不能在 UI 中直接选择。(OpenCode)
从 OpenCode 当前 dev 分支源码可以看到,它的 compaction 不是简单“把全量历史丢给模型总结”。源码中包含 PRUNE_MINIMUMPRUNE_PROTECT、工具输出最大字符数、recent tail 保留预算、结构化 summary template、previous summary 更新逻辑、tail_start_id 等机制;overflow 判断里也会结合模型 context limit、reserved buffer、输入/输出/cache token 来判断是否触发压缩。(GitHub)
抽象成伪代码,大致是:
OpenCode 的关键工程思想是:先轻量 prune,再重型 compact;压缩时保留最近 tail;摘要不是自由发挥,而是结构化的 task handoff。

3.3 LangGraph / LangMem:把短期记忆作为 state 管理

LangGraph / LangChain 的短期记忆更适合我们抽象通用架构。文档建议,当短期记忆过长时,可以 trim messages、delete messages、summarize messages,或者使用自定义策略。(LangChain 文档)
LangMem 的 SummarizationNode 更进一步:当消息超过 token limit 时,会从旧到新处理消息,把达到阈值的历史总结为 summary message,然后输出 [summary_message] + remaining_messages。它还支持 max_tokens_before_summarymax_summary_tokensinitial_summary_promptexisting_summary_prompt 等参数。(LangChain)
这说明短期记忆不一定要绑定在聊天框里,它可以是 Agent graph state 的一个字段:
真正送入模型的是 state 的“投影结果”,而不是 state 的全部原始内容。

3.4 LlamaIndex:短期记忆满了就 flush 到长期 Memory Blocks

LlamaIndex 的 Memory 设计提供了一个很好的参数化范式:token_limit 控制总记忆容量,chat_history_token_ratio 控制短期 chat history 占比,超过比例后把最旧消息 flush 到长期 memory;token_flush_size 控制每次 flush 的 token 数。(Developer Documentation)
它的长期记忆用 Memory Block 表达,例如 static block、fact extraction block、vector memory block。被 flush 的短期消息可以被长期 block 进一步处理,之后再与短期记忆合并进上下文。(Developer Documentation)
这给 RAG / Agent 系统的启发是:
短期记忆不应该只有“保留或删除”两个选项。更好的方式是:旧消息从短期窗口退出,但进入长期索引、事实表、任务日志或向量库。

3.5 OpenClaw / OpenViking / TencentDB Agent Memory:层级化与按需加载

OpenClaw 相关 issue 中有人提出,传统 memory-lancedb 插件类似 flat vector search,只能按相似度找最近点,缺少层级粒度和关联路径。该 proposal 提出多层摘要、细节记忆和关联召回的方向。(GitHub)
另一个 OpenClaw issue 指出,静态加载全部 personality / memory 文件会导致 60%–80% context window 被无关内容占用,并建议改成 semantic context indexing、lazy context loading、compression & expansion、smart context management。(GitHub)
OpenViking 则把 memories、resources、skills 统一为 context database,采用类似文件系统的层级管理,支持 L0/L1/L2 tiered context loading、目录递归检索、可视化检索轨迹和自动 session 管理。(GitHub) TencentDB Agent Memory 也强调“symbolic short-term memory + layered long-term memory”,并宣称在 OpenClaw 集成场景下降低 token 使用、提升任务成功率。(GitHub)
这些方向共同说明:Agent memory 正在从“扁平向量库”走向“层级上下文操作系统”。

4. 短期记忆的推荐架构

下面是一个适合生产 Agent 的短期记忆架构:
这里最重要的是两点:
第一,原始日志与 Prompt 投影分离。系统可以完整保存所有历史,但每一轮只选择性注入必要上下文。
第二,摘要不是唯一记忆。摘要只适合保存 narrative context;精确事实、文件路径、错误栈、用户硬约束、工具结果引用应该结构化保存。

5. 如何持续处理上下文爆炸?

5.1 先做 token budget,而不是等报错

推荐把上下文窗口拆成几个预算:
预算项
含义
system_budget
系统提示、工具定义、规则、skills
memory_budget
running summary、事实表、长期召回
recent_budget
最近若干轮原文
rag_budget
检索片段
tool_budget
工具输出摘要或引用
output_budget
模型回复预留
compaction_budget
压缩调用预留
safety_budget
安全冗余
一个实用公式:
OpenCode 的 reserved 配置体现的就是这个思路:不要把 context window 用满,要给 compaction 和后续输出留空间。(OpenCode)

5.2 分阶段处理:Trim、Prune、Summarize、Flush

不要一上来就总结全部历史。推荐四阶段:
阶段
操作
适合处理
Trim
丢弃明显无用消息
闲聊、重复确认、过期中间步骤
Prune
裁剪工具输出
长日志、长 diff、搜索结果、测试输出
Summarize
总结旧对话
任务背景、已完成动作、关键决策
Flush
转存长期记忆
稳定事实、用户偏好、项目经验
OpenCode 源码中可以看到 prune 与 compaction 是分开的:旧工具输出会先被标记 compacted,再由 compaction agent 处理更大的历史压缩。(GitHub) LangGraph 文档也把 trim、delete、summarize 作为独立策略列出,而不是混成一个动作。(LangChain 文档)

5.3 保留 recent tail,压缩 old head

短期记忆压缩最容易犯的错误是“把所有历史总结成一段,然后清空最近对话”。这会破坏当前任务连续性。
更好的策略是:
Recent tail 通常保留最近 2–6 轮,或者保留最近 2k–8k token。OpenCode 源码中也能看到 recent tail 预算和 tail turn 选择逻辑,说明真实 coding agent 会刻意保护最近上下文。(GitHub)

5.4 工具输出不要直接塞历史

Agent 上下文爆炸的最大元凶通常不是用户聊天,而是工具输出:
推荐做法:
工具输出类型
处理方式
短结果
原文保留
长日志
摘要 + 错误片段 + 原文引用
文件内容
只保留 path、hash、相关行号
搜索结果
保留 top evidence,不保留全部候选
测试结果
保留失败用例、错误栈、命令、exit code
大 diff
保留文件路径、核心变更、风险点
也就是说,Memory 里保存的是:
而不是把完整日志塞进 prompt。

6. 如何避免重复压缩导致“记忆漂移”?

重复压缩是短期记忆系统最危险的问题之一。
假设你每 20 轮压缩一次:
如果每次都只基于上一次摘要继续压缩,信息会逐渐失真:
风险
表现
遗漏
某个约束在 S2 消失
语义漂移
“暂时方案”变成“最终决策”
过度概括
具体文件路径、变量名、错误码丢失
任务错位
已完成、进行中、待做混淆
虚假记忆
摘要模型补全了历史中没有的内容
解决方案如下。

6.1 用 Anchored Summary,而不是自由摘要

Running summary 应该是结构化的,不是自然语言散文。
推荐模板:
Goal:当前任务目标
Constraints:用户硬约束、偏好、禁止事项
Progress Done:已完成事项
In Progress:当前正在做什么
Blocked:阻塞点
Key Decisions:关键决策及原因
Next Steps:下一步
Critical Context:错误、假设、风险、边界条件
Relevant Files:文件路径与作用
OpenCode 源码中的 compaction summary template 也采用类似结构,强调保留目标、约束、进展、关键决策、下一步、关键上下文和相关文件。(GitHub)

6.2 维护 message lineage,知道哪些消息已经被摘要

每次摘要都要记录:
这样可以避免重复摘要同一段历史,也方便后续从原始日志回放。LangMem 的 RunningSummary 中也有类似思想,会记录 summary 和已摘要的 message ids。(LangChain)

6.3 摘要时合并 delta,不要重写全部

错误做法:
更好的做法:
OpenCode 的 compaction 逻辑里也区分了 previous summary 与新上下文,并构造 update anchored summary 的 prompt,而不是每次完全从零开始。(GitHub)

6.4 对不同信息用不同压缩策略

不要把所有内容都压成一段 summary。
信息类型
推荐存储
用户偏好
长期 profile memory
项目规则
AGENTS.md / CLAUDE.md / rules
当前任务状态
task state
错误日志
tool output store + 摘要
关键事实
fact table
推理过程
通常不长期保存,只保存结论与依据
文件变更
path + diff summary + commit ref
RAG 证据
document id + chunk id + quote/ref
Claude Code 的设计也体现了这一点:稳定项目规则在 CLAUDE.md 或 auto memory 中,每次 session 加载;conversation history 则通过 compaction 摘要;部分 path-scoped rules 会在匹配文件再次读取时重新加载。(Claude Code)

7. 一套可落地的短期记忆算法

下面是一个工程实现示例。

7.1 数据结构


7.2 Prompt 组装

关键点是:Prompt 不是直接等于 messages,而是由多个 memory projection 组装出来。

7.3 压缩触发


7.4 摘要校验

这里的 required_items 可以包括:
例子
用户硬约束
“不要改数据库 schema”
当前目标
“修复登录过期 token 返回 200 的问题”
文件路径
src/auth/session.ts
错误字符串
expected 401, got 200
决策
“先加测试,再改 middleware”
待办
“补充 refresh token case”
这一步可以显著降低重复压缩造成的信息丢失。

8. 推荐的 Memory 压缩流程图

这个流程有一个核心原则:
永远保留可恢复的原始记录,但永远不要把全部原始记录都塞进模型。

9. RAG 场景下,Memory 和检索上下文如何配合?

RAG 系统里,Memory 容易和知识库检索混在一起。建议区分:
类别
来源
作用
Conversation Memory
当前会话
保持对话连贯
User Memory
用户历史
个性化、偏好、长期约束
Project Memory
项目文件
架构、规则、命令、约定
Retrieval Context
文档库 / Web / DB
回答当前问题的证据
Tool Observation
工具调用
当前任务执行反馈
在 Prompt 预算有限时,优先级通常是:
也就是说,Memory 不能和 RAG 抢上下文。它应该帮助 RAG 决定“检索什么”和“如何解释结果”,而不是把历史全部塞满。

10. 工程实践:如何设计一个抗上下文爆炸的 MemoryManager?

10.1 核心接口

10.2 必备能力

能力
为什么重要
token counting
不精确计数就无法稳定控制上下文
recent tail selection
保证当前任务连续
structured summary
避免自由摘要丢关键信息
tool output store
防止日志污染上下文
summary lineage
防止重复压缩和漂移
fact extraction
把稳定事实从 narrative summary 中拆出来
source references
需要时可回溯原文
compaction validation
检查摘要是否丢失硬约束

10.3 不推荐做法

错误做法
后果
每轮直接拼接全部历史
成本高、慢、容易爆上下文
只保留最近 N 条消息
丢失早期约束
只保留一个自由文本 summary
容易漂移和幻觉
把工具输出全部塞进消息
日志污染严重
压缩后删除原始日志
无法恢复、无法审计
长期记忆无筛选全量注入
长期记忆反而变噪声

11. 一个生产级短期记忆 Prompt 模板

可以用下面这种结构来做 compaction prompt:
你是 Agent 的上下文压缩器。
你的任务不是写总结文章,而是生成下一轮 Agent 可以继续工作的最小充分上下文。
必须保留:
  • 用户当前目标
  • 用户硬约束和偏好
  • 已完成事项
  • 正在进行的事项
  • 阻塞点
  • 关键决策及原因
  • 精确文件路径、函数名、命令、错误字符串
  • 下一步行动
必须删除:
  • 重复寒暄
  • 已过期尝试
  • 无关工具输出
  • 没有后续价值的中间推理
输出结构:
  • Goal
  • Constraints
  • Done
  • In Progress
  • Blocked
  • Key Decisions
  • Critical Context
  • Relevant Files
  • Next Steps
这个模板的重点是:为继续执行任务服务,而不是为人类阅读服务。

12. 最佳实践清单

12.1 短期记忆

实践
建议
最近原文
保留最近 2–6 轮,或 2k–8k token
旧历史
压缩成 structured running summary
工具输出
长输出文件化,只注入摘要和引用
压缩触发
不要等满;在 70%–85% 左右逐步处理
应急策略
保留 system、用户当前输入、recent tail、硬约束
摘要更新
使用 previous summary + delta merge
校验
检查文件路径、错误码、用户硬约束是否保留

12.2 长期记忆

实践
建议
写入标准
只有稳定、可复用、未来有价值的信息才写入
存储结构
profile / project / task / fact / episode 分层
召回方式
query-based retrieval + priority + recency
注入方式
按需注入,不要全量加载
维护方式
可编辑、可审计、可删除

12.3 Agent / Coding 场景

场景
处理方式
代码库探索
用 subagent 独立窗口,主会话只接收摘要
大量 grep / test output
保存原文到 tool store,Prompt 只放关键行
长任务
定期生成 handoff summary
多文件修改
facts 中保存 active files 和变更目的
多次失败
把失败路径、错误原因、禁止重复尝试写入 summary

13. 总结

Memory 的本质不是“多记一点”,而是“少塞一点、准取一点、可恢复一点”。
短期记忆在多轮对话中的核心架构应该是:
Claude Code 的经验是:项目规则、auto memory、skills、subagent context、compaction 要分层处理。OpenCode 的经验是:auto compact 之前先 prune 工具输出,并为压缩保留 token buffer。LangGraph / LangMem 的经验是:短期记忆应该是 state,可 trim、delete、summarize。LlamaIndex 的经验是:短期 chat history 超限后可以 flush 到长期 memory blocks。(Claude Code)
最终,一个好的 Memory 系统应该做到:
当前任务不断片,旧信息不爆炸,摘要不漂移,细节可恢复,长期经验可复用。
回到首页