AI技术·2026年4月27日·9 分钟

Agent 框架中的上下文管理

深入对比 Pi、OpenClaw、Claude Code 和 Letta 四个主流 Agent 框架的上下文管理策略,涵盖大文件读取、会话裁剪和子 Agent 隔离三大核心问题,揭示不同框架如何收敛到相同的设计模式。

每一个 Agent 框架都会撞上同一个天花板:上下文窗口太小,装不下模型可能需要记住的所有信息。随着会话增长、文件读取膨胀、子 Agent 调用倍增、工具输出堆积如山,框架必须做出决策——什么留在工作集中,什么被压缩,什么留到后续按需检索。

过去两年,我们在 Arize 团队构建了 Alyx(我们的产品内置 Agent),这个问题的各种形态我们都遇到过。我们看到会话膨胀到模型忘记任务是什么,文件读取用模板代码吃掉半个上下文窗口,工具输出把真正的对话挤出局。

现在重要的已不再仅仅是往 prompt 里放什么,而是框架如何随着时间推移管理上下文。最优秀的系统不会把上下文窗口当成被动的转录缓冲区。它们主动管理:保持高价值状态在近处、按需翻页数据、构建索引来定位所需内容(grep 就在做这件事),并以暗示"还有更多可访问内容"的方式截断内容。

图片

管理上下文窗口

Pi、OpenClaw、Claude Code 和 Letta 在这里各有不同的选择,但它们正在收敛到一个相似的底层模式。上下文不再仅仅是能塞进转录记录的那些东西——它是系统必须主动管理的东西。真正的设计问题是:多少管理发生在框架内部,多少留给模型自己处理。

每一个上下文管理决策都编码了对模型行为的假设。核心问题是:框架应该主动约束上下文使用,还是依赖模型自己去正确管理那个预算。

图片

大文件上下文管理

文件读取让这个问题变得具体。当模型需要读取一个超出上下文容量的文件时,总得有人决定保留什么。四个框架都支持 offset 和 limit 参数来实现分页。

图片

Pi (pi-mono)

Pi 读取文件时有硬性上限:2000 行或 50KB,以先到者为准,即使模型没有要求分片。内容从头截断,工具输出会追加一个明确的续读提示:[Showing lines 1-2000 of 50000. Use offset=2001 to continue.] 工具描述也在强化这一点:"output is truncated to 2000 lines or 50KB. Use offset/limit for large files."

Pi 的方式是框架优先:框架先保护你,再教会模型分页。

OpenClaw

OpenClaw 继承了 Pi 的读取工具及其 2K 行 / 50KB 截断。普通文件读取的行为相同。在此基础上,它为 bootstrap 文件(会话启动时一次性加载的上下文文件)增加了额外限制:每个文件 12000 字符,总计 60000 字符。当 bootstrap 文件超出预算时,采用 75% 头部 + 25% 尾部的分割方式——你看到开头和结尾,中间被切掉。

工具结果有独立的预算:16000 字符或上下文窗口的 30%,以较小者为准。当尾部看起来"重要"(错误信息、JSON 闭合括号、摘要关键词等)时,切换到头尾模式;否则只保留开头。

OpenClaw 的方式是纵深防御:Pi 的截断作为第一层,然后是 bootstrap 注入的额外限制,最上面是工具结果预算。

Claude Code

Claude Code 在文件读取上采用双层防御。第一道门是在文件打开之前通过 stat 调用检查 256KB 字节上限——如果文件超过这个大小,读取会被立即拒绝,并返回错误提示模型使用 offset/limit 或 grep。第二道门在读取之后运行:输出被 token 计数,与 25000 token 预算对照,捕获那些滑过字节上限但 token 密度很高的文件。这两个限制都可以通过 Anthropic 的 GrowthBook feature flags 远程调整,无需发布新版本。

即使文件在限额以下,工具默认只返回开头的 2000 行,超过 2000 字符的行会被截断。模型必须通过 offset 和 limit 参数显式请求更多内容。

工具描述是一个完整的多段落 prompt,解释了分页机制、提及大小上限、覆盖图片/PDF/notebook 支持,并鼓励跨文件并行读取。offset 和 limit 参数各自有说明,告诉模型它们用于一次性无法读完的大文件。还有一个条件指令,根据 feature flag 决定是否在 prompt 中直接暴露 256KB 上限。

文件去重系统也值得一提。如果模型在同一范围内重新读取同一文件且文件未变,Claude Code 返回一个摘要替代完整内容,避免上下文中出现重复 token。

Claude Code 的方式是框架优先加远程可调:预读取字节门、后读取 token 门、行数和行长默认值、可操作的错误提示、丰富的工具 prompt、读取去重,以及让 Anthropic 在服务端调整一切的 feature flags。

Letta

Letta 采用了根本不同的方法。每个上传的文件都被解析、分块并嵌入到向量存储中,因此 Agent 可以同时进行精确匹配和语义搜索。这赋予它三个文件工具:open_files 用于直接查看(读取原始文本)、grep_files 用于精确模式匹配(也是原始文本)、semantic_search_files 用于基于语义的检索(基于嵌入的段落)。

当文件在 Agent 上下文中处于"打开"状态时,其可见内容会被截断到一个按模型上下文窗口大小分五档的每文件字符限制:8K 上下文对应 5000 字符,32K 对应 15000,128K 对应 25000,200K+ 对应 40000。同时打开的文件数量也按档位缩放,从小模型的 3 个到大模型的 15 个,默认回退值为 5。超出限制时,LRU 策略淘汰最近最少访问的文件。

Letta 的方式是内存优先:文件同时存在于原始文本和向量存储(嵌入块)中,上下文窗口只展示一个受管理的视图,模型通过工具访问更多内容。

图片

会话裁剪

随着对话增长,每个框架都必须决定保留什么、丢弃什么。这是设计差异最有意义的地方,因为压缩策略决定了长期运行的 Agent 是保持连贯还是逐渐退化。

Pi (pi-mono)

Pi 使用压缩(compaction):由 token 阈值触发的 LLM 驱动的摘要生成。

触发条件:估算的上下文 token 超过 contextWindow - reserveTokens(默认保留:16384 token)

保留内容:从对话末尾向前遍历,保留最近的约 20000 token 消息(keepRecentTokens)

摘要内容:更早的所有内容交给 LLM 进行摘要

摘要去向:成为一条合成的用户消息,前置到保留的尾部

工具调用安全:不会切断孤立的工具结果。遍历边界以保持工具调用/结果对的完整性

OpenClaw

OpenClaw 在 Pi 的压缩之上运行两个不同的上下文管理机制。

触发条件:历史记录超过上下文窗口的 50%(maxHistoryShare,默认 0.5)

保留内容:历史记录被分割为等量 token 的块;最旧的块被丢弃,其余保留并修复工具调用/结果对

摘要内容:被丢弃的内容经过分阶段的多轮 LLM 摘要,带合并步骤

摘要去向:与 Pi 相同——合成消息前置到保留的尾部

工具调用安全:repairToolUseResultPairing 修复块丢弃后的孤立工具结果;splitMessagesByTokenShare 避免在工具调用/结果对内部切割

预压缩刷写:一个静默的 Agent 轮次让 Agent 在历史消失之前将状态持久化到内存文件

第二层:非破坏性的内存中工具结果裁剪(先软裁剪,再硬清除),基于 5 分钟缓存 TTL,保护持久对话的同时为当前请求回收上下文

Claude Code

Claude Code 通过预查询优化和 LLM 驱动的压缩来管理上下文。

触发条件:估算 token 超过有效上下文窗口减去 13000 token 缓冲区(对于 200K 上下文的模型,压缩在约 167K token 时触发)

摘要内容:完整对话发送给模型,附带一个结构化的 9 段式 prompt,涵盖主要请求、关键技术概念、文件和代码、错误和修复、问题解决、所有用户消息、待处理任务、当前工作和可选的下一步

摘要去向:成为一条用户消息,告诉模型会话正在从耗尽上下文的上一次对话中继续

压缩后恢复:最多 5 个最近读取的文件在压缩后重新附加到上下文中,受 token 预算约束

摘要器安全:模型在独立标记块中生成分析草稿和最终摘要。草稿在摘要进入上下文之前被剥离,提升质量而不膨胀结果

Prompt 过长时的回退:如果压缩调用本身触及上下文限制,确定性的头部丢弃移除最旧的 API 轮次组(20% 的组,或足够关闭 token 缺口的数量)

预查询优化(每次 API 调用,无论上下文压力如何)。在每次模型调用之前,Claude Code 运行一个管理工具结果但不动对话文本的流水线。超大工具结果被持久化到磁盘并替换为 2KB 预览,每个工具上限 50000 字符,每条消息汇总上限 200000 字符——所以一个 60KB 的 grep 结果在新会话的第一轮就会被卸载。

Letta

Letta 通过多种压缩策略管理上下文,当主路径溢出时有两阶段摘要器回退。

触发条件:估算上下文使用超过上下文窗口的 90%

滑动窗口淘汰:从 30% 的消息开始(不是 10%),每次迭代增加 10%,直到 token 使用降到目标以下。保留最新消息,淘汰最旧消息

自压缩模式:使用 Agent 自身的模型进行摘要,无需单独的摘要器成本或配置

摘要器溢出的两阶段回退:首先将工具返回裁剪到 5000 字符并重试。如果仍然溢出,对转录记录进行中间截断,保留 30% 头部和 30% 尾部,丢弃中间部分

警告阈值:独立的 75% 内存警告在 90% 压缩触发之前发出

图片

子 Agent

在我们考察的框架中,子 Agent 通常与父会话隔离。这里没有任何一个例子将完整的父对话历史复制到子 Agent 中。问题在于它们继承了哪些工作空间上下文。

Pi 为每个委派任务启动一个新进程,带有内存中的会话。子进程只接收任务字符串作为其唯一的用户消息。不传递父对话历史。

OpenClaw 默认给子 Agent 全新的隔离会话,不带父转录记录。存在一个 fork 模式,可以将父转录记录复制到子 Agent 中,但仅限于同 Agent 的 spawn。工作空间上下文被过滤为最小允许列表(AGENTS.md、TOOLS.md、SOUL.md)。

Claude Code 有两条路径。默认的 typed-agent 路径创建空白对话:委派的 prompt 成为唯一的用户消息,没有父历史。较新的 fork 路径将整个父消息历史传递给子 Agent,用于 prompt cache 共享,加上一条合成的 assistant 消息和占位工具结果。工具为 worker 重建,带有独立的权限模式;异步 Agent 获得显式允许列表(Read、Grep、Glob、Shell、Edit、Write、WebSearch 等)。Agent 定义中引用的 Skills 被急切预加载。完整的 Skill 内容作为用户消息注入到初始对话中,而不是按需加载。

Letta 在普通工具执行时完全不 fork。工具在主 Agent 循环内运行。历史上下文通过专用搜索工具访问:会话搜索用于回忆记忆,归档记忆搜索用于嵌入存储。

图片

比较这四个代码库最令人震惊的发现不是它们有多大不同,而是它们有多么一致。

四个框架都硬性限制文件读取。四个都支持 offset/limit 分页。四个都限制工具结果大小。四个都隔离子 Agent 会话。四个都运行由 token 阈值触发的 LLM 驱动压缩。四个都估算上下文使用量并检测压力。这些不是巧合——它们是对同一个工程问题的趋同解法:一个必须感觉无限的固定大小工作集。

趋同远不止于拥有相同的功能。具体的设计选择也在呼应。Pi 和 OpenClaw 都从头部截断文件读取并追加续读提示。Claude Code 和 OpenClaw 都将超大工具结果持久化到磁盘。Pi、OpenClaw 和 Claude Code 都在压缩期间强制工具调用/结果边界安全。四个中有三个支持将父转录记录 fork 到子 Agent 中。这些框架在独立地得出相同的答案。

这些模式并不局限于编码 Agent。Arize 自家的 Alyx 助手,为数据探索而非代码编辑构建,也独立地收敛到了相同的设计。Alyx 将工具结果限制在 10000 token 预算内,使用二分查找找到能容纳的最大数据集分片。它通过从对话历史中修剪重复预览来去重幂等工具调用,只保留最新的一次。它将大型 JSON 负载拆分为压缩的 LLM 可见预览和完整的服务器端副本,模型可以通过 jq 深入查看——这与 Pi、OpenClaw 和 Claude Code 用于文件读取的"将超大结果持久化到上下文外部"模式相同。它对长单元格值进行头尾截断,并附带回指完整内容的引用。它用 char/4 启发式方法估算 token 压力,在对话跨过 50000 token 时强制设置检查点——此时模型在历史被裁剪之前写入自己的状态摘要,结合了 Claude Code 的确定性压缩触发和 OpenClaw 的预压缩状态刷写。它的子 Agent 以所有四个框架都在使用的隔离模式启动。一个为完全不同领域构建的产品,收敛到了相同的上下文管理方案。

过去 50 年的计算经验告诉我们,最好的内存管理是程序完全不需要思考的那种。寄存器、缓存行、页表、交换空间。每一层由系统管理,每一层对上层透明。程序只管运行。

Agent 框架正在朝同一个方向演进。目标不是把所有东西都展示给模型,而是在正确的时间给它正确的工作集,并允许它动态做出决策来管理自己的上下文。