本地双语复刻页 · 非官方 · 仿 browser-use.com 原版风格重建 · 译文:claude-fable-5 · effort xhigh · 260723 · 英文原文
← All Posts / 目录
Engineering · January 16, 2026(260116)

The Bitter Lesson of Agent Frameworks

智能体框架的苦涩教训

All the value is in the RL'd model, not your 10,000 lines of abstractions.

所有的价值都在经过强化学习的模型里,而不在你那一万行抽象代码里。

Gregor Zunic · Co-founder, CTO(联合创始人 & CTO)

An agent is just a for-loop of messages. The only state an agent should have is: keep going until the model stops calling tools. You don't need an agent framework. You don't need anything else. It's just a for-loop of tool calls.

一个 agent 本质上就是一个消息的 for 循环。agent 唯一该有的状态是:一直跑,直到模型不再调用工具为止。你不需要 agent 框架,不需要任何别的东西。它就是一个工具调用的 for 循环。

Our first Browser Use agents had thousands of lines of abstractions. They worked — until we tried to change anything. Every experiment fought the framework. The agents weren't failing because the model was dumb. They were failing because we were.

我们最早的 Browser Use agent 有几千行抽象代码。它们能跑——直到我们想改任何东西。每一次实验都在和框架搏斗。agent 失败不是因为模型笨,而是因为我们笨。

Stay until the end where I show you how easy it is to build Claude Code.

看到最后,我会展示复刻一个 Claude Code 有多容易。

Why abstractions break learning / 为什么抽象会破坏学习

Here's the thing about abstractions: they freeze assumptions about how intelligence should work. RL breaks those assumptions.

抽象的问题在于:它把"智能应该怎么工作"的假设冻结了。而强化学习恰恰会打破这些假设。

Every time you add a "smart" wrapper around model behavior — planning modules, verification layers, output parsers — you're encoding what you think the model should do. But the model was trained on millions of examples. It has seen more patterns than you can anticipate. Your abstractions become constraints that prevent the model from using what it learned.

每当你给模型行为加一层"聪明的"包装——规划模块、验证层、输出解析器——你都是在把"你认为模型该怎么做"写死进去。但模型是在数百万个样例上训练出来的,它见过的模式比你能预想的多。你的抽象成了枷锁,阻止模型使用它学到的东西。

The Bitter Lesson from ML research is clear: general methods that leverage computation beat hand-crafted human knowledge every time. Agent frameworks are just the latest instance of this mistake.

机器学习研究中的"苦涩教训"很明确:利用算力的通用方法,每一次都胜过手工打造的人类知识。agent 框架不过是这个老错误的最新翻版。

解读 · "苦涩教训"的出处即 Sutton 2019 年的短文,本站有双语复刻。这一段是全文的理论地基。

99% of the work is in the model / 99% 的工作在模型里

Here's the thing: 99% of the work is done within the model itself. We don't need some highly abstract framework around it.

事实是:99% 的工作是模型自己完成的。我们不需要在它外面套一个高度抽象的框架。

Claude Code these days can just write AppleScript directly. It needs information from some obscure Spotify player? It doesn't need a Spotify computer-use tool. It just writes AppleScript on macOS. It has perfect context. It's well-trained on this.

如今的 Claude Code 可以直接写 AppleScript(macOS 的系统自动化脚本语言)。它需要从某个冷门的 Spotify 播放器拿信息?不需要什么"Spotify 专用工具",它直接在 macOS 上写 AppleScript。它有完美的上下文,它在这上面训练得很充分。

You don't anticipate every use case. The model already knows.

你无法预想所有用例。模型早就知道了。

The key insight / 关键洞察

Agent frameworks fail not because models are weak, but because their action spaces are incomplete.

agent 框架的失败,不是因为模型太弱,而是因为动作空间(action space)不完整。

Instead of defining every possible action up front, start from the opposite assumption: the model can do almost anything. Then restrict.

与其预先定义每一个可能的动作,不如从相反的假设出发:模型几乎什么都能做。然后再做限制。

Give the LLM as much freedom as possible, then vibe-restrict based on evals.

给大模型尽可能大的自由,然后依据评测(evals)去收窄(vibe-restrict)。

解读 · 具体做法:①起点全开零护栏 → ②建评测集(一批真实任务+明确的"什么算成功") → ③大量跑,记录失败与危险行为 → ④只对实测暴露的问题加限制,每条限制都有失败证据背书。

Why we threw everything away / 我们为什么推倒重来

The first version of Browser Use was a classic agent framework: a model wrapped in a complex message manager with lots of abstractions meant to control behavior. It worked, but it was painful to extend. Every experiment fought the framework. Adding new capabilities felt like going against the Bitter Lesson. (to be fair models have gotten A LOT better since last year)

第一版 Browser Use 是经典的 agent 框架:模型被包在复杂的消息管理器里,大量抽象用来控制行为。能用,但扩展起来很痛苦,每次实验都在和框架搏斗,每加一个新能力都感觉在违背苦涩教训。(公平地说,模型比去年强了太多。)

So we stepped back and asked a more fundamental question: What are LLMs actually trained to be extremely good at — and what will remain true as models get better?

于是我们退一步,问了一个更根本的问题:大模型到底被训练得极其擅长什么——以及随着模型变强,什么会一直成立?

We threw the entire agent away and started from scratch. To understand what "minimal" could really mean, we reverse-engineered the Claude Code and Gemini CLI. Props to them for creating really good and mostly simplistic primitives. Even though they're internally complex, the underlying idea is simple: Don't over-specify intelligence — let the model reason.

我们把整个 agent 扔掉,从零开始。为了理解"极简"到底能简到什么程度,我们逆向研究了 Claude Code 和 Gemini CLI。向他们致敬——他们造出了非常好、而且大体极简的原语。虽然内部实现复杂,但底层思想很简单:不要过度规定智能——让模型自己推理。

BU Agent: the application / BU Agent:落地

Rather than exposing a small set of brittle "click / type / scroll" primitives, BU Agent gives the model access to raw browser control surfaces.

BU Agent 没有暴露一小撮脆弱的"点击/输入/滚动"原语,而是让模型直接接触原始的浏览器控制面。

At the core: the ability to emit pure Chrome DevTools Protocol (CDP) instructions. In practice, the model can do almost anything in the browser.

核心是:直接发出纯 CDP(Chrome DevTools Protocol,Chrome 开发者工具协议)指令的能力。实践中,模型在浏览器里几乎无所不能。

On top of that: browser extension APIs. They make certain tasks trivial that are awkward or impossible with CDP alone — like accessing the active window or working with permissioned browser state.

在此之上:浏览器扩展 API。有些任务单靠 CDP 很别扭甚至不可能——比如访问当前活动窗口、操作需要授权的浏览器状态——扩展 API 让它们变得轻而易举。

CDP and extension APIs each have blind spots. But together they form a nearly complete action space.

CDP 和扩展 API 各有盲区,但合起来构成了近乎完整的动作空间。

When the model has that freedom, something important happens. If one approach fails, it routes around it. If a tool breaks, it finds another path. As long as in principle everything is possible, LLMs are extremely good at fixing themselves on the fly.

当模型拥有这种自由,重要的事情发生了:一条路走不通,它会绕路;一个工具坏了,它会另寻他途。只要"原则上一切皆有可能",大模型极其擅长边跑边自我修复。

The inversion / 反转

So BU Agent is built from a simple inversion: Start with maximal capability, then restrict. Give the model the freedom to do anything a human could do in a browser. Only then layer on safety, structure, and constraints. That's what lets the system scale with better models instead of fighting them.

BU Agent 建立在一个简单的反转之上:从最大能力出发,然后限制。先给模型"人在浏览器里能做的一切"的自由,然后才叠加安全、结构和约束。这样系统才能随着模型变强而一起变强,而不是和模型对着干。

I hate every other LLM framework / 我讨厌所有其他 LLM 框架

Seriously. The way they implement LLM objects is painful. So I wrote my own. Super easy way to do calling. That's all they do — for Anthropic, OpenAI, and Google. Based on our telemetry, these account for 95% of use cases.

说真的,它们实现 LLM 对象的方式让人痛苦。所以我自己写了一套,调用方式极简。它们只做一件事——对接 Anthropic、OpenAI、Google 三家。按我们的遥测数据,这三家覆盖 95% 的用例。

class ChatAnthropic:
    async def ainvoke(self, messages, tools) -> ChatCompletion: ...

class ChatOpenAI:
    async def ainvoke(self, messages, tools) -> ChatCompletion: ...

class ChatGoogle:
    async def ainvoke(self, messages, tools) -> ChatCompletion: ...

Same interface. Full control over caching, serialization, provider quirks. No magic. No surprises. It's so much easier to do caching and implement messages yourself. Completely model-agnostic. You're not locked into one provider. You just decide.

同一个接口。缓存、序列化、各家怪癖全在自己掌控之中。没有魔法,没有惊吓。缓存和消息自己实现反而容易得多。完全模型无关,不被任何一家锁死,你说了算。

Ephemeral messages / 阅后即焚消息

One interesting thing we need for browser agents: if you request the browser state, it's massive. DOM snapshots, screenshots, element indices — easily 50KB+ per request.

浏览器 agent 有个特殊需求:每次请求浏览器状态,体量都巨大——DOM 快照、截图、元素索引,单次轻松超过 50KB。

Without ephemeral messages, here's what happens: after 10 browser interactions, you have 500KB of state in context. After 20, you're at 1MB. The model starts losing coherence. It forgets the original task. It hallucinates elements that don't exist anymore. Eventually you hit context limits and the whole thing crashes.

没有"阅后即焚"会怎样:10 次浏览器交互后,上下文里堆了 500KB 的状态;20 次后 1MB。模型开始语无伦次,忘记原始任务,幻觉出早已不存在的页面元素。最终撞上上下文上限,整个崩掉。

@tool("Get browser state", ephemeral=3)  # Keep last 3 only
async def get_state() -> str:
    return massive_dom_and_screenshot

If you make a tool call X times (whatever you define), it removes all the previous outputs. Destroys cache a little bit. But it's a very good trade off — LLMs can't really handle massive contexts anyway. The model only needs the recent state; old browser snapshots are noise.

同一工具调用超过 X 次(阈值自定),就把更早的旧输出全部删掉。会稍微牺牲一点缓存,但这笔交易非常划算——大模型本来就处理不好超大上下文。模型只需要最近的状态,旧的浏览器快照都是噪音。

The for-loop doesn't work (until you fix it) / for 循环并不好使(直到你修好它)

The naive approach — stop when the model returns no tool calls — doesn't work well. Agents prematurely finish. Especially if they're missing some context. You want a follow-up, but if you're using this kind of API, it's impossible. The best way to fix it is the done tool.

朴素的做法——模型不再调用工具就停——效果不好。agent 会过早收工,尤其在缺上下文的时候。你还想让它继续,但用这种停法根本做不到。最好的修法是 done 工具。

@tool('Signal that the current task is complete.')
async def done(message: str) -> str:
    raise TaskComplete(message)

When the model outputs a done tool call, it terminates the agent. This forces explicit completion instead of implicit "I guess we're done?" We have two modes — CLI mode: stop when LLM returns no tool calls (quick interactions). Autonomous mode: only stop on explicit done() call. Claude Code does this. Gemini CLI does this. Now you know why it exists.

模型调用 done 工具时,agent 才终止。这强制了显式收工,而不是含糊的"大概做完了吧?"。两种模式——CLI 模式:模型不再调用工具就停(适合快速交互);自主模式:只在显式调用 done() 时停。Claude Code 就是这么做的,Gemini CLI 也是。现在你知道这东西为什么存在了。

But you need reliable infra / 但你需要可靠的基础设施

Yes. The for-loop is simple. Making it robust is not: retries with exponential backoff, rate limit handling, connection recovery, context compaction, token tracking. That's ops. Solved problems. Necessary — but don't confuse it with the agent itself.

是的,for 循环很简单,但让它皮实并不简单:指数退避重试、限流处理、连接恢复、上下文压缩、token 统计。不过那些是运维,是已被解决的问题。必要——但别把它们和 agent 本身混为一谈。

The bitter truth / 苦涩的真相

Every abstraction is a liability. Every "helper" is a failure point. The models got good. Really good. They were RL'd on computer use, coding, browsing. They don't need your guardrails. The bitter lesson: The less you build, the more it works.

每一层抽象都是负债,每一个"辅助函数"都是故障点。模型已经变强了,真的很强——它们在电脑操作、编程、网页浏览上被强化学习训练过。它们不需要你的护栏。苦涩教训:你造得越少,它越好使。

We're open-sourcing this as agent-sdk. Use it in production if you want, but preferably just paste the context into Claude Code and make your own. In whatever language you are coding in. The repo also contains an example of Claude Code re-implemented. Anyways, this is what we learned building bu.app. Try it out. It's awesome!!

我们把这套东西开源为 agent-sdk。想用在生产环境也行,但更推荐:把上下文直接粘给 Claude Code,用你自己的编程语言造一套你自己的。仓库里还附了一个复刻版 Claude Code 的例子。总之,这是我们做 bu.app 学到的东西。试试吧,很棒!!