1 个 Agent 能干一段活,3 个 Agent 一起干就容易乱。OpenBot 想解决的正是这件事:把多个 AI Agent 放进同一张桌面工作台,各有工位、各有队列,互相还能递东西。
OpenBot 一句话就能说清:一个 local-first 的桌面工作台,让多个 AI Agent 常驻在你的机器上,各自有工作区和会话,彼此能发消息、传文件。
「local-first」指程序和数据都跑在本机,只有账号体系那一小段联网。它不等于纯离线:登录与账号相关的请求,走 Cloudflare Workers 上的接口。这个区别得先记住,后面讲权限和同步时会绕回来。
先说为什么现在聊这个——因为开发者手里的 Agent 变多了。
一年前多数人只开 1 个命令行 Agent,问一句答一句,关掉窗口就结束。现在常见的画面是:一个 Codex 在改后端,一个 Claude 在写测试,还有一个在浏览器里翻文档。3 个终端标签,3 套上下文,彼此不认识。
能力早就不是瓶颈了,秩序才是。
所以这篇不打算夸它多强。我把它当成一张工位图来拆:谁坐哪、活怎么排队、话怎么传、出事谁负责、哪里会疼。每一节都按四步走——先说它是什么,再说为什么需要,然后看怎么实现,最后算一笔值不值的账。
一、Agent 不缺能力,缺的是一张固定工位
先给定义。OpenBot 给每个 Agent 分配一个长期存在的「工位」:一个工作目录、一段能持续下去的会话、一条属于自己的任务队列。
它用 TypeScript 写成,代码在 nightly-labs/openbot,产品页是 openbot.run。
为什么要工位?因为终端天生不保存身份。
在终端里,一个 Agent 的上下文活在那个进程里。进程一关,它做过什么、动过哪些文件、还欠哪一步,全凭你脑子里那本账。3 个 Agent 并行的时候,这本账很快就记不住了。
更麻烦的是串味。同一个目录里起两个 Agent,它们看到同一批文件,改同一份配置,最后谁覆盖了谁说不清。你回头翻记录,只看到两份不一样的结果,中间发生了什么,没有痕迹。
OpenBot 的做法,是把「身份」和「现场」一起固化下来。
进程层面分两层:Electron 主进程管窗口、文件、子进程和系统权限;SolidJS 渲染层只负责界面。Agent 的子进程挂在主进程下面,界面怎么刷新都不影响它们继续跑。
数据层面把这些状态写进本地 SQLite,文件名是 openbot.db。写的是命令日志,读的是投影。
这两个词值得停下来。「命令日志」是只追加的记录,谁在什么时候要求谁做什么,按顺序落盘。「读投影」是把日志折叠成当前状态,界面查的是这一份。
分开的好处是崩溃后能重放,坏处是要维护两边一致。日志写成功、投影没更新,界面就会显示一个旧状态。这类问题不难修,但排查时容易先怀疑错地方——看到界面卡在旧进度,先想投影,别急着说 Agent 卡死了。
工位还带来一个隐性收益:可中断。
人在中途离开是常态。接个电话、开个会、下班。工位把「离开」这件事变得没事——Agent 继续跑,队列继续排,回来接着看。终端里做不到这一点,标签一关,或者笔记本一合,回来就得重开一轮。
代价也在这里:它得常驻。主进程活着、SQLite 在写、Agent 子进程在跑,风扇会比平时响一点,磁盘也会慢慢长。你在用一台 8G 内存的机器时,得算一算能常驻几个 Agent。
值不值?如果你只是偶尔问一句,装个桌面应用去换掉终端标签,成本大于收益。
如果你每天都在 3 个 Agent 之间来回切,这套工位的价值就很直接:上下文不丢,文件归属清楚,谁欠着活一眼能看到。

权限开到最大这件事,得单独说
README 顶部有一条警告,值得原样转述:这个项目现在是 development preview,Agent 目前以 danger-full-access 运行,审批策略是 never。
翻译成具体行为:首次启动有一个同意步骤,点完之后,Agent 可以读文件、改文件、执行命令、访问网络、控制内嵌浏览器,每一步都不再单独向你确认。
这个设计选择有它的道理。多 Agent 场景下,如果每个 Agent 每做一步都弹一次确认,你会被通知淹没,最后条件反射地点同意——那种「确认」比不确认更糟,因为它给你一种被保护的错觉。
但代价是真实的。一个 Agent 误解了任务,跑一条 rm 或者一次带 force 的 git 操作,它不会问你。这也是为什么 README 直接写了两条建议:只跑你信任的 Agent 和任务,做好备份。
我的读法是:把它当成一个「在你机器上有完整权限的远程同事」来配置,而不是当成一个沙箱应用。想清楚这一点,很多决策会自然做对——比如它会建议你单独开一个用户账号,或者干脆放在一台不装重要东西的机器上。
顺带说,这类工具的安全边界通常不在产品里,在使用者的部署方式里。同一个软件,跑在你日常工作的那台机器上,和跑在一台专门的机器上,风险差一个量级。
二、常驻:一个 Agent 一套工作区
工位落到磁盘上,就是一组目录。
OpenBot 给每个 Agent 一个独立工作区:~/OpenBot/Agents/<agent-id>。这里的 <agent-id> 是它在系统里的身份,不是昵称。
另外两个位置跟具体 Agent 无关:~/OpenBot/Shared 放跨 Agent 的公共素材,~/OpenBot/Downloads 放从外面拉下来的东西。
为什么要这么切?因为混在一起会出事。
把所有 Agent 都指向同一个项目目录,第一天看着方便,第三天就开始互相踩。A 改了一半的文件被 B 读到,B 根据半成品写了新代码,最后两条错误的路一起往前走。等到编译不过,你已经分不清是哪一步进的坑。
隔离之后,边界很清楚:谁的目录谁负责,要共享就走 Shared。
这里有个容易忽略的细节:目录路径里带着 agent-id,意味着工作区和身份是绑死的。
换一个 Agent 就是换一个目录,不会出现「同一个目录两个身份」的模糊状态。这对排错特别有用——看到一份奇怪的文件,先看它在谁的目录里,责任范围立刻收窄。
那 Shared 怎么用才不出事?
我的建议是把它当收发室,不当草稿纸。往里放的是「已经定稿、别人要用」的东西,比如一份 schema、一张图、一个构建产物。半成品放进去,等于把私货摆到了公共桌面,别人拿起来就用,错就跟着扩散。
会话也是一样。每个 Agent 的会话跟着它的工作区走,不是一个全局大对话。
全局大对话听起来省事,实际会把上下文搞脏。你在讨论前端样式的时候,后端 Agent 的报错信息也在这个窗口里,模型会被带偏,回答开始照顾两边,最后两边都不满意。
分开之后,切换 Agent 等于切换上下文,思路是断开的。这个「断」是好事——它逼你把要交接的东西说清楚,而不是指望对方从一大堆背景里自己捞。
再往下一层,是持久化。openbot.db 里存的不只是聊天记录,还有命令日志。
为什么不直接存最终状态?因为状态是推导出来的,日志才是事实。日志能回答「这条指令是什么时候、由谁、以什么参数下发的」,状态表回答不了。出问题时,你需要的是当时,不是此刻。
代价也要说清。日志只追加,文件只增不减,长期使用要有清理预期。投影要跟着日志更新,写路径多了,一致性就靠代码纪律兜。
还有一个实际影响:备份。想给工作区做快照,得连数据库一起带上,只拷 ~/OpenBot/Agents 是不够的。反过来,想彻底重来一次,删目录还不够,数据库里的身份和队列也得清。
如果你打算把它当日常工具,先想好三件事:磁盘留多少,日志留多久,备份带不带库。

为什么「工位」不只是个比喻
先把这个词落实。传统多开终端时,每个 Agent 的处境是这样的:它在同一个工作目录里跑,能看见同一个仓库的所有文件,读写的是同一份 node_modules,两个 Agent 同时改一个文件时后写的覆盖前面的。
问题不是它们会不会打架,是你没法知道它们打了架。终端里的输出各滚各的,没有任何一个地方记录「谁在什么时候动了哪个文件」。
OpenBot 的工位概念对应三个具体的分离:
目录分离。 每个 Agent 有自己的工作目录,路径是 ~/OpenBot/Agents/<agent-id>。这个目录归属它自己,别的 Agent 默认看不到。要共享得进 ~/OpenBot/Shared。
会话分离。 每个 Agent 有自己的会话。你在 A 里聊的内容,不会出现在 B 的上下文里。
队列分离。 每个 Agent 有自己的消息队列,互不阻塞。A 卡在一个长任务上,B 该干什么还干什么。
三样加起来,才构成一个「工位」。少了任何一样,多 Agent 就会退化成「多个窗口」。
这也是为什么它敢叫自己「工作台」而不是「启动器」。启动器管的是把进程拉起来,工作台管的是把一群进程的边界划清楚。
三、接进来:一个入口接住各种 Agent
工位有了,下一个问题是:谁的工位?OpenBot 不自己造模型,它做的是接入。
接入分成两条路。第一条是原生集成:本地 Codex App Server 和 Claude 那一侧。
Codex 走的是 stdio 上的 JSONL。简单说,主进程起一个子进程,两边用标准输入输出逐行收发 JSON,一行一条消息。这种方式不占端口,不需要鉴权握手,进程死了通道就断,状态很好判断。
Claude 那侧接的是 Claude Agent SDK,消息以 stream JSON 的形式回来。流式的好处是过程可见——工具调用的中间态能显示出来,而不是等一个最终答案。你能看到它先读了哪个文件,再决定改哪里。
第二条路是 ACP,全称 Agent Client Protocol,一套让客户端和 Agent 对话的通用协议。
在 OpenBot 里,走 ACP 的客户端共用同一个适配层,启动命令由各家自己给:
- Grok CLI
- OpenCode
- Gemini,通过 Antigravity 的 ACP server,路径从环境变量
OPENBOT_ANTIGRAVITY_PATH 读
- Cursor CLI,启动命令是
cursor-agent acp
- Cline CLI,启动命令是
cline --acp
除了这两条,还有两个兜底口子:自定义的 OpenAI 兼容端点,以及自定义 ACP agent。本机装了 Ollama 或 LM Studio,OpenBot 会探测到。
为什么要这么多入口?因为不同活适合不同 Agent。
长上下文读代码,和快速改一个配置,是两种任务。有人习惯用 Cursor 那套,有人手里只有 Cline。如果工作台只认一家,其余的人就得在终端和桌面之间来回跳,工位的意义就没了。
协议统一之后,界面只有一套:任务怎么发、队列怎么排、文件怎么传,都走同一套逻辑。差异被压到适配层里。
这里要说清一个边界:接入不等于能力对等。
ACP 是传输协议,它规定消息怎么走,不规定 Agent 能干到什么程度。同一个「读文件」动作,不同 CLI 的实现深度、权限模型、上下文窗口都不一样。工作台能保证的是流程一致,不能保证产出质量一致。
实测里最容易踩的坑,是路径。Antigravity 靠环境变量定位,环境变量没设,表现是连不上而不是报错清晰。这类问题先查环境,再怀疑协议。
另一个坑是启动方式。cursor-agent acp 和 cline --acp 都是把 CLI 拉进 ACP 模式,你手动在终端敲一次能进,工作台里进不去,多半是 PATH 不同——图形界面启动的进程,拿不到你 shell 里的那套环境。
解决办法很土但有效:把 CLI 装在图形环境也能找到的位置,或者在配置里写绝对路径。别指望工作台替你去 source 你的 .zshrc。
还有一个坑是「接上了但状态不对」。
免登录的本地服务、需要 token 的远端服务、需要先跑一次授权的 CLI,这三种的失败表现不一样。第一种是端口不通,第二种是 401,第三种最麻烦——连接成功,但每个动作都返回权限错误。看到权限错误先别改协议配置,去 CLI 里手动授权一次。
值不值?多入口的代价是配置面变宽,每个 Provider 都有自己的脾气。
但对「多个 Agent 常驻」这个前提来说,这是必须付的钱。只支持一家的工作台,本质上还是一个加壳的客户端——你把终端换成了窗口,别的东西都没变。

四、消息队列:FIFO、可暂停、崩溃后还在
一个 Agent 同时被 3 个人催,会发生什么?在终端里,答案是三份需求混成一段乱序文本。
OpenBot 用队列回答这个问题。每个 Agent 有一条自己的消息队列,按 FIFO 排队——先到先做,后来的等着。
FIFO 这个词很朴素,但它解决了一个真问题:顺序可预期。
不排队的时候,你补一句「顺便把测试也跑了」,模型可能把它当成本轮的补充要求,也可能当成下一轮的。排队之后,「顺便」就是一条新任务,位置明确,落在哪一格你能看见。
队列的三个动作是这套机制的核心:
暂停和取消的区别在于语义。暂停是「等一等,我可能还要」,取消是「这件事不存在了」。取消之后,队列后面的任务会往前顶,不会留一个空洞等它。
为什么需要暂停?因为 Agent 干活的时候,人常常会中途改主意。
比如它在批量重命名文件,你看到第三个名字不对,想先说清楚规则再放行。没有暂停,你要么打断(它可能停在半路),要么等它跑完再回滚(代价更大)。
有了暂停,你可以把它摁住,补一条说明,再放开。它不会跳步,也不会把中间态当成品。
更硬的一层是崩溃安全。
队列是持久化的,写进本地存储。程序被强杀、机器断电,重开之后队列还在,未完成的任务不会凭空消失。这个特性听起来像基础设施的常规要求,但对 Agent 工作台很关键——Agent 干的活往往有副作用,改文件、发请求、跑构建,丢了队列就等于丢了「还剩什么没干」的账。
这里的取舍值得讲透。
持久化队列要付出写盘成本,每条消息落盘一次,高频小任务会有额外开销。它也带来一个语义问题:重启后那条「做到一半」的任务算什么?
OpenBot 的选择是把未完成的任务留在队列里,由人来决定继续还是取消。这是偏保守的设计——它不替你做幂等判断,也不假装知道副作用做到哪一步。
这个保守是对的。让程序猜「这条重跑会不会重复提交」,猜错的代价比多问一句大得多。宁可让人花 10 秒确认,也不要让程序花 1 小时补数据。
坑在哪?在「取消」的边界。
取消是取消排队中的任务,不是撤销已经发生的动作。文件已经改了,请求已经发了,取消只保证后面的不再执行。把它当回滚用,一定失望。
所以我的用法是:破坏性动作前面放一条暂停点。先让队列停住,确认清楚,再放行。这个习惯跟队列本身无关,但它是队列能不能用好的分水岭。
另一个坑是队列的长度感知。
队列排了 8 条还是 2 条,界面上要能看清。排太长了,任务之间会互相影响——前面那条改了文件,后面那条的假设就过时了。我一般控制在 3 条以内,做不完就先停,重新想一遍顺序。

队列为什么需要「崩溃后还在」
这一点容易被当成实现细节跳过,但它是常驻 Agent 能不能用的分水岭。
先想一个场景:你给 Agent 排了五个任务,它跑到第三个,你合上笔记本。半小时后打开,如果队列只在内存里,剩下两个任务没了,而且它不会告诉你它们没了——它只是安静地停在那里,等你以为它还在跑。
更隐蔽的情况是进程崩了但你没注意。Agent 的面板还开着,状态显示「空闲」,你以为活干完了,实际上队列里的东西在崩溃那一刻全丢了。
所以「队列落盘」不是一个性能优化,是正确性问题。它保证的是:你说过的话不会因为一次意外消失。
对应的另一面是恢复语义。落盘之后,重启时它得知道每个任务处于什么状态——没开始的可以重跑,跑到一半的不能简单重跑(可能产生了副作用),已经完成的不能重复执行。这套状态机如果设计得草,落盘反而会带来重复执行。
从 README 的描述看,它做的是 FIFO 队列加崩溃安全的持久化,并且支持暂停、恢复、取消。这三个动作配合落盘,实际给出了「可控」这件事的最小集合:你想停能停、想继续能继续、想撤能撤。
这也解释了为什么它把队列做成每个 Agent 独立,而不是全局一个。全局队列在某个 Agent 卡住时会堵住所有人;独立队列各堵各的,你最多损失一个工位的产出。
五、递东西:Agent 之间怎么传消息和文件
队列解决的是「人对 Agent」,还差一半:「Agent 对 Agent」。
真实项目里,一个任务经常要跨 Agent。A 在写接口,B 在写调用方,中间那份类型定义归谁写?在终端方案里,答案是复制粘贴——人当路由器。
OpenBot 把这层搬进了工作台。Agent 之间可以直接发消息,也可以传文件。
传文件的落点在 ~/OpenBot/Shared/Transfers。往这个目录写文件时,会有 1 个清单文件 .openbot-transfer.json 记录这次传输,内容是所有权、接收方、大小和 SHA-256。
为什么要搞一份清单,而不是直接放到共享目录?
因为共享目录只解决「放得进去」,不解决「算谁的」。
没有清单的时候,一个文件出现在共享区,谁放的、给谁的、是不是传了一半,全靠猜。SHA-256 在这里是干活的:它让接收方能验证拿到的东西和发出的一致,传输被截断或者被改写,一算就知道。
「所有权」这一项更微妙。它意味着文件在系统里有一个明确的归属者,不是一堆无主文件堆在共享盘上。
这在排错时的差别很大。假设下游拿到一份旧 schema,你得先判断:是发送方发错了版本,还是接收方缓存了旧的?有所有权和摘要,就能把范围切到一侧,不用两边一起查。
具体到用法,我建议把传输当「交接单」,不当「同步盘」。
一张交接单要有三样:对象是什么(文件名)、谁给的(所有权)、对不对(摘要)。这三样齐了,接收方可以自己完成校验,不需要再开一个对话问「你这个是最新的吗」。
而同步盘的心智是「文件会自己出现」,两边都不用管过程。这种心智在多人协作里很舒服,在多 Agent 里很危险——Agent 不会主动去看共享目录有没有更新,它只在被要求的时候才读。
所以流程要显式:A 写完,发一条消息给 B,说清文件名和用途;B 收到消息,去 Transfers 取,校验摘要,再用。
三步都不能省。省掉消息,B 不知道有东西;省掉校验,B 可能用一份坏文件;省掉用途说明,B 会猜错该怎么用。
消息本身也是这个道理。
Agent 之间的消息不是闲聊,它应该带着结构:要什么、给什么、什么时候要。写成一坨自然语言,接收方得先理解再拆解,多一层损耗就多一层错。
坑在这里:传递会放大错误。
一个 Agent 判断错了字段类型,传给下一个 Agent,下一个基于错误前提写了实现,再传给第三个。等到编译报错,错误已经在 3 个工位之间转了一圈,你可能要回翻 3 段记录才能定位。
传递链越长,越需要在每一跳设检查点。我的做法是让每次传递都附一条「可验证的断言」,比如「这个接口返回 3 个字段」,接收方第一步就是验证这句话,不成立就退回去,不让错误往下走。
还有一层是隐私边界。
本机跑不等于没有外传。Agent 调用远端模型的时候,你递给它的文件内容会跟着请求出去。传文件之前想一下:这份东西能不能出本机?
工作台能管的是流程,管不了判断。判断在你这边。

传输带元数据这件事,为什么值得
~/OpenBot/Shared/Transfers 这个目录里,每次传输都带一个 .openbot-transfer.json,里面写归属、收件人、大小、SHA-256。
四个字段,各自解决一个问题。
归属 回答「谁生成的」。多 Agent 场景下,一份产物出问题时第一个要问的就是这个。
收件人 回答「给谁的」。同一份文件可能发给多个 Agent,也可能只发给某一个。这个字段也决定了删除 Agent 时的清理范围——README 说得很清楚:删一个 Agent 会删掉它的工作区、它生成的附件、以及「只发给它」的传输;如果还有别的 Agent 在用同一条消息,传输保留。
这条清理规则是我认为设计得比较克制的地方。很多工具删除时的默认行为是「连带清理」,看起来干净,实际会误删别人还在用的东西。
大小和 SHA-256 回答「有没有被改过」。Agent 之间传文件不像 HTTP 有内容协商,传过去的东西可能被中间某个环节动过。有了哈希,接收方可以验。
四个字段加起来,把「文件传输」从「扔过去」变成了「有凭据的交付」。这个差别在调试时特别明显——出问题时你知道该问谁、该验什么。
六、共享频道:一个任务只能有一个 owner
多个 Agent 看同一件事,最容易出的问题是「都以为别人在做」。
OpenBot 的答案是一个共享桌面频道:同一个任务,只有一个 owner。
owner 之外的人想参与,得走显式委派。委派是一个动作,不是一句「你也看看」。它有明确的指向:从谁到谁,做哪一段。
频道上的五个操作把权责说清楚了:
- Stop:停下
- Resume:接着来
- Reassign:换人
- Archive:归档
- Restore:恢复
这五个词选得挺讲究。Stop 和 Archive 分开,是因为「暂停」和「收工」是两件事。停下的任务还在场,归档的任务退出视野但不销毁。
Reassign 单独存在,说明换 owner 是常规操作,不是异常处理。
为什么要这么设计?因为并行协作里,最贵的成本不是算力,是「谁在做什么」的不确定性。
人多的团队靠会议同步。Agent 没有会议,只能靠状态。状态不显式,就会退化成互相猜测:A 觉得 B 在改,B 觉得 A 已经改完,两边都不动,任务静默停住。
单一 owner 把这个模糊地带砍掉了。任何时刻,一个任务有一个负责人,其他人要么旁观,要么被委派。
这条规则听起来限制协作,实际是让协作可算。
我在实际用的时候,会把它当成「值日表」。一个任务开出来,第一件事是认领,认领之后就写清范围。范围写不清,后面全是扯皮——不是 Agent 扯皮,是你自己在两份不一致的产出之间做仲裁。
Archive 和 Restore 这一对,解决的是「历史怎么留」。
归档不等于删除。任务从当前视野里退出去,记录还在,需要的时候能恢复。这对长项目很重要:3 周前为什么改了那个配置,答案可能就在一条已归档的任务里。
坑在哪?在委派的边界。
委派一段活,如果只说了「你把这部分做了」,没说清输入和验收标准,接收方会自己补全假设。补全的假设和你的预期不一致,返工就是必然。
我的写法是三句话:给什么、要什么、怎么算完。第三句最容易被省,也最容易被反咬。
另一个坑是频道里的信息密度。
频道是所有 Agent 都能看到的地方,适合放「公共事实」:约定、接口、当前 owner。不适合放过程性思考——那些东西会污染其他人的上下文,让它们把别人的草稿当成前提。
所以我把频道当公告板,不当聊天室。公告板上只贴已经确定的东西。
还有一层是人的位置。频道里人也是参与者,可以停、可以换人。这意味着责任最终还在人这边——Agent 不会替你决定优先级,它只会按你说的做。
Reassign 用得多,往往说明前面的范围没划清。这是个信号,值得回头看。

单一 owner 到底防什么
「一个任务只能有一个 owner」这条规则听起来像常识,但它是多 Agent 协作里最容易出事的地方。
出事的方式不是两个 Agent 抢着干,而是两个 Agent 都以为对方在干。你 @ 了 A 和 B,两个都看到了,A 想「B 会处理」,B 想「A 会处理」,任务静静躺着,直到你来问进度。
显式委派解决的正是这个。它要求一个任务有且只有一个 owner,其他参与者要么不接,要么得先把 owner 转过去。这样责任链是清晰的,出问题能追到人。
配套的四个控制也各有分工,值得分开看:
Stop 是暂停当前执行,任务还在,owner 还是那个人。用于「先别动了,我要看看」。
Resume 是从暂停点继续。它和 Stop 是一对,保证暂停不等于放弃。
Reassign 是换人。这才是真正处理「owner 干不了」的按钮——不删任务、不重开任务,只把责任转移。
Archive 是把任务收起来。它不同于删除,归档的任务还在,只是不在当前视图里。
Restore 是 Archive 的反向操作。
五个动作凑齐,一个任务才算能完整地走完「进行—暂停—换人—归档—恢复」这条生命周期的每一段。少一个,你就会在某个具体场景里卡住。比如没有 Reassign 时,owner 的 Agent 挂了,你只能删任务重开,前面的上下文全丢。
七、浏览器:Agent 的眼睛要留着
很多任务是「去网页上看一眼」。查文档、看控制台、打开一个预览页。终端 Agent 撞上这类活,通常只能求人。
OpenBot 里内嵌了一个持久浏览器。这里的「持久」有两个意思:窗口常驻,登录状态也跟着留下。
为什么要嵌入,不用本机已有的浏览器?
因为在外面开一个浏览器,Agent 看不到它。它能调工具打开页面,但读不到页面上渲染出来的东西——那些靠 JavaScript 拼出来的内容,抓页面源码是拿不到的。
嵌进工作台,情况就变了:页面渲染在 Agent 能读的地方,滚动、点击、取值都在同一个上下文里。
第二个原因是登录态。真实任务经常需要登录:看后台的一个数字,确认一条数据是否落库。每次都要人重新登录,Agent 的活就断在这里。
持久浏览器把会话留着,第一次登录由人完成,后面 Agent 用同一份状态。这个设计把「人的一次介入」和「Agent 的多次使用」分开了。
第三个原因是可见。界面就在旁边,Agent 在页面上做什么,你看得见。这个可见性对信任很关键——把一个会点按钮的 Agent 放到看不见的浏览器里,多数人不敢用。
再说 Computer Use,也就是让 Agent 直接操作图形界面。
它是可选的,不是默认开的。这个默认值选得对。
Computer Use 的原理是看屏幕、动鼠标。它能做的事很广,代价是每步都可能出错:点错了位置、识别错了按钮、把弹窗当成了正文。而且它的错误很难复现,同一串动作第二次跑可能就点偏了。
所以我的建议是把 Computer Use 当备选,不是主力。
有 API 的走 API,有命令行工具的走命令行,都没有了,再让人去点一次。Computer Use 只在这一串都走不通的时候才值得开。
那它什么时候值得开?我总结了两类。
一类是必须看渲染结果的活,比如判断一个样式有没有错位、一张图有没有加载出来。这类没法用接口验证。
另一类是低频的一次性操作,写脚本不划算,手动点又不放心。这种交给 Computer Use,省下的是写脚本的时间。
边界要说清:它不保证可重复。
同一个操作跑两次,结果可能不一样。所以凡是要跑很多次的活,别交给它;凡是出错代价高的活,也别交给它。它适合「做一次、看一眼、错了重来」的场景。
坑还有一个:权限。
浏览器能访问的网站,等于 Agent 能访问的数据。把工作浏览器和私人浏览器分开,是个务实的做法。工作台上那个窗口,只登工作账号。
登录态持久这件事,反过来也是风险。状态留着方便,也意味着别人用这台机器的时候,能直接进到那些页面。机器要不要锁屏,这是个现实问题。
值不值?如果你的任务里网页占比超过三成,内嵌浏览器省下的时间很实在。
如果只是偶尔查一次文档,用不着为它多开一层。

那个「清楚」的边界具体是什么
架构那一节要讲的不是分层本身,是这条边界为什么重要。
src/main、src/backend、src/preload、src/renderer 四个目录。前三层的职责是:主进程管生命周期与安全,后城管适配器、持久化、调度、传输、浏览器,preload 只暴露一个带类型的 window.openbot。
这个划分的关键在 preload。它是一道收窄的接口:渲染层能用什么,全由这个文件定义。渲染层不能直接 require,不能直接拿 Node API,不能直接访问文件系统。
Electron 项目里这条线容易糊。图省事直接用 nodeIntegration: true 的项目,渲染层等于一个完整的 Node 进程,一个 XSS 就等于本地代码执行。OpenBot 选的是一条更费事但更正确的路:所有能力都得在 preload 里显式声明,渲染层只能调声明过的东西。
再叠一层:内嵌浏览器用的是独立的沙箱会话,它访问不到 window.openbot,也访问不到管理的本地附件。这一条很重要——Agent 打开的网页和它自己的运行环境必须隔离,否则一个恶意页面就能顺着浏览器摸到 Agent 的文件。
边界划清楚的实际收益是「可审计」。你能一眼看出渲染层能做什么,因为清单在一个文件里。这比散落在几百个组件里的直接调用要好查得多。
八、架构:日志、投影与一条清楚的边界
前面七节讲的都是功能,最后说骨架。
OpenBot 分三层。
主进程是 Electron。它管窗口、文件系统、子进程、系统权限。所有「碰系统」的动作都经过它,渲染层不直接碰文件。
渲染层是 SolidJS。它只做界面,把状态画出来,把操作发下去。细粒度响应式的意思是改一个值只重画用到它的地方,不是整页刷新。
状态层是本地 SQLite,文件名前面提过,openbot.db。里面是命令日志加读投影。
再加一层在云上:账号 API 跑在 Cloudflare Workers。
这条边界最关键:什么在本机,什么在云上。
本机的是工作区、会话、队列、传输清单、日志、投影。云上的只有账号相关的那一小段。所以断网时,本地协作照常跑;登录、身份这类事情才需要网络。
为什么要这么切?因为 Agent 处理的东西,多数不适合上传。
代码、配置、日志、截图,这些内容一旦离开本机就没有回头路。把功能尽量压在本机,是 local-first 的实际含义。它不是一句「我们很注重隐私」,而是把数据流画出来之后,绝大部分箭头都在本机内部。
分层带来的另一个好处是崩溃可控。
渲染层崩了,重开窗口,主进程和子进程还在,队列还排着。主进程崩了,队列在库里,重开之后能接上。这个「分层降级」的能力,比任何单点的稳定性优化都实在。
代价是复杂度。
三层之间要有协议,要有错误传递,要有状态同步。一个操作从点击到落盘,经过渲染层、主进程、子进程,任何一环慢或者错,你看到的表现都是「点了没反应」。排查时得按层往下走:先看界面状态变没变,再看主进程有没有收到,最后看子进程是不是活着。
日志加投影这套结构,也是同样的取舍。
日志是事实,投影是视图。查询走投影很快,重建走日志很稳。代价是两边要对齐,写路径一多,漏更新就是 bug。
我的经验是:看界面上的进度卡住,先怀疑投影;看 Agent 完全不动,先怀疑子进程;看子进程活着但没输出,先怀疑 Provider 连接。三个方向,对应三层,别混着猜。
值不值?这套架构对一个桌面应用来说是偏重的。
单独的 Electron 加一个渲染层就够了,加 SQLite 日志和云上账号 API,都是为「多 Agent 常驻」这个前提付的成本——没有持久化,队列活不下来;没有账号,多设备就无从谈起。
反过来说,如果你要的只是一个本地对话框,这套东西全是负担。
这就是我在开头说的:它的定位不是「更好的终端」,是「多个 Agent 的工位系统」。判断它值不值,先判断你有没有那么多 Agent。

结语
回到最开始那句:1 个 Agent 能干一段活,3 个 Agent 一起干就容易乱。
OpenBot 的答案不复杂。给每个 Agent 一个工位,给它一条队列,给它一套传东西的规矩,再给一个只能有一个 owner 的任务板。剩下的,交给人判断。
这套设计里有几处我觉得值得抄:
- 顺序显式化。FIFO 不是技术炫耀,是让「先后」这件事有据可查。
- 权责单一化。一个任务一个 owner,把模糊地带砍掉。
- 边界写死。什么在本机、什么在云上,画得出来才守得住。
- 分层降级。崩一层不至于全崩,这是长跑的必备。
也有几处要有心理准备:配置面变宽、日志会增长、多入口意味着多套脾气、Computer Use 不适合重复劳动。
它不是给所有人的。它是给那些已经在一个屏幕上开了 3 个 Agent,并且开始记不住谁在做什么的人的。
如果你正处在这个阶段,试试把终端换成工位。不是为了好看,是为了让「还剩什么没干」这件事,有一个地方能查到。
如果你已经在多 Agent 协作里踩过坑,也欢迎来云栈社区聊聊你的工位规划和队列安排。
参考
- OpenBot 仓库:
https://github.com/nightly-labs/openbot — 源码、README 与协议说明,本文技术细节的主要来源
- OpenBot 官网:
https://openbot.run/ — 产品定位与下载入口,含商业授权联系方式
- Agent Client Protocol:
https://agentclientprotocol.com/ — ACP 的协议定义,理解多 CLI 接入的公共基础
- PolyForm Noncommercial 1.0.0:
https://polyformproject.org/licenses/noncommercial/1.0.0/ — 非商业许可原文,商用前需确认授权范围