type
Post
status
Published
date
May 22, 2026
slug
agent47
summary
tags
Agent
category
icon
password
0. 先给结论
在 Agent / RAG / LLM 应用里,Memory 不是“把历史消息全部塞回 Prompt”,而是一套“选择、压缩、索引、恢复、校验上下文”的工程系统。
短期记忆的核心问题不是“能不能记住”,而是:
随着对话轮次增加,如何让模型每一轮都看到足够有用的信息,同时不被历史噪声、工具输出、重复摘要和过期信息拖垮。
成熟系统的共同做法可以概括为五层:
- Recent Window:保留最近若干轮原文,保证连续性。
- Running Summary:把更早的历史压缩成结构化摘要。
- Tool Output Pruning:对旧工具输出做裁剪、占位或摘要。
- Persistent Memory / Project Memory:把稳定事实、偏好、项目规则沉淀到长期记忆。
- 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 包含 auto、prune、reserved 三个关键选项:自动压缩、移除旧工具输出、为压缩预留 token buffer。(OpenCode)OpenCode 还把 compaction 做成一个隐藏系统 agent:官方 agents 文档中
compaction 是 primary mode 的 hidden system agent,会在需要时自动运行,用户不能在 UI 中直接选择。(OpenCode)从 OpenCode 当前 dev 分支源码可以看到,它的 compaction 不是简单“把全量历史丢给模型总结”。源码中包含
PRUNE_MINIMUM、PRUNE_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_summary、max_summary_tokens、initial_summary_prompt、existing_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 系统应该做到:
当前任务不断片,旧信息不爆炸,摘要不漂移,细节可恢复,长期经验可复用。
分享
