
context 和 memory (默认指 long-term memory) 大家天天都能见到,但很少能说清楚到底是什么;我指的不是把这个词翻译过来,或者简单的描述为:这是一次给 LLM 输入的 prompt 总和;
本篇要讨论的是,context 内部到底是哪些东西?多久获取一次?底层到底是如何组织内容的,生命周期多长,内部如何维护。memory 也一样,都面临着同样的问题。
如果对其他 Agent 架构的相关文章感兴趣,关注我,在我的 x 主页文章列表还有其他若干篇拆解 Agent 架构的文章,也可以访问我的个人网站。
回到经典的 ReAct 中观察 Context
ReAct 的核心路径而是“行动 -> 观察结果 -> 基于结果再决策”的闭环。也叫:Act -> Observation -> Thought / Decision 模型。
每一轮对话下,都会产生若干次这样的循环;Context 在每一次循环中都要被构造一遍:
用户消息 -> Context_1 -> LLM 决定调用工具 工具结果 -> Context_2 -> LLM 决定继续调用工具 工具结果 -> Context_3 -> LLM 输出最终答案

即每次 LLM 调用前,都会从当前状态重新装配一个 Context。
每轮 Context 怎么来:同步读取
可以把 context 理解为一次只读拼装,当用户输入一个新问题时,Agent 会瞬间执行一次同步的“多路捞取与拼装”,把不同周期的记忆汇聚成当前的上下文(Context Window),然后喂给 ReAct(Reasoning and Acting)循环。
这个拼装过程的“配方”通常如下:

1. 从 Session State 读取短期记忆(Short-term Memory)
内容: 当前会话刚刚发生过的几轮对话,工具执行状态,任务状态等 特点: 绝对精确、基于内存(RAM)或 Redis 缓存 (读取更快),直接拉取最新的 N 条原始 Message。
2. 从 RAG 捞取事实内容(Fact Retrieval)- 如果有 RAG 设计的话
内容: 静态的企业知识库、产品手册、外挂数据库中的客观事实。 特点: 解决 Agent“知不知道这个硬核知识”的问题,通过我们前面聊过的 BM25 + 向量混合检索召回。
3. 从长期记忆捞取相关内容(Long-term Semantic Memory)
内容: 用户长期的画像、偏好、习惯、或者几十天前聊过的核心结论(例如:“用户习惯用 Python 编写代码”、“用户之前提到过他的系统运行在 Ubuntu 22.04”)。 特点: 它是语义相关的。系统会拿当前的 Query 去长期记忆库(通常是独立的向量库或图谱)里检索出相关的“记忆切片”。
4、其他事实数据
例如 Hermes 和 openclaw 的 Profile 等
这里就能很快理解,context 拼装极为频繁和琐碎,所以为了不影响 ReAct 执行效率,context 必须是同步读取,不能有阻塞。
Session State
一般一个 Agent 都需要设计一个类似 Session State 跟踪当前 session 的工作现场状态,其中主要包括短期记忆和运行状态:
Session State
├─ 短期记忆
│ ├─ transcript / messages
│ ├─ conversation summary
│ ├─ 当前工具结果
│ └─ 当前任务上下文
│
└─ 运行控制状态
├─ next node
├─ pending interrupt
├─ retry count
├─ pending tool call
└─ checkpoint metadata在一些外部副作用调用前后(例如某个事件监听)、或者某个流程节点完成、生命周期边界等(通常叫 checkpoint 检查点),都能对 Session State 记录一次 snapshot 快照,方便下次对任务进行恢复或者其他操作;类似数据库事务的 savepoint 或者游戏存档。
长期记忆 long-term Memory : 异步写入
context 是只读拼装的工作模式,长期记忆则是写入操作,长期记忆的写入如果做成同步的,Agent 的响应速度会直接崩溃;因为把原始对话变成“长期记忆”,需要消耗大量的 LLM Token 去做提炼和清洗,过程比较耗时。所以普遍采用异步任务(Celery 队列、后台 Worker 或事件驱动)来处理长期记忆的固化。
异步 Memory 流程只管写入,和 context 的读取完全是独立的两条路径。
不是每个运行时事件都能变成长期记忆,Memory 的整个生命周期系统也很复杂,先放一张 Memory 系统架构图:

两大 Memory 输入来源:
1、绿色通道:Direct Candidate Path(直接候选路径)
绿色通道的数据进来时就已经很干净,不需要 LLM 去猜:
- Explicit user request: 用户直接下达指令,比如:“记住我习惯用 Ubuntu 系统” 或者 “忘掉刚才的数据库密码”。
- Main Agent memory tool: Agent 在 ReAct 循环中,主动调用了专门的记忆管理工具(例如 save_user_preference)。
- Manual Profile edit: 用户在前端界面上手动修改了自己的画像档案。
2、橙色与蓝色通道:Extraction Path(异步抽取路径)
这是非结构化、充满噪声的原始素材(Raw source material),需要通过一个 Fact Extractor(事实抽取器) 进行打磨。
- 橙色部分(运行时状态): 包含刚才聊到的 Session State 里的原始 Transcript、工具调用结果(Observations)、当前 Context 压缩生成的 Summary,以及工作流状态。
- 蓝色部分(外部输入): 验证过的外部事件、导入的历史文档、或者后台定时触发的审查任务。
- 事实抽取器(Fact Extractor): 异步 Worker 调用 LLM,结合预设的清洗规则(rules),从这堆素材中压榨出纯净的事实(claims)和支撑这些事实的证据(evidence)。
Fact Candidate Intake(事实候选接纳区)统一的写控制边界
不管是通过绿色通道直接输入的,还是通过异步抽取提炼出来的,在真正进入数据库之前,都必须在一个叫 Fact Candidate Intake(事实候选接纳区) 的关卡进行“排队报到”。
Fact Candidate Intake是整张架构图最核心的工程隔离带:Candidate is the single write-control boundary(候选者是唯一的写控制边界)。这意味着,任何数据想变成长期记忆,必须被封装成一个标准的候选对象,它包含以下属性:
- claim:到底要记住什么事实(如:用户习惯用 Rocky Linux)。
- owner + scope:这条记忆属于哪个用户,在什么范围(全局/单应用)有效。
- evidenceRefs:这句结论是从哪几条原始对话或工具输出里提炼出来的。
- confidence:抽取模型或系统对这条记忆的置信度分值。
- producer:是谁触发了这条记忆的写入(用户自己、还是哪个后台 Agent)。
流水线清洗与加工:从候选到落库
一旦进入紫色的核心加工流水线,系统会执行一系列确定性的计算机软件工程操作:
- Validate schema + permissions: 校验这个三元组或结构化数据符不符合数据库的格式规范,且当前用户/Agent 有没有权限写入。
- Canonicalize + deduplicate(规范化与去重): *规范化:* 把表达模糊的词规范化。比如把 “Ubuntu系统”、“乌班图” 统一规范为标准词 Ubuntu_OS。
• *去重:* 如果库里已经有一模一样的内容,直接丢弃或合并权重,避免向量库膨胀。
- Resolve conflicts(冲突解决): 这是最关键的一步。 发现新记忆和旧记忆打架了(例如旧记忆写着 系统: CentOS,新记忆写着 系统: Rocky Linux)。系统会在这里启动冲突解决策略:是根据时间戳覆盖、还是保留两者并标明“系统已迁移”。
- Retention / TTL / delete policy(留存与生存时间策略): 计算这条记忆的生命周期。如果是敏感信息,是否需要设置 TTL(到期自动删除)?
• 右侧有一条虚线 forget 连到这里:当触发删除指令时,直接走删除策略将其中断或抹除。
- Write / update / reject: 历经千难万险后,系统最终做出决策:是写入新记录、更新老纪录,还是拒绝此次写入。
fact candidate 一般都会被设计为一个队列,“立刻进入队列” 不代表 “立刻开始同步处理写库”,可以采用优先级调度等方案处理队列内积压的候选事实。
存储层与闭环:Long-term Memory Store
最终,通过考核的记忆会进入底部的数据库:
- Long-term Memory Store: 持久化存储(可以是支持关系推理的知识图谱,或者是存储向量的 Vector DB)。
- Update retrieval indexes: 写入后,立刻更新检索索引(刷新 HNSW 向量索引或图邻接表),确保下一次检索能立刻查到。
完美的时序闭环(Future turns)

(这张图是我和 GPT Image 2 讨论几轮后,最终画了一张我比较满意的图,再次贴一下,看懂了这张图,你就看懂了工业界落地 Agent 记忆架构的终极通关秘籍。)
注意图最下方那条长长的紫色虚线:Future turns: retrieve relevant memory into Context -> 它指向了左侧的异步审查触发器和未来的输入流。
当新的对话开启,系统会从这个 Long-term Memory Store 中把沉淀好的记忆重新捞出来,塞进全新 Loop 的 Context 中。
阶段性总结:Context 读取 |Memory 写入
我始终强调读和写,这是从思维上深入理解并区分 Context 和 Memory 的关键:
读路径:为了“现在这次 LLM 调用”组装 Context 写路径:为了“未来的 session”沉淀 Long-term Memory

memory 的存储
在底层的物理实现上,长期记忆(Long-Term Memory)的存储、检索与 RAG 几乎是一模一样的。 它们在底层共享了同一套技术栈和算法方案。甚至可以说,Agent 的长期记忆库,本质上就是一个专属于该用户的、高频动态更新的“微型外挂 RAG 数据库”。
关于 RAG 存储、检索、召回、重排,我之前写过一篇详细的教程,还配有 Notebook 课程:
https://x.com/i/status/2076175810265018554
既然 Memory 异步写入 - 为什么 Context 还能及时被更新
memory 是异步写入的,是个耗时操作;而每次的 context 是同步读取的,要求快速读取到合理的 context。
如何保证每次 Context 获取到的是最新消息?
还记得之前的 Session State,跟踪当前 session 的工作现场状态:
Session State
├─ 短期记忆
│ ├─ transcript / messages
│ ├─ conversation summary
│ ├─ 当前工具结果
│ └─ 当前任务上下文
│
└─ 运行控制状态
├─ next node
├─ pending interrupt
├─ retry count
├─ pending tool call
└─ checkpoint metadata这些数据是内存数据,会立即进入上下文 Context;保证了 LLM 拿到的是最新的上下文数据。即使遇到上下文满,需要压缩的情况,最近几条内容也不会被压缩。
“滑动窗口(Sliding Window)+ 局部压缩(Local Compression)”
为了保证 Agent 在高频的 ReAct Loop 中既不“健忘”,又不会因为 Context 塞得太满而崩溃,框架在处理短期记忆(Session State)时,遵循了“保新压旧” 原则。
滑动窗口就是预留给最近一段原始会话历史的范围。“滑动”指的是新消息不断到来时,窗口向最新消息移动,最旧的一小段退出原始窗口 -> 被压缩成 summary、归档,或按策略丢弃。最常见、也最稳妥的是按 token 预算 + 完整消息边界 控制:
保留最近的消息, 直到达到 short-term context budget; 不要把一组 tool call / tool result 从中间硬切开。
在实际工程中,Session State 的短期记忆通常会被划分为两个核心区域:动态观测区(Active Window) 和 历史压缩区(Buffer/Summary Zone)。
1. 绝对不压缩的“动态观测区”(最新几条 Transcript)
- 规则: 最近的 N 轮对话(或者最近的 M 次 ReAct Loop 轨迹)是绝对不能动、原汁原味保留的。
- 原因: 大模型需要依靠最原始的字眼、语气、以及刚刚发生的代词(如“它”、“刚才那个报错”)来维持精准的逻辑推理。一旦把最近两轮的对话给压缩成“用户询问了工具问题”,大模型瞬间就会失去对“指代消解”的能力,ReAct 的工具调用也会直接抓瞎。
2. 局部压缩的“历史压缩区”(更早的对话历史)
- 规则: 当对话轮数超过设定阈值,为了腾出 Context 空间,系统会对老旧的历史进行异步或同步的局部摘要(Summary)。
- 做法: 把更早的 10 轮对话丢给一个轻量级模型,压缩成一段话:“前情提要”
所以,每一次 ReAct Loop 拼接全新的 Context 时,真实完整的结构是这样的:
Context =
当前用户输入
+ Profile / System Instructions
+ 最近原始 Transcript
+ 当前 Tool Call / Tool Result
+ 历史压缩 Summary
+ 按需召回的 Long-term Memory
+ 可选 RAG Evidence
+ Tool Schemas为什么说这和长期记忆的异步写入是完美互补的?
因为这两者的生命周期和职责分工极度明确,完美避开了时序上的冲突:
- 短期记忆压缩(Runtime 救急): 它是为了应付当前 LLM Context Window 的硬限制。它只管把当前会话里“过去发生的事情”缩水成一句话,目的是让当前的 ReAct 循环能顺利跑完,属于临时性的局部应急处理。
- 长期记忆异步写入(Storage 固化): 它是为了跨会话(比如明天、下个月)的知识沉淀。后台异步 Worker 在单轮对话结束后,会去读最原始、没有经过任何损伤的完整 Transcript 记录。在这个时候,它才会去粗取精,提炼成跨越时间周期的知识节点(如:用户将系统从 CentOS 迁移到了 Rocky Linux),然后写进图谱或向量库。
“保新压旧” 解决当前 session 的 Context Window 与连续推理问题;长期记忆的异步沉淀解决跨 session 的知识复用问题。两者互补,但仍需要证据、版本、冲突与删除策略,才能避免把临时信息或过时事实固化为错误记忆。 在没有发生上下文压缩(数据摘要)的情况下,Session State(短期记忆)中被判定为 Context 需要的所有数据(筛选后),都会直接原样塞进当前的 Context Window 中。

上下文压缩
“压缩”是属于当前上下文(Context Window)的防洪机制,它改变的是文本的“展现形式”。当 Context 触发压缩(比如对话轮数太多了,或者 Loop 轨迹太长了)时,被揉碎压缩的全是当前 Context 里的历史内容。这个“历史大杂烩”具体包括:
- 过往的 Transcript 记录: 比如 5 轮对话前的用户寒暄、扯淡、或者已经解决掉的前置问题。
- 工具调用轨迹(Tool Traces): 在之前的 ReAct Loop 里,Agent 曾经调用过 SQL 查询、网页抓取 等工具,LLM 当时输出的思考(Thought)、行动(Action)以及工具返回的巨量原始数据(Observation)。这些数据在当时很重要,但随着 Loop 推进,它们变成了“沉没成本”,是压缩的重点对象。
- 之前获取到的记忆与 RAG 片段: 比如在一轮对话开始时,系统从外挂 RAG 里捞出了 3 段产品说明书拼进了 Context。过了几轮 Loop 后,这些说明书如果依然占地方,也可能会随着历史大杂烩一起被“揉碎”提炼成更简短的摘要背景。
它们被压缩的目的只有一个: 腾出宝贵的、原汁原味的 Token 空间,留给最新的几条 Transcript 和当前这一次 Loop 的工具输入输出,确保 LLM 此时此刻不卡壳。
这里的压缩不是传统的文件压缩,而是在内存中对 Context 进行压缩;
例如:典型的 LLM-based Context Compression :
它是高级的语义提炼。它是让大模型去阅读这 10,000 个 Token 的历史轨迹、工具调用输出,然后用它自身的“理解力”,把这些巨量的信息浓缩成 500 个 Token 的高度概括。
但 “上下文压缩” 不一定都用大模型。也可能是简单截断、按 role 删除旧消息保留最近 N 条。
成熟 Agent 往往采用:
原始最近窗口 + 一份结构化历史摘要 + 长期保留 raw transcript 作为审计/回溯证据
总结
上下文工程和记忆系统,本身就十分复杂的,如果读完本篇觉得概念太多,可以记住核心机制:
读路径:为了“现在这次 LLM 调用”组装 Context 写路径:为了“未来的 session”沉淀 Long-term Memory
写文章不易,特别是把结构捋清楚,配图,再讲出来;如果觉得有用还请一键三连;关注我,我会不定期更新 Agent 架构相关文章。
如果都读到这里了,可能对其他 Agent 相关的文章也感兴趣:
https://x.com/i/status/2075171293029179866