不训模型、押注上下文工程,并不是一句口号。Manus 把 Agent 框架重写了 4 次, 每一次都是因为发现了「上下文该怎么塑形」更好的答案。这篇文章是他们承认的局部最优。
如果模型进步是涨潮,那 Manus 想做船,不做海底的柱子。 所以他们不训模型,而是反复打磨上下文。这条路走下来,沉淀出 6 条围绕缓存命中、注意力分配、外部记忆、错误证据的经验。
这些经验都不优雅 —— 作者称这套手工调参为「Stochastic Graduate Descent」。 但每一条都对应一个真实失败:缓存被破坏、动作空间膨胀、上下文被截断、注意力跑偏、错误被擦掉、套路化输出。
作者上一家创业用过老路:从零训模型做 open IE 和语义搜索。GPT-3 一出,自家模型一夜失效。 这次创业一开始就要决定:还是训一个端到端 agentic 模型,还是赌前沿模型的 in-context learning?
这条选择题不是哲学问题,而是节奏问题。BERT 时代,每加一个新任务都要 fine-tune + 评估,几周一个迭代周期。 对一个 pre-PMF 的产品,这种反馈循环是致命的 —— 等你训完,需求和模型都已经变了。
作者的判断是:与其和底层模型耦合,不如让产品和模型解耦。GPT-3 / Flan-T5 这一代模型的真正意义, 并不只是更强,而是开启了 in-context learning 这条新路 —— 从此可以靠塑造上下文,而不是改权重,去让模型完成新任务。
模型进步是涨潮。如果产品是 pillar 钉死在某个具体模型上,潮水来了反而被拍碎。 船能跟着潮水起 —— 底层模型变强,产品立刻一起变强,不用重训。
上下文工程是实验科学。Manus 的 agent 框架被重写了 4 次,每次都是因为发现了一个更好的塑形方法。 这套手工架构搜索 + Prompt 调试 + 经验性瞎猜的过程,团队戏称为 SGD。
Agent 的输入会随每一步增长,但每一步的输出(一个结构化函数调用)几乎不变长。 这让 prefill / decode 的比例严重失衡 —— 缓存命中率因此对延迟和成本影响巨大。
典型 Agent 的循环是这样:用户输入 → 模型选动作 → 执行 → 把动作和 observation 追加到上下文 → 进入下一轮。 在 Manus 上,每一轮平均的 输入 / 输出 token 比是 100:1。这意味着 prefill 才是真正的成本中心, 而 prefill 阶段的 KV-cache 几乎是免费午餐 —— 只要你的前缀稳定。
前缀稳定本身又对设计提出了非常具体的约束:不能在系统 Prompt 顶部放精确到秒的时间戳; 不能让 JSON 序列化产生不稳定的 key 顺序;不能修改之前的 action / observation。 这三件事单看都很小,合起来就是「每一处不稳定都会从那 token 开始作废所有缓存」。
由于 LLM 是自回归的,哪怕前缀里差一个 token,从那一处往后所有缓存都会作废。最常见的踩坑是把精确到秒的时间戳放在系统 Prompt 顶部。
不要回去改之前的 action 或 observation;并且序列化必须是确定性的。很多语言/库的 JSON 序列化不保证 key 顺序,会悄悄破坏缓存。
有些 API(如 Claude)不会自动判断「和上次共享前缀」,需要你手动放一个 marker:「从开头到这里的 KV,请帮我存起来」。底线是放在系统 Prompt 末尾 —— 因为它最大、最稳定、被所有请求共享。放再往后会把易变内容混进缓存,命中率反而降;还要顾及 TTL(Claude 默认 5 min),别把断点放在很久才被命中一次的位置。
随着 Agent 能力变多,工具数量会爆炸,尤其是有了 MCP 之后,用户会不可避免地塞进几百个奇怪的工具。 直觉上想做「按需加载工具」,但 Manus 的实验给出了一个反直觉的结论。
动态增删工具有两个致命问题:第一,工具定义通常排在上下文最前面,一改就让后续所有 action / observation 的 KV-cache 失效; 第二,之前的轨迹里如果引用了已经被移除的工具,没有 constrained decoding 的话,模型会产生 schema 违例或幻觉调用。
Manus 的解法不是改工具集,而是改「能不能调用」。一个 context-aware 的状态机管理工具可用性, 在 decode 阶段直接对 token logits 做掩码,逻辑上「这一步只允许选 browser_* 开头的工具」。 这样工具定义不动,缓存不破,模型也不会被「之前提到过现在没有」的工具搞晕。
| 模式 | 语义 | 实现方式(以 Hermes 格式为例) |
|---|---|---|
| Auto | 模型可以选择调用工具,也可以不调用 | 只 prefill 回复前缀:<|im_start|>assistant |
| Required | 必须调用工具,但不限制是哪个 | prefill 到 tool call token:<|im_start|>assistant <tool_call> |
| Specified | 必须从某个特定子集里选工具 | prefill 到函数名前缀:{"name": "browser_ |
{"name": "browser_,
剩下的 token logits 自动被命名前缀框死,不需要为此实现一套有状态的 logits processor。
即使有 128K 窗口,长任务里也常常不够用,并且会暴露三个问题:观察值过大、超长 context 性能下降、即便缓存也仍要为 prefill 付费。 激进截断或压缩会丢信息 —— 你没法预测 10 步后哪条 observation 会变关键。
Manus 的解法是把文件系统当作终极上下文:尺寸无限、天然持久、Agent 自己可以读写。 模型把文件系统当成结构化的外部记忆 —— 不再纠结要不要把整篇网页留在窗口里,而是按需写下、按需读回。
关键约束是:压缩必须可恢复。网页正文可以从上下文里删掉,只要 URL 还在;文档内容可以省略, 只要沙箱里的路径还在。这种「外部存档 + 句柄保留」的策略,让上下文长度变小却不至于让信息真的消失。
作者顺手抛出一个想法:如果 SSM(State Space Model)能学会把长程状态外化到文件系统, 它的速度优势可能让它成为 Neural Turing Machine 真正的继承者。
https://... 网页 / 大 PDF,原始正文几万 token。
/sandbox/page-12.md,正文从上下文中移除。
一个典型 Manus 任务平均 50 次工具调用。这是一个很长的循环,长到模型容易忘记最初的目标,或在中间走偏。 最便宜的对抗手段是「在输出末尾不断复述目标」 —— 让全局 plan 一直待在最近的注意力跨度里。
长上下文的两个老问题是 lost-in-the-middle 和目标漂移。 每写一遍 todo.md,等于把任务目标重新挪到 context 的最末端 —— 模型最近的注意力会更倾向最末尾的 token。 这是用自然语言给自己加注意力偏置,不需要改任何架构。
这件事的好处是几乎免费:todo.md 是一个 plain text 文件,写它的成本很低,但它会让模型的下一步选择天然对齐到当前任务。
Agent 会犯错。这不是 bug,是常态:模型会幻觉、环境会报错、外部工具会抽风。 最常见的本能是「把这次失败擦掉、重置状态、调高 temperature 再试一次」 —— 这看起来更安全,但代价是模型永远学不到东西。
让失败留在 context 里,是 Manus 实测中最有效的简单做法之一。当模型看到一次失败的 action 和它产生的 observation / stack trace, 它会隐式更新自己的内部信念:「这条路走不通」。下一次 sample 类似动作的概率会自然下降。
作者的判断更进一步:错误恢复本身就是真正 agentic 行为的最好指示器之一。 但这件事在学术 benchmark 里几乎没怎么被衡量 —— 那些 benchmark 大多在「理想条件下能不能完成」上打转。
Few-shot 是个好工具,但在 Agent 系统里会反咬一口:上下文里全是相似的「动作 + 观察」对,模型会继续按那个模式走, 哪怕已经不再合适。批量重复任务(比如审 20 份简历)最容易暴露这个问题。
模型不是在「思考下一步」 —— 它是在延续上下文里的模式。 如果你让它审 20 份简历,前面 5 份都用同一种格式输出,它会沿着这个节奏一直走, 即便后面那几份并不适合用同样的方法处理。结果是漂移、过度泛化,甚至幻觉。
Manus 的对策是有意制造结构化的多样性:不同的序列化模板、措辞替换、顺序或格式上的轻噪声。 这不是为了好看,而是为了打破节奏,让模型重新认真看当前这条 input,而不是惯性输出。
前 4 份简历都用同一句式做出 PASS / REJECT 判断 —— 第 5 份开始模型不再认真读,直接套句式。
每份简历换一种总结模板、换一种表达,同一件事用不同方式说,让模式无法稳定形成。
这 6 条经验不是普世真理,而是 Manus 在反复重写、走死路、面向千万级真实用户测试中沉淀下来的局部最优。 你不一定要照搬 —— 但如果它们能让你少踩一个坑,这篇文章就值了。
作者在末尾承认:上下文工程仍然是一门年轻的实验科学,没有定理。 但对一个生产中的 Agent 系统来说,它已经是必修课。
模型可能会更快、更强、更便宜,但能力本身替代不了「记忆、环境与反馈」的设计。 上下文怎么塑形,决定了你的 Agent 跑多快、容错多强、扩到多大。