跳转到内容

机台与隔离舱

机台是一台可丢弃的机器,智能体实时操控着它——可能是一个浏览器、一个 Linux 桌面、一个 Android 模拟器、一个 iOS 模拟器,也可能是隔离终端背后的那台机器。多数机台是虚拟机;由宿主机管理的移动端模拟器是明确的例外。本页解释每一道边界,以及这些部件为何被造成现在的样子。要打开一台机台,请参见给智能体一台用于测试的机器;确切的动作、端口与默认值则见机台参考。

原生沙盒把一个进程包进命名空间隔离——这是实打实的,但它与你共用内核,而且约束的是一个进程,不是一台计算机。一旦智能体需要在一个桌面应用里四处点击、在真实浏览器中渲染页面,或者跑一些你宁愿不去想的 rm -rf 实验,诚实的边界就是一台机器:它自己的内核、自己的磁盘、自己的网络栈。

隔离机台使用两种 VM 后端:

  • microVM 用于终端和浏览器——从按摘要锁定的容器镜像启动,虚拟机管理程序亚秒级完成启动。
  • QEMU 用于完整的 Linux 桌面,因为它需要一种 microVM 所没有的显示设备。

移动端机台由宿主机管理,并直说自己的边界更弱:

  • Android 通过 ADB 驱动 Google 的模拟器。
  • iOS 在 Mac 宿主机上创建并拥有一个 CoreSimulator 设备,并通过 WebDriverAgent 驱动它。机台关闭时,该设备及其数据会被删除,但它的网络仍跟随宿主机 Mac。

隔离机台写入的是只读基础镜像之上的一次性覆盖层,因此一台机台无法改变下一台机台的启动基础。iOS 机台拥有一个用完即弃的 CoreSimulator 设备,机台关闭时即被删除。Android 则挂接到一个生命周期长于该会话的宿主机模拟器上。

机台的网络起始于什么都没有。主机按机台逐个放行,放行要经过一道客户机无法绕开的关卡:这台机器通往外部的路径,只有启动配置创建出来的那些。隔离终端获得开发者基线(包仓库、代码托管平台、apt 镜像源);浏览器机台放行产品站点(usectrl.dev),并启动到一个无需出站访问的本地 file:// 页面;其余一切都在连接时被拒绝。

Android 和 iOS 不受这道关卡覆盖。它们在宿主机上运行的模拟器使用服务器主机的网络,因此界面与能力报告会把出站访问标记为未强制,并且绝不提供带有误导性的“以不受限模式重启”控件。

出站访问与凭据是同一套策略的两个侧面——凭据代理(见下文)不会为客户机够不到的主机签发令牌。

机台读到的一切都被视为不可信内容:页面文本、DOM 与无障碍树、界面转储和控制台输出回到智能体时,外面都围着一条常设的“这是数据,不是指令”规则。围栏只是框定,出站访问关卡才是它背后的强制执行。

输入经由虚拟机管理程序,采集经由客户机

标题为“输入经由虚拟机管理程序,采集经由客户机”的部分

在桌面机台上,键盘和指针事件由虚拟机管理程序自己的虚拟输入设备注入。客户机从不运行能够合成输入的提权守护进程——因此攻破客户机的软件,并不会把键盘交到攻击者手上。采集走的是相反方向,经由客户机内一个小型非特权代理,因为客户机可以在画面跨网络传输之前先做缩放。

观看的人与操控的模型,需求恰好相反,因此一台机台提供两条相互独立的流:

  • 人类通道是你面板尺寸下的完整分辨率,随你调整大小而重新协商,由服务器以不透明字节转发——服务器从不解码画面,因为请求路径上放一个视频解码器,正是让它无法应答 RPC 的原因。
  • 智能体通道是降采样后的静态画面,上限为对模型友好的尺寸,每次工具结果一张图;压缩时旧帧会被丢弃,但它们的文字描述会保留下来。

两条通道互不迁就:你得到流畅的画面,模型得到廉价的画面。

独占控制权在每个动作都必经的那个单点上强制执行——而不是靠请模型自觉。当人类持有它时,服务器会拒绝智能体的变更类动作。智能体保留自己的观察类动作(截图、提取),好讲述你正在做什么;接管这件事会经由它的引导通道告知它,而不是让它从被拒绝的点击里去发现。只要没有别人持有那把锁——或者持有者就是你——画布上的点击与输入就照常可用;状态标签显示为你已获得控制权。每一个输入事件——你的和智能体的——都会落进按机台划分的审计日志,带着它的主体和一个单调递增的序号,因此即便机器已经消失,“那是谁点的”依然有据可查。

复制粘贴在两个方向上都可用,文件的拖入拖出也一样——但机台是另一台计算机,整个设计从不假装不是。

剪贴板按需传递,而不是持续镜像。在机台上按下复制组合键,客户机会被告知执行复制,随后它放进剪贴板的任何东西——文本、一张图片、一组文件——都会被取回并放到你的剪贴板上。粘贴把同样的内容送往另一个方向,然后在客户机内部按下粘贴。后台不会同步任何东西:你在自己机器上复制的密码,不会因为某个定时器触发就悄悄出现在虚拟机里。

跨越边界按用户、按方向各自独立把关。粘贴到机台默认允许;从机台复制出去则先弹出询问(可授予十分钟权限,或选择始终允许,该选项位于设置 → 服务器 → 机台 → 剪贴板访问下)。它从来不是工作区设置:一名成员无法替另一名成员的剪贴板表示同意。

在 macOS 上,跨越边界的组合键是 ⌘C/⌘V,不存在歧义——⌃C 仍会以中断的形式送达客户机 shell。在 Windows 和 Linux 上,两者是同一组按键,因此客户机会原样收到它,而只有客户机的剪贴板确实发生变化时,你的剪贴板才会被覆盖。中断始终是中断。

拖入的文件会被复制到机器的投放文件夹,然后再提供给这台机器。所谓“提供”意味着什么,取决于哪些做法是诚实的:浏览器页面会在你松手的位置收到一个真实的拖放事件,因此上传区域的表现和拖入本地文件时一模一样;桌面拿到的则是 ~/Drops 中的文件,其路径放在它的剪贴板上——因为没有任何宿主机能在不于客户机内运行提权守护进程的情况下,向任意 Linux 工具包合成一次拖放,而这恰恰是本设计拒绝拥有的东西。结果会说明实际发生的是哪一种,因此拖到上传区域上的 CSV,绝不会在它仅仅是一个文件夹里的文件时被报告为已上传。拖放到隔离终端上时,文件会被复制进去,并在提示符处键入它的客户机路径,因为 shell 真正能用的就是这个。

拖出的文件只在桌面机台上可用,因为 X11 的拖放协议让载荷在拖放进行中就可读取。在客户机内开始拖动,把它拖出画布,文件就会被取回你的机器,并作为一次真实拖放交给操作系统。在客户机的文件管理器里复制文件、再到宿主机上粘贴,也是同样的道理。浏览器机台刻意不提供这一能力:无头浏览器没有可供读取的拖放选择,而最接近的可用信号——页面的文本选择——根本不能证明存在拖放,据此行事就会劫持普通的点击。

从客户机读出的任何内容都仍属不可信内容:剪贴板文本进入模型时,与页面提取处在同一道围栏之内;审计日志只记录发生过一次粘贴以及内容有多少——从不记录它说了什么。

git push 在你自己打开的隔离终端里可以正常工作,但客户机并不保存任何代码托管平台令牌。它持有的是一个按 VM 一份的机密,其唯一的权力,就是向宿主机的凭据代理索取——经由一条宿主机控制的隧道——一个短时效、限定到具体仓库的令牌:按操作申请、有速率限制、受与出站访问相同的宿主机允许列表约束,并在机台关闭的瞬间吊销。一台生来就要被丢弃的机器,里面不能有任何值得偷的东西。

这个令牌能做什么,在机台打开时就已确定。由人打开的终端机台会拿到一个可写令牌,其权限受该成员自己的代码托管访问范围约束。由智能体打开的机台只拿到只读令牌:git 凭据助手无法区分推送和 fetch,所以无法像智能体运行网关在宿主机上那样,在客户机内为每次推送询问推送规则。

当机台承载你的仓库时,宿主机上的工作树始终是权威版本。那份副本在启动时同步进去(.env 之类的机密文件除外),而提交只在你要求时才回来——取回到 refs/rigs/<id>/*,绝不推送,绝不检出。虚拟机里的任何东西都无法移动你的分支或触碰你的工作树;合并机台产出的内容,永远是人在宿主机上做出的决定。

在终端 VM 内启动的开发服务器会变得可以访问——在你的机器上、在浏览器(VM)中、可选地在你的局域网上、可选地挂在一个带 HTTPS 的开发域名下——而完全不必把机器对外开放。虚拟机管理程序的端口映射在启动时就已固定,因此之后的所有转发都走两条由宿主机控制的狭窄通道:一条预先创建好的入站多路复用器,它拒绝拨向客户机并未真正监听的目标;以及宿主机为客户端发起的连接保持打开的反向隧道。开发域名的 HTTPS 在服务器处终止,使用一份本地签发的证书颁发机构,其密钥从不进入任何客户机。具体机制见从终端转发端口。

机台不可能变成宠物:

  • 硬性 TTL,客户机无法延长;
  • 空闲阶梯——超过空闲窗口后停驻(vCPU 停止,RAM 常驻),达到其两倍后关闭——其中打开着的终端或正在观看的查看者都算作“使用中”;
  • 内存预算,按常驻兆字节而非会话数计算,因为已停驻的 VM 释放 CPU 却保留每一字节 RAM;最先被淘汰的是最久未使用的机器,而且绝不会是有人正在操控或观看的那台;
  • 服务器关闭时拆除,并在下次启动时执行一次带所有权标记的孤儿清理,因此崩溃过的服务器绝不会留下一个无人可管的虚拟机管理程序。

首次启动会拉取机器固定的镜像;随后服务器在后台缓存一份预先解压的副本,因此同类机器之后每次启动都只需数秒,隔离终端启动时就已经装好 GitHub 托管运行器那一档的 CLI 基线——编译器、Python、jq、归档工具——而不是某种语言工具缓存或 Docker。电脑机台的镜像则在构建时把同一套 CLI 基线烘焙进去,与 XFCE 和浏览器放在一起。

“在我机器上是好的”这句话,说的只是一个渲染引擎。因此浏览器机台不是“那个浏览器”——它是Chromium、Firefox 或 WebKit,在你打开它时选定,而一台机台终其一生都运行它被打开时所依据的那一个。一个对话里可以每种各有一台:在 Chromium(VM) 旁边打开 Firefox(VM),你就得到两台机器,把两者指向同一个页面,就是那次比较。WebKit 是所有能跑在 Linux 上的东西里最接近 Safari 的——引擎同源,因此 Safari 用户撞上的缺口它也能捕捉到。

三者的动作完全相同;底下的协议则不然,差异会被明说,而不是被抹平。Chromium 讲 DevTools Protocol,并推送帧,因此页面静止时它的实时画面不产生任何开销。Firefox 讲 WebDriver BiDi,WebKit 讲经典 WebDriver,两者都没有屏幕串流,因此它们的实时画面靠轮询——每秒几帧,这适合观看页面,而不适合观看动画。当某个引擎确实做不到某件事时(Firefox 没有绕过缓存的重新加载;WebKit 看不到页面加载完成之前记录的控制台输出),给出的答复会如实说明,而不是悄悄改做别的事。

机台刻意不是一个全局目的地。实时画面是工作所在的空间或 PR 里的一个标签页,作用域限于那个对话——因此你的标签页和智能体的 browser_use 调用指向的是同一台机器(在浏览器机台上,每个引擎一台)。机台能否启动是宿主机层面的属性,因此能力、镜像、按工作区的自定义镜像以及运行中的机器列表都在设置 → 服务器 → 机台下。操控隔离舱属于它自己的护栏动作类别,因此工作区、智能体或空间可以允许它、要求批准,或直接拒绝——只读模式则一概拒绝。