03 · 架构与归属
架构与归属:分层上下文与状态归谁所有
上一章拆解了单条记忆的写入→存储→检索→遗忘→治理——这章把镜头拉远,看系统如何分层组织这些记忆,以及记忆状态归谁所有。
本章你将建立的 schema
- 一个 agent 的记忆按容量分两层:在上下文窗口内(快、贵、易失)与窗口外(慢、廉、持久),靠 agent 自己驱动的分页在两层间搬运——这是操作系统式的 virtual context management。
- 哪一层留什么不是固定规则,而是 agent 自调用记忆编辑函数动态决定;Letta 把这件事独立成一类 sleeptime agent,在空闲时后台重写。
- 记忆状态可以归三方所有——app / framework / vendor——这是一条与分层正交的轴,决定了控制权、可移植性、隐私 与 代码量、锁定、数据驻留之间的取舍。
第 02 章把一条记忆的命运拆成了五个环节。但一个真实 agent 不是只有一条记忆——它有成百上千条,且每一轮推理都要在有限的上下文窗口里做取舍:窗口装得下的,直接读;装不下的,得留在别处、按需调入。这就引出了第一条结构轴——记忆按容量分层。第二条轴更隐蔽却同样决定架构:这些记忆的状态(谁来存、谁能改、存在谁的机器上)归谁所有。两条轴合起来,决定了一套 agent 记忆系统长什么样。
3.1分层虚拟上下文:把记忆做成操作系统
MemGPT 把 agent 的记忆按"装不装得进上下文窗口"分成两层——窗口内是内存,窗口外是磁盘——并让 agent 用函数调用在两层间分页(paging)搬运,这套机制叫 virtual context management(虚拟上下文管理)。
上下文窗口是有限的、且每个 token 都要付 prefill 算力(第 02 章 #s25 的延迟问题)。把全部历史塞进窗口,要么超限报错,要么成本随轮次线性膨胀、还撞上 lost-in-the-middle 的准确率塌陷。但记忆又必须可以无限增长。矛盾的出口是经典的——操作系统早就用虚拟内存解决过:物理内存装不下的页,换出到磁盘,需要时再换入。MemGPT(arXiv 2310.08560, 2023-10)把这套照搬给 LLM:让 agent 在一个"看起来无限"的记忆地址空间上工作,而真正进窗口的只是当前需要的那一小部分。
两层的解剖
MemGPT 把记忆地址空间切成 main context(主上下文,即 in-context = 窗口内 = "内存")与 external context(外部上下文,即 out-of-context = 窗口外 = "磁盘")两层。每一层内部还有结构:
- in-context(内存) —— 真正进 LLM 窗口、每一轮都被读到的部分,又分两块:
- FIFO 队列:滚动的消息历史 + 一段系统生成的递归摘要。先进先出,旧消息被挤出窗口时换出到磁盘层。
- core memory blocks(核心记忆块):常驻窗口的小块结构化文本,典型是
persona(agent 自己是谁)与human(它对当前用户的画像)。这块是 agent 可以直接读、也可以自己改写的"工作便签"。
- out-of-context(磁盘) —— 不进窗口、按需调入的部分,也分两块:
- recall storage(召回存储):被挤出 FIFO 的完整消息历史。它不丢,只是不常驻;agent 可以按时间或关键词把某段对话调回窗口。
- archival storage(归档存储):一个向量库,放 agent 主动归档的任意长文本/事实。容量实际无界,靠语义检索调入——这一块正是第 01 章讲的 RAG 读取路径,只是这里它是更大架构里的一层,不是全部。
底层机制(比文档深一层)
分页的触发不靠外部代码轮询,而是由 agent 自己驱动。MemGPT 在系统提示里告诉模型:你的窗口快满了(给出 token 占用百分比),你有这些记忆函数可以调用。模型于是在正常回复之外,多输出一个函数调用——把某段当前不需要的内容写入 archival、或把某段过去的内容从 recall 读回。换句话说,"换页"被实现成一次普通的 tool call,与 agent 调天气 API 没有本质区别;不同的只是这些工具操作的是 agent 自己的记忆地址空间。这也解释了为什么 MemGPT 必须基于一个会主动调用工具的 LLM:不会发函数调用的模型,就驱动不了分页,这套架构直接失效。
代价也由此而来。每一次换入/换出都是一次额外的 LLM 推理(决定换什么)加一次存储/检索往返,所以分页不是免费的——它把"上下文超限"这个硬约束,换成了"多花若干次模型调用 + 检索延迟"这个软成本。在 V(模型不可靠地决定换页时机)时失效:如果模型该归档时不归档,FIFO 仍会溢出;该调回时调错,关键事实仍然缺席。架构提供了机制,但治理质量取决于模型的判断力。
"内存 / 磁盘"类比抓住了核心——分层 + 按需调度 + 对上层呈现"无限"假象。但边界要标清:操作系统的换页由 MMU 硬件 + 内核确定性触发(缺页中断),程序自己感知不到;MemGPT 的换页由概率性的 LLM 判断触发,且 agent 完全知情并主导。所以它更像"一个能自己决定把哪些笔记收进抽屉的人",而不是"透明的虚拟内存"。把它当成全自动、零认知开销的内存层,会高估它的可靠性。
一条事实被 agent 归档进了 archival storage(向量库),但没有留在 core memory block 里。下一轮对话,这条事实会自动出现在 LLM 的上下文窗口里吗?
展开答案
不会。archival 在窗口外("磁盘"层),默认不进 LLM 窗口。要让它重新进入窗口,agent 必须在这一轮主动发起一次检索(archival search)把它调回。这正是 core blocks 与 archival 的关键区别:core 是常驻的(每轮都读),archival 是按需的(检索才读)。所以"什么该放 core、什么该归档"这个决定,直接决定了一条记忆是每轮可见还是要主动捞——这就是 3.2 节自编辑要解决的问题。
3.2自编辑与分页:agent 自己决定记什么
core memory 里留什么,不由外部规则决定,而是 agent 自己调用 memory_replace / memory_append 改写核心块、调用 archival insert 归档;Letta 进一步把"巩固记忆"独立成一类 sleeptime agent,在空闲或轮次之间后台重写这些记忆块。
3.1 给了机制(两层 + 分页),但留了一个空白:谁来决定哪条记忆放哪层、什么时候改写?如果由开发者写死规则(比如"用户名永远进 core"),就退化成第 01 章批判过的静态 RAG——没有 agent 自主的写入路径,正是门槛那条分界线的反面。MemGPT/Letta 的选择是把这个决定权交还给 agent 本身:让模型在对话过程中,根据它对"什么重要"的判断,自己编辑自己的记忆。这把"记忆治理"从一段外部代码,变成 agent 的一种行为。
自编辑:记忆函数就是工具
Letta(MemGPT 的工程化实现)给 agent 一组操作自身记忆的工具。agent 像调用任何工具一样调用它们:
# agent 在一轮对话中,除了回复,还可多发这些 tool call:
# 1) 改写 core memory 里的某一块(原地替换一段文字)
memory_replace(block="human",
old="用户在用 Postgres",
new="用户已迁移到 MySQL") # 状态变更 → 直接覆盖旧值
# 2) 往 core memory 块追加一条(块没满时)
memory_append(block="human",
content="偏好简洁回复,不要寒暄")
# 3) 把当前不需要常驻的内容归档到向量库("换出")
archival_insert(content="2026-06 排障会议纪要:根因是连接池泄漏 …")
# 4) 需要时再从向量库语义检索调回("换入")
results = archival_search(query="上次连接池问题怎么解决的")
memory_replace原地覆盖,是写入时冲突消解(第 02 章 #s21 的 recency-wins)在架构层的落点——旧值不是被新值挤出窗口,而是被 agent主动抹掉。archival_insert / search就是 3.1 的换出/换入,实现成两个普通工具。
底层机制(比文档深一层)
core memory 块是有容量上限的(它常驻窗口,占的就是宝贵的 token 预算)。所以 memory_append 在块满时会失败或触发"该精简了"的信号——这恰恰强迫 agent 做取舍:要塞新事实,就得先 memory_replace 删掉或合并旧的。换句话说,容量上限不是缺陷,而是逼出治理行为的设计:它把"无界记忆 = 什么都记 = 没有有用的可记"(第 02 章 #s24)这个问题,在架构层用一道硬墙挡住。代价是:取舍质量完全依赖模型判断,模型若误删一条很少引用但关键的事实,这条记忆就真没了(它不在 core,也没被归档)。
sleeptime agent:把巩固搬到后台
在线对话时,agent 既要回复用户、又要顺手编辑记忆,这是双重负担——且仓促。Letta 把"巩固记忆"这件事拆成一个独立的 agent 类型:sleeptime 是 Letta 里的一等 agent_type(与普通对话 agent 并列)。它在主 agent 空闲、或两轮对话之间被唤起,专门重写共享的 memory blocks:把零散事实归并、把过期信息清掉、把长对话提炼成更紧凑的 core 内容。
Letta 文档把 sleeptime 类比成"睡眠期巩固记忆"——人脑在睡眠中把当天的短期记忆整理进长期记忆。类比抓住了"离线、批量、整理"这层。边界:人脑的巩固是无意识、自动的生理过程;sleeptime agent 是一个显式被调度的子 agent,要消耗真金白银的模型调用,且整理质量取决于给它的提示与模型能力。把它当成"免费的后台魔法"会低估其成本——它本质是"用额外算力,把治理从在线关键路径上挪开"。
用户连续三轮提到迁移数据库:第 1 轮"团队在评估 MySQL",第 5 轮"决定迁了",第 9 轮"迁移完成,但有些慢查询"。在线主 agent 在第 5 轮做了一次 memory_replace,把 core 里的"用户在用 Postgres"覆盖成"用户已迁到 MySQL"——保证后续回复不再口径错乱(第 04 章 #s44 的"陈旧记忆"陷阱就是漏了这一步)。sleeptime agent 在当晚空闲时,把这三轮散落的细节("评估→决定→完成 + 慢查询")归并成 core 里一条结构化条目,并把三轮原始对话归档进 archival——下次用户回来,agent 一进窗口就知道"这位用户刚迁到 MySQL、关注慢查询",无需现场翻历史。
如果完全关掉 sleeptime agent,只留在线主 agent 自编辑,会牺牲什么?
展开答案
不会丢失"能不能记"的能力——主 agent 仍可自编辑。牺牲的是巩固的质量与时机:在线 agent 在回复用户的同一轮里顺手改记忆,时间紧、视野只到当前这轮,容易留下零散、重复、未归并的条目(比如三轮迁移信息各记一条,没合并)。sleeptime 用离线时间做全局整理——它能纵览多轮、做去重与提炼。所以关掉它,记忆仍能用,但会随时间碎片化、冗余累积,core memory 的信噪比下降。这是"在线即时治理"与"离线批量巩固"的取舍:省一份后台算力,换记忆质量的缓慢退化。
3.3记忆状态归属轴:谁来存、谁能改、存在谁的机器上
与"分几层"正交的第二条结构轴是记忆状态归谁所有——app-owned(开发者自托管)、framework-owned(第三方库、自运维)、vendor-owned(模型厂商托管),从左到右,代码越写越少,但控制权、可移植性、隐私也越交越多。
3.1 / 3.2 回答了"记忆怎么组织",但回避了一个工程上同样要命的问题:这些记忆的状态物理上存在哪、谁有权改、换个模型还带不带得走。同一套分层架构,可以全跑在自己机器上,也可以把存储与抽取整个外包给 OpenAI 或 Google。这个选择不影响"记忆是不是闭环",但直接决定锁定风险、数据合规、运维负担。把它当成一条独立的轴来选,而不是和框架选型混为一谈,才不会在上线后才发现记忆被锁死在某家厂商。
三个归属档位
app-owned(应用拥有) —— 控制 / 可移植 / 隐私三项最大,代价是代码量最大。代表是 Anthropic 的 memory tool:它只提供工具接口——对一个 /memories 目录的 create / read / update / delete 操作——而存储后端完全由开发者自己托管。关键事实:Anthropic 的服务器上不留任何记忆,记忆数据从不离开开发者的基础设施。开发者要自建存储、检索、保留策略,但换得对数据物理位置与生命周期的完全掌控。
framework-owned(框架拥有) —— 跨模型可移植性最好,基础设施自运维。代表是 Mem0 / LangGraph / Zep:它们提供完整的记忆栈(抽取、存储、检索、冲突消解),与具体模型解耦——今天用 Claude、明天换 GPT,记忆层不动。代价是这套栈跑在你自己的机器/数据库上,运维(扩容、备份、监控)归你。它在"全自己写"和"全外包"之间取中道。
vendor-owned(厂商拥有) —— 代码量最少,但锁定、保留不透明、数据驻留在厂商基础设施。三个代表各有形态:
- OpenAI
previous_response_id(配store=true):传上一次响应的 ID,厂商自动接续上下文,保留 30 天。 - OpenAI Conversations API:持久化的对话对象,无 TTL(不自动过期)。
- Google Gemini Memory Bank(Vertex AI):托管存储、自动抽取记忆,且模型无关(memory bank 不绑定某个 Gemini 版本)。
共同点:开发者几乎不写记忆代码——存储、抽取、TTL 全由厂商管;共同代价:记忆物理上躺在厂商机器,保留与删除策略不完全透明,且要迁走就得重做。
| 维度 | app-owned | framework-owned | vendor-owned |
|---|---|---|---|
| 代表 | Anthropic memory tool | Mem0 / LangGraph / Zep | OpenAI / Gemini Memory Bank |
| 记忆数据存在哪 | 开发者自己的基础设施(Anthropic 服务器不留) | 开发者自运维的存储/数据库 | 厂商的基础设施 |
| 代码量 | 最多(自建存储/检索/保留) | 中(库给栈,运维自管) | 最少(传个 ID / 自动抽取) |
| 跨模型可移植 | 高(接口与模型无关) | 高(框架本就模型解耦) | 低(绑厂商,迁走需重做) |
| 保留 / TTL | 开发者定 | 开发者定 | 厂商定:previous_response_id 30 天 / Conversations 无 TTL / Memory Bank 托管 |
| 隐私 / 数据驻留 | 最强:数据不离开自有环境 | 强:在自有环境,依赖自身合规 | 弱:驻留厂商,策略不完全透明 |
| 主要风险 | 运维与实现成本高 | 运维负担(扩容/备份/监控) | 厂商锁定 + 保留不透明 |
正交搭档:Anthropic context editing
归属轴管的是"记忆状态存在哪",但还有一类与之协作的服务端机制专管"窗口里的陈旧工具结果怎么清"。Anthropic 的 context editing(策略名 clear_tool_uses_20250919)是服务端能力:当上下文累积超过设定的 token 阈值时,自动把陈旧的工具调用结果清掉、替换成占位符——把宝贵的窗口空间腾出来。它本身不是"记忆"(不负责长期保存),而是第 02 章 #s25 上下文治理在厂商侧的一种实现。
context editing 负责清(把过期工具结果移出窗口),memory tool 负责留(把该长期记住的写到窗口外的文件)。单独用 context editing,清掉的信息就真没了;配上 memory tool,agent 在结果被清前先把承重信息归档到 /memories,等于"清"与"留"接力。Anthropic 报告的实测:在一个 100 轮 web-search 评测上,两者联合把任务表现提升 39%,token 消耗减少 84%——量级的提升来自"先归档再清理"的配合,而非任一单项。
归属是架构决策,不是细节:它一旦上线就难改。把记忆托管给 vendor-owned(比如 OpenAI Conversations)上线后,要迁到 app-owned,意味着记忆数据的格式、抽取逻辑、保留策略全要重做,且历史记忆未必导得出。涉及 PII 或受合规约束(数据不得出境/出厂商)的场景,务必在选型阶段就把归属轴定死,而不是先跑通功能、回头再补——回头的代价是重写整层。
自测
-
MemGPT 的 in-context 层里,
core memory blocks与FIFO 队列有什么本质区别?为什么 archival storage 不属于 in-context?参考答案
core memory blocks 是常驻窗口的小块结构化文本(persona/human),每轮都被读到、且 agent 可直接改写;FIFO 队列是滚动的消息历史,先进先出,旧消息满了会被换出到磁盘层。两者都在窗口内("内存"),但一个常驻、一个滚动。archival storage 是向量库,在窗口外("磁盘"),默认不进 LLM 窗口,只有 agent 主动检索才调回——所以它属于 out-of-context,不是 in-context。
-
为什么说 MemGPT 的"分页"必须基于一个会主动调用工具的 LLM?如果模型不发函数调用会怎样?
参考答案
因为换入/换出在 MemGPT 里实现成普通的 tool call——agent 在系统提示得知"窗口快满了 + 有这些记忆函数",于是在回复之外多输出一个函数调用来归档或调回内容。触发分页的是模型自己的函数调用,不是外部轮询代码。若模型不会/不发函数调用,该归档时不归档,FIFO 仍会溢出超限;该调回时不调回,关键事实仍然缺席——整套虚拟上下文管理直接失效。这也是它对模型工具调用能力的硬依赖。
-
一个团队要做政企客户的 agent,合同要求"客户数据不得离开客户自有机房"。归属轴上,vendor-owned(如 OpenAI Conversations)为什么直接出局?app-owned 与 framework-owned 哪个更合适?
参考答案
vendor-owned 的记忆物理上驻留在厂商基础设施(OpenAI/Google 的机器),且保留策略不完全透明——这直接违反"数据不得出机房"的硬约束,所以出局。app-owned(如 Anthropic memory tool,记忆只存开发者自己托管的后端、厂商服务器不留)与 framework-owned(如自部署的 Mem0/Zep,跑在自有数据库)都能把数据留在自有环境,二者皆可满足合规。取舍在代码量与运维:app-owned 要自建存储/检索/保留(代码最多但最可控),framework-owned 用现成栈但要自运维。对"只要把数据留住、不想从零写记忆栈"的团队,framework-owned 通常是性价比更高的落点。
把两条轴叠起来设计
本章给了两条正交的轴:分层(in/out-of-context)与归属(app/framework/vendor)。现在设计一个 agent:它要分层(core blocks 常驻 + archival 按需)、要跨模型可移植(明年要保留"从 Claude 换到别的模型"的余地)、还要数据留在自有机房。请回答:(1) 归属档位该选哪个?为什么 app-owned 与 vendor-owned 都不是最优?(2) 如果同时还想用上 Anthropic 的 context editing 来控成本,这会不会和"跨模型可移植"冲突?该怎么权衡?(3) sleeptime 式的后台巩固,在你选的归属档位下由谁来跑、算力成本落在哪?把这三问串成一段架构决策,你就把 3.1–3.3 三节缝成了一个系统。第 04 章 #s43 会给出更细的框架选型判据来校准这个决策。
参考来源
- Packer 等,MemGPT: Towards LLMs as Operating Systems(arXiv 2310.08560, 2023-10)—— virtual context management、main/external context、分页机制原始论文。
- Letta 文档,Memory Blocks 与 Sleeptime Agents —— core memory blocks 结构、自编辑工具、sleeptime 一等 agent_type 的工程实现。
- Anthropic,Managing context on the Claude Developer Platform(2025-09-29)—— memory tool(客户端托管文件)、context editing(
clear_tool_uses_20250919)、100 轮评测 +39% / token −84% 的联合数据。 - Google Cloud,Vertex AI Agent Engine Memory Bank —— 托管、自动抽取、模型无关的 vendor-owned 记忆参考。
- OpenAI,Conversation state ——
previous_response_id(30 天)与 Conversations API(无 TTL)的厂商托管状态。