天下兴亡,匹夫有责。——顾炎武

一次一条命令:agent-browser 把浏览器拆成一小步一小步

先算一下「用浏览器」这件事的成本

让 agent 上网,通常有三种做法。

一种是给它一个浏览器自动化框架的 API,让它写脚本;一种是给它一个云端的浏览器实例,让它通过协议操作;还有一种是给它截图,让它看着图猜。

三种做法都绕不开同一个问题:上下文。浏览器是一个巨大的状态机,一次把所有东西塞给模型,它会被淹没;每次只给一点点,它又不知道自己站在哪。

agent-browser 的答案是把这件事拆到最小:一次一条命令。

它的描述只有一行:给 AI agent 用的浏览器自动化 CLI。

项目地址:https://github.com/vercel-labs/agent-browser

正文说得更清楚:一个用 Rust 写的原生 CLI,给 agent 一个可以一次一条命令驱动的浏览器。snapshot 把页面返回成紧凑的文本,每个元素带一个像 @e1 这样的 ref。agent 对着 ref 操作,再读一次页面确认,如此往复。

而浏览器在命令之间是开着的,所以每一步都只是一次很短的 shell 调用。

这句话是整套设计的支点。

短调用,长状态

传统自动化脚本是「一个脚本干完所有事」:打开、点击、等待、断言、关闭,全在一次执行里。

这种模式对人是友好的——写完就跑。但对 agent 不友好:agent 需要在中途看结果、改主意、处理意外。如果每一步都要重启一个浏览器、重新登录、重新导航,那「看一眼再决定」这件事的成本会高到没法做。

agent-browser 把状态留在浏览器里,把控制权交给一次次短调用。

于是循环变成了这样:

1
2
3
4
5
agent-browser open example.com
agent-browser snapshot -i # 只列可交互元素,每个带 ref
agent-browser click @e1 # 对该快照里的 ref 动手
agent-browser get url # 检查结果
agent-browser close

五条命令,对应五个决策点。中间任何一步发现不对,agent 都可以停下来、换个思路、再试。

这不是「更快的自动化」,这是把自动化变成对话。

那个 snapshot 长什么样

example.com 的快照只有一行:

1
- link "Learn more" [ref=e1]

一行。一个链接,一个名字,一个引用。

这就是它能工作的原因。「把页面转成紧凑文本」这句描述里,「紧凑」两个字是核心。一个页面的 DOM 可能有几千个节点,截图可能有一千多像素高,而一段可读文本加引用,可能只有几十个 token。

顺便说,那个 - 前缀很讲究。它让输出天然像一个缩进列表,模型读起来跟读大纲一样。这不是装饰,这是给模型设计的排版。

那条我最喜欢的规则

文档里有一段话,我认为是整个项目的价值观所在:

当目标被隐藏、被禁用、在视口之外,或者被另一个元素——比如一个同意横幅——遮住时,点击会失败,而不是报告成功。

后面跟着三步处理:关掉遮挡的元素,重新拍快照,重试。

这句话值得停下来想。

在浏览器自动化里,「点击」这个动作有一个历史悠久的谎言:很多工具会告诉你点成功了,实际上它只是把事件派发到了一个坐标上,而那个坐标下面可能是横幅、可能是空白、可能是另一个按钮。

对脚本来说这没什么,因为脚本会自己去断言。但对 agent 来说,这就是灾难:它会认为这一步完成了,然后基于一个错误的前提继续往下走,十步之后你才发现它一直在操作一个从来没打开过的页面。

agent-browser 的选择是让点击诚实地失败。宁可让 agent 多走一轮,也不让它带着幻觉前进。

这和它另一条规矩是一套的:ref 描述的是快照那一刻的页面,导航或 DOM 变了之后,必须重新拍快照再用 ref。

两条规则合起来,是一句话:这个工具不允许你拿着旧地图找新路。

背后:一个 daemon,一小时

架构那一段写得很清楚。

CLI 把每条命令发给一个后台 daemon,daemon 在第一条命令时启动,通过 Chrome DevTools Protocol 控制浏览器。每个 --session 有自己的 daemon 和浏览器。默认情况下,daemon 会在一小时没有命令之后关掉无头浏览器并退出;如果配置了会话持久化,会先保存状态。

这几个决定的组合挺妙。

「第一条命令时启动」意味着你不需要预热,不用 agent-browser start。装完直接用。

「每个 session 一个 daemon 和一个浏览器」意味着并行不打架。你可以让一个 agent 在 session A 里查文档,另一个在 session B 里填表,它们不会互相抢焦点。

「一小时闲置退出」是一个很现实的保洁约定。它承认了:agent 会忘记关浏览器。与其让几十个 Chrome 进程躺在那儿吃内存,不如设一个超时,把垃圾收走。

而「退出前先保存状态」——这一句说明它知道 agent 的工作不能随便丢。

不止一种浏览器

默认是 Chrome for Testing,通过 agent-browser install 下载。

但文档列出的其他选项很值得看:

用 --cdp 或 --auto-connect 接管一个正在运行的 Chrome;用 --engine 切到 Lightpanda 或 Obscura;在 iOS 模拟器里驱动 Mobile Safari;或者接一家云浏览器:Browserbase、Browserless、Browser Use、Kernel、AgentCore。

六种来源,覆盖六种场景。

接管已运行的 Chrome,是给「我这个站点需要我已登录的账号」准备的。云浏览器,是给「本机跑不动或者要并发很多个」准备的。iOS 模拟器,是给「移动端布局又崩了」准备的。

而两个非 Chromium 引擎的存在,说明它没有把 CDP 当成唯一真理——这句话写得很克制,只提了名字,没吹性能。

一个只有几句话的 skill

「给 AI agent 用」那一节,有个设计我很欣赏。

CLI 自带和版本匹配的 skills:

1
2
3
agent-browser skills get core                 # 核心循环、ref、等待与常见流程
agent-browser skills get core --ref commands # 单个参考,比如完整命令表
agent-browser skills get core --full # 加上完整命令参考与模板

而装进 agent harness 的那个 skill,是一个很薄的 stub——它只告诉 agent:去运行 agent-browser skills get core,然后照做。

这个设计解决了一个很烦的问题:文档和版本不同步。

如果 skill 里塞满命令说明,那 CLI 一升级,skill 就成了过期地图。而如果 skill 只是一个跳板,那 agent 每次读到的都是手上这个二进制自己带的说明。

同一个思路也出现在 AGENTS.md 的建议里:第一次用之前,先跑那条命令,然后跟着它走。

给 agent 写文档,和给人写文档不是一回事。人读一次就够了,agent 每次都要重新读;所以文档必须和它手上的工具是同一份。

MCP 客户端也有对应的入口:一条命令起一个 stdio 的 MCP 服务器。

命令表:能点,也能看穿

命令表按组排,粗看有十组。

导航与读取、交互、等待、捕获、页面与网络状态、标签与弹窗、调试与性能、会话与认证、agent 集成、观察、安装。

其中有一组最能说明它的野心:调试与性能。

console、errors、highlight、inspect、trace、profiler、vitals、react、a11y。

一个「给 agent 用」的浏览器工具,为什么要带 profiler 和 vitals?

因为 agent 要干的活不只是「点一下」。「这个页面为什么慢」「这个组件为什么没渲染」「这个按钮的对比度够不够」——这些问题的答案不在 DOM 结构里,在性能轨道和无障碍树里。

而 react 这条命令很有意思:能读 React 的内部状态,意味着它能回答「这个列表到底拿到了几条数据」这种问题,而不只是「屏幕上有几个 li」。

另一组是捕获:截图、PDF、录屏、下载、diff。

diff 值得单独说。它是给「这次改动到底改了哪儿」准备的——agent 改完样式,拍两张图做差,比让它描述「我觉得看起来好多了」靠谱得多。

平台:原生二进制,五个目标

macOS 的 ARM64 和 x64、Linux 的 ARM64 和 x64 各两种 C 库、Windows x64。

npm 包负责挑对的那一个。

这解释了两件事:为什么它用 Rust 写,因为要发原生二进制;为什么它敢说自己「一次命令一次调用」却不担心启动开销,因为每次启动的是个原生程序,不是整个 Node 运行时。

从源码构建要 Node 24 以上、pnpm 11 以上,再加上 Rust。检查也很标准:cargo fmt、cargo clippy、cargo test,以及一个会真的启动无头 Chrome 的端到端测试。

最后那条要单线程跑,也不奇怪。开浏览器的测试,天然不适合并行。

最后

回到开头那个成本问题。

agent 用浏览器,最贵的从来不是「看见」,是「下一步该干什么」——而这件事需要它在中途有机会停下来、看一眼、改主意。

一次性脚本给不了这个机会,全量截图又太贵。

agent-browser 选的是中间那条窄路:把浏览器留在后台,把控制权拆成一条条短命令,每条命令只做一件事,做完就回来汇报。

而它最难得的地方,是那些不允许自己撒谎的规则:点击失败就报失败,ref 过期就要重拍,遮挡物必须先处理。

一个会骗人的自动化工具,你只能用一次。一个会说「这一步没成,你自己看看」的工具,才敢让它跑一整个下午。