返回博客2026年4月27日3 分钟阅读

Agent 框架的上下文管理:四种实现,殊途同归

摘要

Pi、OpenClaw、Claude Code、Letta 在文件读取、会话压缩、子 Agent 隔离上做了不同的选择,但仔细比对会发现,它们正在收敛到同一套设计模式。Arize 创始人 Aparna Dhinakaran 把四个代码库摊在一起做了对照。

本文翻译自 Arize AI 创始人 Aparna Dhinakaran 在 X 上发布的长文 Context Management in Agent Harnesses(2026 年 4 月 26 日)。原文从源码层面比较了 Pi、OpenClaw、Claude Code、Letta 四个 Agent 框架在上下文管理上的设计选择。

译者注:原文发出后,Letta 团队的 @sarahwooders@just_cameron 在评论区指出文中关于 Letta 的若干描述并不准确 - 文章对 Letta 的分析是从 3 月底开始的,期间 Letta 这边变化很快:文里详述的 Filesystem(vector store + open_files / grep_files / semantic_search_files)已被官方文档标记为弃用;Letta Code 早在 Claude Code 之前就同时支持隔离与 fork 两种子 Agent 模式(且支持把任意带状态的专用 Agent 当作子 Agent 调用);压缩策略除了滑动窗口之外还有 compact all 模式;另外还有 Dream agents(后台重写 prompt / skill / 外部记忆)、消息默认存储 + 混合检索(含内建 search agent)、超大工具结果被截断并存为可访问文件等机制没有覆盖到。Aparna 已在评论区回复会更新原文。下文 Letta 相关的段落请结合这些指正一起读。

Context Management in Agent Harnesses

每一个 Agent 框架最后都会撞到同一道墙:上下文窗口太小,装不下模型可能想记住的所有东西。会话越拉越长,文件读取越积越厚,子 Agent 不断 fork,工具输出层层堆叠 - 框架必须决定,什么留在工作集里,什么压缩,什么之后再去取。

过去两年,我们一直在打磨 Arize 自家的产品级 Agent - Alyx,几乎每一种形式的"上下文不够用"都踩过:会话长到模型忘了任务在干嘛,文件读取里一半窗口塞满了样板代码,工具结果把真正的对话挤到了角落。

所以现在真正重要的问题已经不是"prompt 里放什么",而是"框架如何长时间地管理上下文"。最好的系统不会把上下文窗口当成一条被动的 transcript 缓冲区,而是主动经营它:高价值的状态留在近端,按需翻页拉取数据,建索引去找需要的东西(grep 就是干这个的),并以一种暗示"还有更多可访问"的方式做截断。

Managing Context Window

Pi、OpenClaw、Claude Code、Letta 在这件事上做了不同的选择,但它们正在收敛到一个相似的底层模式。上下文不再是 transcript 里恰好能塞下的那一段,而是系统必须主动管理的对象。真正的设计问题是:这部分管理工作,多少由框架来做,多少留给模型自己来做。

押注:相信模型能管好自己的上下文

每一个上下文管理上的决策,背后都是对模型行为的某种假设。核心问题是 - 框架要主动卡死上下文用量,还是相信模型会自己把这部分预算用对。

Large File Context Management

拿文件读取来看就最具体了。当模型要读一个塞不进上下文的大文件时,必须有人决定保留哪部分。四个框架都支持 offset 和 limit 来分页。

Pi(pi-mono)

Pi 读文件时设了硬上限 - 2,000 行或 50KB,先到先生效,哪怕模型没指定切片范围。内容从尾部截断,工具输出后会附加一句明确的"继续读取"提示:[Showing lines 1-2000 of 50000. Use offset=2001 to continue.]。工具描述里也强化了这一点:"输出截断到 2000 行或 50KB。大文件请用 offset/limit。"

Pi 的思路是框架先行 - 框架先保护你,然后再教模型去翻页。

OpenClaw

OpenClaw 直接继承了 Pi 的读文件工具,连 2K 行 / 50KB 的截断逻辑也照搬过来。在普通文件读取上行为完全一致。在这之上,它叠了额外的限额,专门管"启动文件",也就是会话开始时一次性加载的上下文文件 - 单文件 12,000 字符,总量 60,000 字符。当某个启动文件超过预算时,会按 75% 头 + 25% 尾切:你看到开头和结尾,中间被砍掉。

工具结果有独立预算 - 16,000 字符上下文窗口的 30%,取小者。当结尾看起来"重要"时(错误信息、JSON 闭合括号、summary 这类关键字),会切到头+尾模式;不然就只保留开头。

OpenClaw 走的是纵深防御 - 第一层 Pi 的截断,第二层启动文件的额外限额,第三层工具结果的预算。

Claude Code

Claude Code 在文件读取上叠了两层防线。第一道闸是 256KB 字节上限,在文件被打开之前就通过 stat 调用检查 - 如果超了,读取直接被拒绝,错误信息里会指引模型改用 offset/limitgrep。第二道闸在读取之后跑:输出按 25,000 token 预算做 token 计数,把那些字节数没超但 token 密度极高的文件拦下来。这两个上限都通过 GrowthBook feature flag 远程可调,Anthropic 不需要发新版本就能动它们。

哪怕文件没超限,工具默认也只返回前 2,000 行,单行超过 2,000 字符的部分会被截掉。模型必须显式带上 offset 和 limit 参数才能拿更多。

工具描述本身就是一段完整的多段提示词,把分页讲清楚,提到大小限制,覆盖图像 / PDF / notebook 的支持,并且鼓励对多个文件并行读取。offset 和 limit 参数有自己的描述,告诉模型这是为"一次读不完的大文件"准备的。还有一段条件性指令,会根据 feature flag 把 256KB 的限制直接铺到 prompt 里。

文件去重机制也值得一提。如果模型在同一个范围里重复读同一个文件,且时间没变,Claude Code 会返回一个占位 stub 而不是完整内容,避免在上下文里消耗重复 token。

Claude Code 的思路是框架先行 + 远程可调 - 读取前的字节闸、读取后的 token 闸、行数和行长默认值、可执行的错误信息、丰富的工具 prompt、读取去重,加上让 Anthropic 在服务端就能动所有参数的 feature flag。

Letta

Letta 走了一条根本不一样的路。每个上传的文件都会被解析、切块、向量化进 vector store,所以 Agent 同时拥有精确搜索和语义搜索两种能力。这给了 Agent 三个文件工具 - open_files 直接查看(读原文)、grep_files 做精确模式匹配(也读原文)、semantic_search_files 在嵌入后的片段里做语义检索。

当一个文件在 Agent 上下文里"打开"时,可见内容会被截断到一个按模型上下文窗口分档的字符上限:8K 上下文档 5,000 字符,32K 档 15,000,128K 档 25,000,200K+ 档 40,000。同时打开的文件数也跟着分档伸缩 - 小模型 3 个,超大模型最多 15 个,默认值是 5。超出限制时用 LRU 策略把最少访问的文件挤出去。

Letta 的思路是记忆先行 - 文件同时以原文和向量切块的形式存在,上下文窗口里只展示一份被管理的视图,模型通过工具去访问更多。

真正的工程难点:会话裁剪

会话越长,每个框架都得决定保留什么、丢掉什么。这恰恰是设计差异最显著的地方,因为压缩策略直接决定了一个长生命周期的 Agent 能保持连贯,还是会缓慢退化。

Session Pruning

Pi(pi-mono)

Pi 用的是 compaction - 由 token 阈值触发的 LLM 摘要。

  • 触发条件:估算的上下文 token 超过 contextWindow - reserveTokens(reserve 默认 16,384 token
  • 保留什么:从对话末尾向前走,保留最近 约 20,000 token 的消息(keepRecentTokens)
  • 摘要什么:更早的内容全部交给 LLM 做摘要
  • 摘要去哪:变成一条合成的用户消息,拼接到保留的尾部前面
  • 工具调用安全:永远不会切在一个孤立的 tool result 上,会沿着边界走,确保 tool-call / tool-result 成对完整

OpenClaw

OpenClaw 在 Pi 的 compaction 之上跑两套不同的上下文管理机制。

  • 触发条件:历史消息超过上下文窗口的 50%(maxHistoryShare,默认 0.5)
  • 保留什么:历史按 token 等量切成多个 chunk,最旧的一块被丢弃,其余保留,并修复 tool-call / result 配对
  • 摘要什么:被丢弃的内容走一个分阶段的多轮 LLM 摘要,最后还有一步 merge
  • 摘要去哪:和 Pi 一样 - 合成消息拼接到保留尾部前
  • 工具调用安全repairToolUseResultPairing 会修复 chunk 丢弃后产生的孤立 tool result,splitMessagesByTokenShare 避免在 tool-call / result 对内切开
  • 压缩前 flush:一次静默的 agentic 轮次让 Agent 在历史消失之前把状态持久化到 memory 文件
  • 第二层:对工具结果做非破坏性的内存内裁剪(先软裁、再硬清),缓存 TTL 5 分钟,在保住持久会话的前提下为当前请求腾出上下文

Claude Code

Claude Code 的上下文管理走两路 - 查询前的优化 + LLM 驱动的 compaction。

  • 触发条件:估算 token 超过"有效上下文窗口减去 13,000 token 缓冲"(200K 上下文的模型大约在 167K token 处触发)
  • 摘要什么:整段对话被发给模型,配一份 9 段结构化 prompt - primary request、关键技术概念、文件与代码、错误与修复、问题求解、所有用户消息、待办任务、当前工作,以及可选的 next step
  • 摘要去哪:变成一条用户消息,告诉模型"这是从一个因为上下文耗尽而中断的会话续上来的"
  • 压缩后恢复:压缩之后会把最近读过的最多 5 个文件重新塞回上下文,受 token 预算约束
  • 摘要器安全:模型分别输出一个 analysis 草稿块和一个 final summary 块。草稿在 summary 进入上下文前被剥离 - 既提升了质量,又不让结果膨胀
  • prompt-too-long 兜底:如果 compaction 调用本身撞上下文限制,会用一个确定性的 head-drop 把最旧的若干 API 轮次组掉(20% 的组数,或刚好够把 token 缺口补上)

查询前优化(每次 API 调用都跑,跟有没有上下文压力无关):在每次模型调用之前,Claude Code 走一个 pipeline,专门管工具结果,对话文本不动。超大的工具结果被持久化到磁盘,替换成 2KB 预览,单工具上限 50,000 字符,单消息总量上限 200,000 字符 - 所以一个 60KB 的 grep 结果在新会话的第一轮就被卸到磁盘上。

Letta

Letta 用多种 compaction 策略管上下文,主路径溢出时还有一个两阶段摘要器兜底。

  • 触发条件:估算上下文用量超过窗口的 90%
  • 滑动窗口驱逐:起点是 30% 的消息(不是 10%),每次迭代再加 10%,直到 token 用量降到目标以下。保留最近的,赶走最旧的
  • self-compact 模式:用 Agent 自己的模型做摘要 - 不用单独配置摘要器,也不会多花一份调用费用
  • 摘要器溢出的两阶段兜底:先把工具返回值钳到 5,000 字符重试;如果还是溢出,对 transcript 做中段截断,保留 30% 头 + 30% 尾,砍掉中间
  • 告警阈值:单独设了一道 75% 内存告警,比 90% 压缩触发更早

子 Agent 的上下文管理

在我们看的这些框架里,子 Agent 通常是和父会话隔离的。这里没有任何一个例子会把父会话的完整对话历史塞给子 Agent。问题在于,子 Agent 继承哪些工作区上下文。

Subagent

Pi 为每个被委派的任务起一个新进程,配一个内存里的会话。子进程拿到的唯一一条用户消息就是任务字符串本身。父对话历史不传。

OpenClaw 默认给子 Agent 一个全新的隔离会话 - 不带父 transcript。但它有一个 fork 模式,会把父 transcript 复制进子 Agent,限制是只对同类型 Agent 之间的 spawn 生效。工作区上下文被过滤成一个最小白名单(AGENTS.md、TOOLS.md、SOUL.md)。

Claude Code 有两条路。默认的 typed-agent 路径起的是一个空白对话 - 委派出去的 prompt 就是唯一的用户消息,没有父历史。一个更新的 fork 路径会把父消息历史完整传给子 Agent,目的是让 prompt cache 能复用,外加一条合成的 assistant 消息和占位的 tool result。工具会以子 worker 自己的权限模式重新构建;async Agent 拿到一份显式白名单(Read、Grep、Glob、Shell、Edit、Write、WebSearch 等)。Agent 定义里引用的 Skill 会被预先加载 - skill 的完整内容作为用户消息注入到初始对话里,不是按需加载。

Letta 在普通工具执行里压根不 fork - 工具就在主 Agent 循环里跑。历史上下文通过专门的搜索工具去访问 - conversation search 拿 recall memory,archival memory search 查嵌入存储。

设计正在收敛的地方

把这四个代码库摆在一起,最意外的不是它们多么不同,而是它们多么一致。

四个框架都对文件读取设了硬上限。四个都支持 offset / limit 分页。四个都给工具结果上限。四个都隔离子 Agent 会话。四个都跑 token 阈值触发的 LLM compaction。四个都估算上下文用量、检测压力。这些不是巧合,是同一个工程问题的收敛解 - 一个固定大小的工作集,要让人感觉无限。

收敛不止停留在"功能相同"。具体的设计选择都在押同一个韵脚。Pi 和 OpenClaw 都在文件读取里做尾截断 + 续读提示。Claude Code 和 OpenClaw 都把超大工具结果持久化到磁盘。Pi、OpenClaw、Claude Code 都在 compaction 里强制 tool-call / result 边界安全。四个里面有三个支持把父 transcript fork 进子 Agent。这些框架在独立地走向同一组答案。

而且这些模式不只属于编码 Agent。Arize 自家的 Alyx 助手 - 一个为数据探索而非代码编辑设计的产品 - 独立走到了同一套设计上。Alyx 给工具结果设了 10,000 token 预算,用二分搜索去找能塞下的最大数据切片。它对幂等的工具调用做去重 - 把对话历史里的重复预览裁掉,只保留最新一次。它把大 JSON 拆成两份 - 一份压缩过的、给 LLM 看的预览,一份完整的、放在服务端的副本,模型可以通过 jq 钻进去 - 这正是 Pi、OpenClaw、Claude Code 在文件读取里用的"把超大结果存在上下文外"的同一种模式。它对长 cell 值做头+尾截断,并附带回指完整内容的引用。它用 char/4 启发式估算 token 压力,会话过 50,000 token 时强制做一次 checkpoint - 模型在历史被裁剪之前自己写一份状态摘要 - 把 Claude Code 的确定性触发器和 OpenClaw 的"压缩前 flush"组合在了一起。它的子 Agent 用的是四个框架都在用的隔离模式。一个为完全不同领域而生的产品,收敛到了同一份上下文管理 playbook。

From 50 years of computing to agent harnesses

50 年的计算机历史告诉我们一件事 - 最好的内存管理,是程序压根不去想它的那种。寄存器、cache line、页表、swap,每一层都由系统管,对上一层不可见。程序只管跑。

Agent 框架正在朝同一个方向走。目标不是把所有东西都摊给模型看,而是 - 在合适的时刻给它合适的工作集,让它自己动态地决定如何管理上下文。

相关文章

2026年3月22日

Claude Code vs Cursor:选哪个?

两个最热门的 AI 编程工具,一个是终端原生,一个是 IDE 魔改。适合的场景完全不同,选错了比不用还难受。

Claude CodeCursorAI

2026年2月9日

给 Agent 加定时任务?七个你一定会踩的坑

从 OpenClaw 一次关掉 60+ cron issues 的重构中,提炼出 Agent 定时任务系统的七个可靠性教训:亚秒精度陷阱、LLM 调用必须有超时、失败退避不能省、单次任务的死循环、投递上下文会过期、重复管道要合并、以及 —— 不是所有模型都会按你的 schema 传参。

AIAgentCron

合作伙伴

CompeteMap — 英国及爱尔兰学生竞赛一站式搜索

数学、编程、科学、写作等各类竞赛信息汇总,支持按年龄和科目筛选,再也不错过报名截止日。

准备开始了吗?

先简单说明目标,我会给出最合适的沟通方式。