Manus 上下文工程 可视化阅读
阅读原文
Yichao ‘Peak’ Ji · Manus 联合创始人 · 2025 年 7 月 18 日

Manus 的上下文工程实战 · 6 条来自重写 4 次 Agent 框架的经验

不训模型、押注上下文工程,并不是一句口号。Manus 把 Agent 框架重写了 4 次, 每一次都是因为发现了「上下文该怎么塑形」更好的答案。这篇文章是他们承认的局部最优。

作者:Yichao ‘Peak’ Ji,Manus 联合创始人 发布于 2025 年 7 月 18 日 约 10 分钟阅读
TL;DR · 核心想法

如果模型进步是涨潮,那 Manus 想做船,不做海底的柱子。 所以他们不训模型,而是反复打磨上下文。这条路走下来,沉淀出 6 条围绕缓存命中、注意力分配、外部记忆、错误证据的经验。

这些经验都不优雅 —— 作者称这套手工调参为「Stochastic Graduate Descent」。 但每一条都对应一个真实失败:缓存被破坏、动作空间膨胀、上下文被截断、注意力跑偏、错误被擦掉、套路化输出。

100:1
Manus 平均的 input / output token 比 —— 缓存价值是 chatbot 场景的几十倍
10×
Claude Sonnet 上 cached vs uncached 输入 token 的价格差
~50
一个典型任务里 Manus 平均会调用的工具次数
4
Manus 重写 Agent 框架的次数 —— 每次都因为找到更好的上下文塑形方法
阅读方式建议:第 1 节是动机,剩下的 6 节是 6 条相对独立的经验,可以按顺序读,也可以挑你正在踩的坑跳读。 每一条都对应一个具体失败模式 + 一个反直觉的解法。
01 · 起点

不训模型,押注上下文:从 BERT 时代学到的痛苦教训

作者上一家创业用过老路:从零训模型做 open IE 和语义搜索。GPT-3 一出,自家模型一夜失效。 这次创业一开始就要决定:还是训一个端到端 agentic 模型,还是赌前沿模型的 in-context learning?

这条选择题不是哲学问题,而是节奏问题。BERT 时代,每加一个新任务都要 fine-tune + 评估,几周一个迭代周期。 对一个 pre-PMF 的产品,这种反馈循环是致命的 —— 等你训完,需求和模型都已经变了。

作者的判断是:与其和底层模型耦合,不如让产品和模型解耦。GPT-3 / Flan-T5 这一代模型的真正意义, 并不只是更强,而是开启了 in-context learning 这条新路 —— 从此可以靠塑造上下文,而不是改权重,去让模型完成新任务。

Decision · 一开始就拍板

把 Manus 做成船,不做海底的柱子

模型进步是涨潮。如果产品是 pillar 钉死在某个具体模型上,潮水来了反而被拍碎。 能跟着潮水起 —— 底层模型变强,产品立刻一起变强,不用重训。

→ 选择 context engineering,而不是 train end-to-end
Reality · 真做起来不优雅

「随机研究生下降」 —— Stochastic Graduate Descent

上下文工程是实验科学。Manus 的 agent 框架被重写了 4 次,每次都是因为发现了一个更好的塑形方法。 这套手工架构搜索 + Prompt 调试 + 经验性瞎猜的过程,团队戏称为 SGD。

→ 不优雅,但能在小时级出迭代,而不是周级
Author’s framing 模型可能越来越快、越来越强、越来越便宜,但再多原始能力也替代不了对记忆、环境与反馈的精心设计。 上下文怎么塑形,最终决定了你的 Agent 跑多快、容错多强、能扩到多大。
02 · 围绕 KV-Cache 设计

KV-cache 命中率,是生产 Agent 最该盯的单一指标

Agent 的输入会随每一步增长,但每一步的输出(一个结构化函数调用)几乎不变长。 这让 prefill / decode 的比例严重失衡 —— 缓存命中率因此对延迟和成本影响巨大。

典型 Agent 的循环是这样:用户输入 → 模型选动作 → 执行 → 把动作和 observation 追加到上下文 → 进入下一轮。 在 Manus 上,每一轮平均的 输入 / 输出 token 比是 100:1。这意味着 prefill 才是真正的成本中心, 而 prefill 阶段的 KV-cache 几乎是免费午餐 —— 只要你的前缀稳定

前缀稳定本身又对设计提出了非常具体的约束:不能在系统 Prompt 顶部放精确到秒的时间戳; 不能让 JSON 序列化产生不稳定的 key 顺序;不能修改之前的 action / observation。 这三件事单看都很小,合起来就是「每一处不稳定都会从那 token 开始作废所有缓存」。

Manus 上的 prefill / decode 极度失衡

同一个任务里:上下文每步都在变长,但每步的结构化函数调用输出几乎是常数。
INPUT
≈ 100 tokens / 步 · 持续增长
OUTPUT
1 个结构化函数调用 · 几乎是常数
读法:chatbot 场景里 input/output 接近 1:1,缓存收益有限;Agent 是 100:1,缓存命中率几乎决定单位成本。
CACHED INPUT
$0.30/MTok
命中前缀缓存的输入 token —— Claude Sonnet 报价。
UNCACHED INPUT
$3.00/MTok
未命中缓存的输入 token —— 同样的内容,价格 10×。
三条具体做法
PRACTICE 01

保持 Prompt 前缀稳定

由于 LLM 是自回归的,哪怕前缀里差一个 token,从那一处往后所有缓存都会作废。最常见的踩坑是把精确到秒的时间戳放在系统 Prompt 顶部。

Now: 2025-07-18 14:23:08
Now: 2025-07-18 14:00:00
PRACTICE 02

上下文 append-only

不要回去改之前的 action 或 observation;并且序列化必须是确定性的。很多语言/库的 JSON 序列化不保证 key 顺序,会悄悄破坏缓存。

{"b": 2, "a": 1}
{"a": 1, "b": 2}(固定 key 顺序)
PRACTICE 03

必要时显式标记 cache 断点

有些 API(如 Claude)不会自动判断「和上次共享前缀」,需要你手动放一个 marker:「从开头到这里的 KV,请帮我存起来」。底线是放在系统 Prompt 末尾 —— 因为它最大、最稳定、被所有请求共享。放再往后会把易变内容混进缓存,命中率反而降;还要顾及 TTL(Claude 默认 5 min),别把断点放在很久才被命中一次的位置。

[system prompt] ‖ <cache_break> ‖ [history…]
[system prompt] ‖ [history] ‖ <break> ‖ [user]
03 · 屏蔽,而不是移除

动作空间膨胀时,用状态机 + logits mask,不要动态加减工具

随着 Agent 能力变多,工具数量会爆炸,尤其是有了 MCP 之后,用户会不可避免地塞进几百个奇怪的工具。 直觉上想做「按需加载工具」,但 Manus 的实验给出了一个反直觉的结论。

动态增删工具有两个致命问题:第一,工具定义通常排在上下文最前面,一改就让后续所有 action / observation 的 KV-cache 失效; 第二,之前的轨迹里如果引用了已经被移除的工具,没有 constrained decoding 的话,模型会产生 schema 违例或幻觉调用

Manus 的解法不是改工具集,而是改「能不能调用」。一个 context-aware 的状态机管理工具可用性, 在 decode 阶段直接对 token logits 做掩码,逻辑上「这一步只允许选 browser_* 开头的工具」。 这样工具定义不动,缓存不破,模型也不会被「之前提到过现在没有」的工具搞晕。

三种函数调用模式 · 用 response prefill 来约束

模式语义实现方式(以 Hermes 格式为例)
Auto 模型可以选择调用工具,也可以不调用 只 prefill 回复前缀:<|im_start|>assistant
Required 必须调用工具,但不限制是哪个 prefill 到 tool call token:<|im_start|>assistant <tool_call>
Specified 必须从某个特定子集里选工具 prefill 到函数名前缀:{"name": "browser_
命名规范让「子集屏蔽」零成本
browser_open browser_click browser_scroll shell_run shell_kill file_read file_write
browser_open browser_click browser_scroll shell_run shell_kill file_read file_write
所有浏览器工具都以 browser_ 开头,所有命令行工具都以 shell_ 开头。 要让 Agent 这一步只能挑浏览器工具?只需 prefill 到 {"name": "browser_, 剩下的 token logits 自动被命名前缀框死,不需要为此实现一套有状态的 logits processor
Why this matters 用户提供新输入时,Manus 必须立即回复,而不是又去调工具。这种「这一步该不该调工具、能调哪些工具」的策略, 用 logits mask 做远比修改工具列表更稳 —— 它不破坏缓存,也不会让模型看到自己调用过的、现在却不存在的工具。
04 · 把文件系统当作上下文

128K context 不够用,不是因为太短,而是「不可压缩」是错的前提

即使有 128K 窗口,长任务里也常常不够用,并且会暴露三个问题:观察值过大、超长 context 性能下降、即便缓存也仍要为 prefill 付费。 激进截断或压缩会丢信息 —— 你没法预测 10 步后哪条 observation 会变关键。

Manus 的解法是把文件系统当作终极上下文:尺寸无限、天然持久、Agent 自己可以读写。 模型把文件系统当成结构化的外部记忆 —— 不再纠结要不要把整篇网页留在窗口里,而是按需写下、按需读回。

关键约束是:压缩必须可恢复。网页正文可以从上下文里删掉,只要 URL 还在;文档内容可以省略, 只要沙箱里的路径还在。这种「外部存档 + 句柄保留」的策略,让上下文长度变小却不至于让信息真的消失。

作者顺手抛出一个想法:如果 SSM(State Space Model)能学会把长程状态外化到文件系统, 它的速度优势可能让它成为 Neural Turing Machine 真正的继承者

CONTEXT WINDOW

把所有东西塞进上下文

  • 观察值动辄几万 token,一两次网页/PDF 就能撑爆 128K
  • 哪怕窗口够长,超过某个阈值后模型表现会退化
  • Prefix cache 也救不了 prefill 的传输和计算成本
  • 主动截断 = 不可逆地丢信息,无法预测哪条会变重要
FILE SYSTEM AS MEMORY

把上下文卸载到文件,留下句柄

  • 容量不限持久、Agent 自己 read/write
  • 只在需要的时候把内容拉回上下文
  • 压缩要求可恢复:删正文,留 URL / 文件路径
  • 对长程任务等价于 externalized memory
Step 1 · 看到长内容
抓取 https://... 网页 / 大 PDF,原始正文几万 token。
Step 2 · 落地存档
内容写到 /sandbox/page-12.md,正文从上下文中移除。
Step 3 · 句柄留在上下文
只保留 URL + 文件路径。多步以后还能 read_file 把它取回来。
05 · 通过复述操纵注意力

Manus 写 todo.md 不是装可爱 —— 是让目标一直挨着上下文末尾

一个典型 Manus 任务平均 50 次工具调用。这是一个很长的循环,长到模型容易忘记最初的目标,或在中间走偏。 最便宜的对抗手段是「在输出末尾不断复述目标」 —— 让全局 plan 一直待在最近的注意力跨度里。

长上下文的两个老问题是 lost-in-the-middle 和目标漂移。 每写一遍 todo.md,等于把任务目标重新挪到 context 的最末端 —— 模型最近的注意力会更倾向最末尾的 token。 这是用自然语言给自己加注意力偏置,不需要改任何架构。

这件事的好处是几乎免费:todo.md 是一个 plain text 文件,写它的成本很低,但它会让模型的下一步选择天然对齐到当前任务

注意力分布与 todo.md 复述

开始 第 50 次工具调用 →
init
act
obs
todo
act
obs
todo
act
obs
todo
act
obs
注意力衰减区(lost-in-the-middle) 近期注意力高地
/sandbox/todo.md · v0.7
[x]读取用户提供的 PDF 并抽取关键章节
[x]把每章总结成 200 字
[x]检索 3 篇相关的对比论文
[ ]调用 chart 工具画指标曲线
[ ]把 report.md 转成 PDF 交付
Mechanism 复述不是给人看的 —— 是给模型自己看的。每写一遍,就把全局目标挤到最近的注意力跨度里,自然降低跑偏概率
06 · 错的东西也要留在上下文里

擦掉失败 = 抹去证据。模型从此再也学不会避开同一个坑

Agent 会犯错。这不是 bug,是常态:模型会幻觉、环境会报错、外部工具会抽风。 最常见的本能是「把这次失败擦掉、重置状态、调高 temperature 再试一次」 —— 这看起来更安全,但代价是模型永远学不到东西。

让失败留在 context 里,是 Manus 实测中最有效的简单做法之一。当模型看到一次失败的 action 和它产生的 observation / stack trace, 它会隐式更新自己的内部信念:「这条路走不通」。下一次 sample 类似动作的概率会自然下降。

作者的判断更进一步:错误恢复本身就是真正 agentic 行为的最好指示器之一。 但这件事在学术 benchmark 里几乎没怎么被衡量 —— 那些 benchmark 大多在「理想条件下能不能完成」上打转。

PATTERN A · 擦掉失败

看起来更干净,但模型再也不会变好

attempt browser_click(button='export') [trace cleaned · retry with higher temp] attempt browser_click(button='export') [trace cleaned · retry with higher temp] attempt browser_click(button='export')
没有失败证据,模型的先验不变。同一类错误会在第 4、第 5、第 N 次重新发生, 直到上下文耗尽或人工介入。
PATTERN B · 留下失败

难看,但下次不会再走这条死路

attempt browser_click(button='export') ElementNotInteractable: button is hidden try browser_scroll(to='bottom') browser_click(button='export') · success
模型看到 stack trace,下一次类似情形会先想到 scroll 再 click。 错误恢复本身就是 agentic 行为最重要的特征。
07 · 别给自己 few-shot 进套子里

语言模型很会模仿。一旦上下文里有套路,它就会一直套下去

Few-shot 是个好工具,但在 Agent 系统里会反咬一口:上下文里全是相似的「动作 + 观察」对,模型会继续按那个模式走, 哪怕已经不再合适。批量重复任务(比如审 20 份简历)最容易暴露这个问题。

模型不是在「思考下一步」 —— 它是在延续上下文里的模式。 如果你让它审 20 份简历,前面 5 份都用同一种格式输出,它会沿着这个节奏一直走, 即便后面那几份并不适合用同样的方法处理。结果是漂移、过度泛化,甚至幻觉。

Manus 的对策是有意制造结构化的多样性:不同的序列化模板、措辞替换、顺序或格式上的轻噪声。 这不是为了好看,而是为了打破节奏,让模型重新认真看当前这条 input,而不是惯性输出。

Few-shot 进了套路

前 4 份简历都用同一句式做出 PASS / REJECT 判断 —— 第 5 份开始模型不再认真读,直接套句式。

resume_01.pdf — «Strong Python background»PASS
resume_02.pdf — «Strong Python background»PASS
resume_03.pdf — «Strong Python background»PASS
resume_04.pdf — «Strong Python background»PASS
resume_05.pdf — 没读,直接套PASS
症状:判断逐渐失真,模型不再被 input 的个体差异驱动。

引入受控的多样性

每份简历换一种总结模板、换一种表达,同一件事用不同方式说,让模式无法稳定形成。

r1 · 重点列举 Python 项目经验PASS
r2 · 用 bullet 列亮点 / 风险点REJECT
r3 · 一句话摘要 + 1 个问题PASS
r4 · 优劣势对照表格PASS
r5 · JSON 摘要字段REJECT
原则:在动作和 observation 里引入少量结构化扰动,足以让模型重新关注个体输入。
Heuristic 越是上下文统一,Agent 越是脆弱。在重复决策场景里,「制造多样性」和「保持稳定 KV-cache 前缀」并不矛盾 —— 变化的是 action / observation,不变的是系统 Prompt 和工具定义。
08 · 收尾

Agent 的未来,是一段段精心设计的上下文

这 6 条经验不是普世真理,而是 Manus 在反复重写、走死路、面向千万级真实用户测试中沉淀下来的局部最优。 你不一定要照搬 —— 但如果它们能让你少踩一个坑,这篇文章就值了。

作者在末尾承认:上下文工程仍然是一门年轻的实验科学,没有定理。 但对一个生产中的 Agent 系统来说,它已经是必修课

模型可能会更快、更强、更便宜,但能力本身替代不了「记忆、环境与反馈」的设计。 上下文怎么塑形,决定了你的 Agent 跑多快、容错多强、扩到多大

01 · 围绕缓存命中
把 prefix 当成圣物,把 append-only 当成戒律
时间戳、工具列表的微小变动都会击穿缓存,10× 成本差距就在一个 token 之间。 再加上 deterministic 序列化和必要的 cache 断点。
02 · 形状不动,能力可调
用状态机 + logits mask 控制工具空间
不要动态加减工具。让 browser_* / shell_* 这样的命名前缀做天然分组, 靠 prefill 在 decode 阶段切换可用集合。
03 · 上下文不是窗口
文件系统才是真正的长期记忆
压缩必须可恢复。把内容卸载到文件,只在上下文里留 URL / 路径句柄,需要时再读回。
04 · 注意力要被引导
复述目标,用 todo.md 把目标推到末尾
长循环里 lost-in-the-middle 必然发生。 每隔几步把任务计划重写一次,用自然语言给自己加注意力偏置
05 · 错误是数据
不要擦掉失败 trace,让它出现在下一次 prompt 里
失败的 stack trace 会改变模型的内部先验。错误恢复是最能区分真假 Agent 的指标, 却在大多数 benchmark 里被回避。
06 · 警惕模式
在 action / observation 里加入受控的多样性
上下文越统一,Agent 越脆弱。同一件事换种说法,是最便宜的去套路化手段。
Agentic future will be built one context at a time. Engineer them well.
Agent 的未来会一段一段上下文地堆出来。把每一段都好好设计。
— Yichao ‘Peak’ Ji · Manus 联合创始人