计算机运行时

Doitx 将智能体运行时和计算机运行时分开:

chat/API -> one Pi agent session -> Doitx computer tools -> SandboxProvider -> E2B / Daytona / Boat
                                                   |-> Docker
                                                   |-> desktop/fake

SandboxProvider workspace <-> AgentHomeStore <-> Doitx-owned DATA_DIR

Pi 在 Doitx API/worker 进程中运行。它不会被安装到 E2B 中,也不由 E2B 执行。内置工具是普通的 Pi 工具,而非 Claude 或 MCP 专用工具,因此通过 Pi 暴露的任何模型都可以调用它们。屏幕操作仍需要能够接受图像工具结果并推理截图的模型。

计算机约定

每个空间默认获得一台 Team Computer。Bot 共享其文件和已安装的工具。每个 Team Bot 从 bots/<bot-id>/ 启动,而有意共享的工作放在 shared/ 中。这些文件夹用于组织工作,但不是安全边界:每个 Team Bot 都能访问完整的 Team 工作区。Bot 也可以改用 Private Computer,此时整个工作区就是它的主目录。

每个活跃的 Team Bot 都有自己的 X 显示和 Chrome 进程,并有一个以该 Bot 身份为键的持久 Chrome 配置文件。登录状态、cookie 和浏览历史相互独立。配置文件永远不会从另一个 Bot 克隆、合并,也不会在桌面释放时被删除。两个 Bot 都保留各自的更改;重新打开某个 Bot 时即使其显示槽位发生变化,也会使用其现有配置文件。Team 运行使用带栅栏的按 Bot 数据库租约:不同的 Bot 可以并发操作,而一个 Bot 同时只有一个计算机驱动。

Docker、E2B、Daytona 和 Boat 使用相同的 Linux 桌面生命周期命令。远程适配器共享分配、观察、操作和控制处理;提供商代码提供命令执行、持久工作区路径和屏幕 URL。Boat 通过其 hostPort API 的受保护端口(而非默认桌面 API)暴露共享运行时。查看和控制使用各自可撤销的 websocket 能力;拆除会在显示槽位被复用前断开客户端。拆除失败会使槽位保持保留状态以便重试。

桌面栈在 Bot 使用图形工具时才惰性启动。默认没有配置的桌面上限;一台计算机可以承载 100 个或更多 Bot 配置文件,同时运行的桌面数受其 RAM、CPU、进程容量和可用的本地调试端口限制。Docker 运维人员可以设置一个正的 SANDBOX_TEAM_SCREEN_LIMIT 来限制活跃桌面数(0 表示不设置)。超出显式限制的请求会返回 MULTI_SCREEN_UNAVAILABLE;shell 和文件工具仍然可用。所有桌面共用一个受令牌保护的屏幕网关,因此增加桌面无需发布更多端口。每个查看/控制能力都指向唯一的本地 Unix socket;回收显示槽位无法将旧连接重定向到它的下一个 Bot。非活跃 Bot 不会各自运行 Chrome。关闭查看器不会破坏桌面。在 Docker 上以及 Boat、E2B、Daytona 和 Railway 计算机的 Linux 桌面上,成功完成运行会撤销该运行的控制权,但会为该 Bot 保留屏幕和浏览器以供下次运行,保持现有视图打开及其已登录会话。确实重启的浏览器会恢复其上次会话。如果达到配置的 Team 屏幕限制,最旧的空闲、未受控屏幕可能会被拆除以腾出给新 Bot。失败或取消的运行以及显式删除计算机仍会拆除其屏幕。CreateOS 屏幕照旧释放。整机空闲关闭会停止剩余进程,同时保留工作区。没有针对每个桌面的单独不活动计时器。持久配置文件会占用磁盘,但不需要运行中的 Chrome 进程。

SandboxProvider 是提供商边界。后端必须实现:

  • 生命周期:预配/重新连接、停止和销毁;
  • 桌面:观察、有序批量操作、用户输入和实时屏幕会话;
  • 执行:在机器内运行命令;
  • 文件:列出/读取/写入,以及完整的工作区导入/导出。

在受支持的图形计算机上,模型会获得 browser_navigate、browser_snapshot 和 browser_act,用于获取页面文本和元素引用。默认的 computer 浏览器提供商通过 sandbox 的可选 pageBrowser 契约驱动可见的 Chromium 标签页。Docker 通过与桌面控制相同的受管屏幕和租约检查来实现该契约;请更新计算机镜像以安装辅助程序。其他提供商在实现该契约之前保留其现有的桌面工具。无需托管浏览器服务或 API 密钥。

计算机容器就是安全边界。Team Bot 共享 OS 用户、工作区、浏览器配置文件和 shell/X11 访问;屏幕租约协调的是工具调用,而不是该计算机内部互不信任的进程。CDP 仅绑定到容器的回环接口,不会作为主机端口发布。当工作负载需要隔离时,请使用独立的计算机。

该辅助程序使用隔离的脚本环境,屏蔽密码值,并拒绝过期引用,而不是重定向到替换后的元素。快照包含有界的页面文本和最多 80 个交互元素。框架和不支持的交互需要桌面工具。失败的操作会报告已确认的进度以及结果是否不确定:继续之前检查当前状态,永远不要自动重放已完成或结果不确定的操作。对于没有视觉能力的模型,如果页面工具无法操作,请请求接管。

虚拟计算机和显式的 BROWSER_PROVIDER=fake|emulator 使用进程内会话进行测试。这些会话不是实时登录的浏览器。浏览器变更共享现有的教学保护和工作中区检查点流程。computer_observe、批量 computer_act、open_path、launch_app、shell 和文件工具会根据计算机和模型能力保持可用。连续相同的桌面帧会保留其元数据,但从模型上下文中省略重复的图像字节。工具结果会说明上一张屏幕截图仍然有效。在重复相同的视觉操作后画面仍无变化时,computer_act 会拒绝再次执行该操作,并引导智能体使用页面文本工具。

人类输入和智能体输入可以在不同的 Team 屏幕上共存。“Take control”(接管)会为用户授予该 Bot 屏幕的独占控制租约,使嵌入式查看器接受输入。对于 Team Bot,当该 Bot 持有实时的计算机执行租约或存在活跃运行时,接管会以 HTTP 409(“Stop the bot first”)被拒绝,除非该运行处于 waiting_takeover(Bot 请求了受保护输入)。先停止 Bot,再接管;释放后,智能体可以继续。当模型明确需要受保护输入或人工判断时,request_takeover 仍然可用。在运行期间不计费的提供商上,等待中的接管会在请求后将计算机保持唤醒 30 分钟,以便用户到达时登录、2FA 或 CAPTCHA 页面仍处于打开状态;之后计算机会挂起,而运行继续等待。计费提供商遵循正常的空闲计时器,该请求会重新启动计时器。

终端和文件

Web 和桌面计算机视图会从屏幕上的 dock 打开终端和文件浏览器。dock 的浏览器按钮会隐藏这些窗口并保留其会话,从而再次显示整个屏幕。

终端

  • Activity 视图始终显示 Bot 在其计算机上做了什么,包括实时和历史记录。每个操作都记录为 computer.command 事件:

    • shell 命令,带有脱敏后的命令及其输出的末尾部分;
    • write_file、attach_file、open_path 和 launch_app,各占一行,写入操作带有大小,失败时带有错误。

    只读工具(read_file、list_files)被排除。用户持有控制时 Bot 仍可运行命令,因此该信息流从不消失。

  • 在无控制时(例如控制权被释放或过期后),“Open shell” 按钮会重新接管并切换到 shell。

  • 持有控制的用户还会获得一个带有交互式 shell 的 Shell 标签页。它在首次使用时启动,在标签页切换之间保持连接,并在连接断开时重新连接到新的 shell。computer.terminalUrl 会在计算机内屏幕网关旁边启动一个小型 PTY 服务器。

    • 它绑定到该显示的控制令牌,并通过与控制屏幕相同的密封能力和网关访问。同一租约下的后续会话(另一个标签页、重新打开的窗口)会以各自的 shell 加入该服务器,因此已打开的 shell 会继续运行。
    • 它以计算机的工作区用户(绝不以 root)运行,环境与 Bot 的 shell 工具相同。Docker exec 会继承容器的非 root 用户;E2B、Daytona 和 Boat 使用与 shell 相同的命令运行器。当计算机以没有 passwd 条目的主机 uid 运行(macOS 上的 Docker)时,终端会通过 nss_wrapper 为其自身会话将其命名为 rakazo,因此提示符和 whoami 可以正常工作;/etc/passwd 保持不变。
    • 释放控制、过期或屏幕拆除会停止它,断开所有 shell,并删除其会话文件。
  • 提供商通过 SandboxProvider.connectTerminal 选择加入。Docker、E2B、Daytona 和 Boat 支持它。主机(desktop)计算机从不暴露浏览器 shell;其他不支持它的计算机仅显示 Activity。

  • 虚拟提供商通过一个回环 websocket 网关提供模拟 shell,该网关使用相同的帧协议。测试随后可以通过密封能力和 Web 代理驱动浏览器终端,而无需暴露主机 shell。

浏览器代理

  • 可以将 Bot 配置为通过上游代理浏览(参见 self-host.md 中的“Per-bot browser proxy”)。由于 Chromium 无法携带代理凭据,启动时会在计算机内的 127.0.0.1 上启动 browser-proxy-relay.py,并传入 --proxy-server=http://127.0.0.1:<port>。
  • 中继端口派生自 Bot 的浏览器配置文件路径,因此共享一台计算机的两个 Bot 会获得各自的中继。回环保持直连,因此 CDP 和屏幕路径从不经过上游代理。
  • 中继由 rakazo-browser 随浏览器一起启动,并在浏览器退出时停止;只影响该 Bot 的浏览器。

文件

  • 浏览和文本预览可通过已存储的工作区在已停止的计算机上进行。
  • 当 Files 窗口可见时,打开的文件夹每隔几秒以及每个 Bot 命令之后都会刷新,因此来自 shell 或 Bot 的更改无需重新打开即可显示。
  • 下载需要计算机处于运行状态。
  • 上传还需要控制权。上传内容位于 Bot 的工作区路径下,并受附件大小上限限制。

屏幕连接诊断

Web 服务会记录 screen.proxy.target_rejected、screen.proxy.http_failed、screen.proxy.http_upstream_response、screen.proxy.websocket_handshake_failed、screen.proxy.websocket_error、screen.proxy.websocket_upgraded、screen.proxy.websocket_closed 以及撤销事件。每个事件都包含一个随机的 screen.connection_id 和查看/控制策略。WebSocket 关闭事件包含上游握手是否完成、其持续时间以及发起关闭的一方(client、upstream 或 revoked)。日志省略能力 URL、socket 令牌、提供商主机名、cookie 和请求头。

Web 和 Electron 屏幕框架是沙箱化的,因此应用无法查看其内部。在为代理页面加框之前,应用会先获取一次该页面,并在屏幕位置显示固定的、本地化的代理拒绝原因(例如 “The screen did not respond.”)。代理会用一个小的报告器重写每个 noVNC 页面,该报告器在页面运行后立即向应用发送 loading,然后发送 connecting、connected、failed(其状态文本永不显示)或 disconnected;Doitx 自己的 embed.html 设置相同的 noVNC class,因此 Docker 计算机也会报告。收到 failed 报告,或在 15 秒内没有超过 loading 的任何消息,会显示错误和 Retry,而不是空白框架。移动应用遵循相同的屏幕报告,并在连接失败时提供 Retry。保持静默的框架意味着文档从未在该浏览器中执行;停在 loading 的框架意味着 noVNC 的模块从未完成求值;failed 报告意味着页面已运行但 noVNC 无法连接。

该 shim 还会从页面中移除 VideoDecoder。1.7 之前的 noVNC 版本使用顶层 await 探测 WebCodecs H.264 支持,而在某些 Android 设备上 VideoDecoder.flush() 永不返回,导致页面停留在 noVNC_loading 且没有错误。屏幕从不使用 H.264。每个代理的屏幕响应都会带上 Cache-Control: no-transform,因此位于 Web 服务前面的 CDN 不会向页面注入脚本或压缩页面。

当屏幕显示连接错误时,检查 API 日志中 computer.screenUrl 是否成功,然后检查同一时间附近的 Web 服务 screen.proxy.* 事件。对于 screen.proxy.target_rejected,读取 reason:invalid_path 表示不是会话路径(未联系 API);authority_rejected 表示非 OK 的 API 响应(过期、撤销或 API 错误);invalid_authority_response 表示 OK 响应但目标正文无效或格式错误;authority_unavailable 表示请求、连接或超时失败。上游错误或非 101 握手指向提供商屏幕网关。已升级但很快关闭的连接指向握手后的传输中断。如果 API 签发了 URL 但没有出现 Web 服务事件,请检查浏览器 Network 面板及其 WebSocket 请求。不要将完整的能力 URL 粘贴到 issue 或日志中。

E2B 后端

E2B 适配器使用 @e2b/desktop,Novita 适配器使用 novita-sandbox 来处理机器生命周期、shell 命令、文件和端口 URL。每个 Bot 桌面都使用共享的 Linux 运行时,包括第一个 Bot。其 X 显示、屏幕截图、输入和查看/控制传输遵循与其他受管提供商相同的生命周期。

Daytona 后端

数据库存储提供商类型和不透明的 providerRef。该引用是一条加速路径,而非持久数据。它只会传回同一提供商类型。机器缺失或提供商类型更改会创建替代机器,并通过提供商中立契约恢复其工作区。

Boat 后端

Boat 适配器使用 Boat 官方的 TypeScript SDK 进行生命周期、命令和文件操作。它以 noEnv: true 创建和恢复 sandbox,这是第三方提供 API 密钥时的要求,并在计算机活跃期间保持两小时 TTL 刷新。共享运行时创建每个 Bot 的显示和 noVNC 传输,通过受保护的 Boat 端口托管暴露。Doitx 的加密屏幕能力代理绑定查看/控制策略,并使提供商凭据不出现在浏览器可见的 URL 中。观察和操作针对所分配的 Bot 显示。

Boat 停止会归档机器,恢复会重新连接同一个不透明的 sandbox id。每个 Bot 的 Chrome 配置文件位于可移植工作区下,并包含在检查点/导出中。API 无法恢复的 Boat 状态(已删除、已取消、快照数据过期,或恢复无法修复的 error)会被报告为 gone,因此计算机会被替换并从其最后一个检查点恢复。

持久化

可移植的计算机工作区是持久边界。E2B 使用 /home/user/rakazo-home;Docker 和本地提供商暴露等效的主目录。在 E2B 上,浏览器配置文件位于该工作区下的 .browser-profiles。Doitx 会在运行完成或失败时、显式停止之前以及空闲挂起之前,将传输的工作区检查点保存到 AgentHomeStore。Docker 直接挂载 Doitx 拥有的主目录,并且仅在这些边界推进其修订标记。新机器或替代机器在使用前会导入最新存储的工作区。

LocalAgentHomeStore 将最新工作区保存在 DATA_DIR/homes/<computer-home-key> 下,并将检查点元数据单独保存在 DATA_DIR/home-revisions 下。替代内容会在交换当前副本之前暂存,检查点按计算机串行化。此实现仅保留最新版本,而不是不可变的修订归档。使用它的部署必须将 DATA_DIR 放在 Doitx 拥有的持久卷上,对该卷进行静态加密,并将其纳入异地备份。

设置 OBJECT_STORAGE_BUCKET 后,ObjectAgentHomeStore 会将 S3 兼容的桶作为事实来源,并将 DATA_DIR/homes 作为缓存,而不改变智能体工具或 sandbox 提供商。每个检查点会将主目录打包成由目录和常规文件组成的单个归档,用主目录的数据密钥密封(64 KiB AES-256-GCM 分段,绑定到主目录的存储 id 和修订版本),以 homes/<storage id>/<revision> 上传,在计算机行上推进修订版本,并删除上一个对象。读取和启动会首先将缓存更新到已存储的修订版本。数据密钥由空间的内容密钥包装并存储在计算机行上,因此删除计算机会使其对象无法读取;worker 的协调器随后会删除没有任何计算机拥有的对象。恢复仅接受不包含 . 或 .. 段的相对路径,只创建目录和常规文件,并强制执行大小和条目限制。参见 自托管。

在导出远程工作区之前,远程后端会静默桌面浏览器,以便一致地复制配置文件数据库和登录状态。当另一个 Bot 持有执行或用户控制租约时,运行检查点会推迟;最后完成的运行或空闲作业会保存共享工作区。空闲关闭会在导出前占用计算机,防止在快照期间启动新 Bot。它们只排除 .browser-profiles 内的临时缓存/锁文件;名称相似的项目文件仍会持久保留。

一次性的 OS 镜像不是可移植的磁盘快照。迁移到另一个提供商时,安装在工作区之外的系统包会丢失;持久的机器定制应通过可复现的镜像或设置配方来表示。这正是使未来切换后端切实可行、而不必尝试转换供应商特定 VM 快照的原因。

Docker 计算机包含 uv,用于无 root 的 Python CLI 安装。运行 uv tool install <package>;工具环境、命令 shim、受管 Python 版本和缓存都保存在持久主目录下。这安装的是 Python 命令行工具,而不是 apt 依赖之类的系统包。镜像还附带 GitHub 的 gh CLI;Bot 通过设备流对该 CLI 进行认证,gh 会将该凭据保存在持久主目录下。pdftotext、pandoc 和 openpyxl 可用于从 PDF、文档和电子表格中提取文本。

验证

智能体验证指南 还描述了通过真实 Pi 进行的确定性联系人导出回放,可使用有状态的虚拟计算机或真实的 Docker Chromium。它不需要推理,并补充了下方的视觉验收测试。

离线测试覆盖工具结果图像、操作解析、提供商一致性(包括页面浏览器适配器和 computer_act 回退)、工作区检查点/恢复、提供商 SDK 转换、生命周期集成,以及针对内存中 Boat API 的真实 Boat 适配器。它们从不调用模型或实时 sandbox。

显式验收测试需要 Docker(用于临时 Postgres)、E2B_API_KEY、OPENROUTER_API_KEY 以及一个支持视觉的 OpenRouter 模型 id:

COMPUTER_E2E_MODEL=<vision-capable-openrouter-model-id> pnpm test:computer

它会启动完整 API,预配真实的 E2B 桌面,在 sandbox 内提供确定性页面,并要求真实模型观察并点击一个按钮。该按钮会创建服务器端标记;测试随后要求模型使用终端和文件工具,并验证该标记和记录的工具调用。最后,它销毁提供商机器,通过过期的提供商引用启动替代机器,并验证外部检查点恢复了模型创建的文件。该命令为选择性启用,除非显式调用,否则不会由 pnpm test 或 CI 运行。

Docker 桌面生命周期回归

构建计算机镜像,然后运行 VERIFY_DOCKER_TEAM_SCREENS=1 pnpm exec vitest run infra/sandboxes/supervisor/src/team-desktops.docker.test.ts。设置 RAKAZO_COMPUTER_IMAGE 以选择预构建镜像。该测试使用一个禁用网络且浏览器状态为虚拟的隔离 Docker 容器;它验证并行 Chrome 桌面访问本地 fixture 站点、独立的 cookie、释放后的配置文件持久化、传输拆除,以及槽位复用后拒绝旧查看/控制令牌。它会同时运行 Docker 监管和远程提供商使用的命令路径。默认单元测试在没有 Docker 的情况下离线验证配置文件持久化、分配和租约栅栏。

计算机维护

更新和恢复作为持久后台作业运行。在操作保存和替换工作区期间,计算机范围的预留会排除新的执行租约、停止、接管、空闲挂起和计算机模式更改。进度与计算机的预配栅栏分开存储。Web 和 Electron 显示对话框或紧凑的后台药丸;移动端使用原生 sheet。重新打开应用会从 API 恢复活跃操作和失败。

更新会使用配置的提供商/镜像显式重建;canUpdate 表示计算机支持该操作,而不是检测到了更新的镜像。提供商迁移和镜像版本发现不属于此 UI 的一部分。更新会在拆除前对实时工作进行检查点。恢复可以回退到最后一个已保存的工作区,因此其失败操作会警告未保存的工作可能丢失。

协调器会在错过入队后重新发布排队的操作。已被认领的作业永远不会被破坏性重放。停止心跳十分钟的 worker 会被标记为已中断,并保持预留,直到其提供商调用结束。只有到那时恢复才可用。如果某个 worker 已永久消失,运维人员必须先停止受影响的 worker,并确认提供商操作已停止,然后才能使用中断对话框中仅限服务器所有者的 Release computer(释放计算机)操作。其确认要求显式断言 worker 和提供商操作已停止,然后使正常恢复可用。对应的 RPC 是 computer/releaseInterrupted,参数为 { id, workersStopped: true };它只接受所有者当前工作区中的已中断操作。仅有过期心跳从不授权接管。进度报告实际的生命周期阶段,而不是估计的百分比;工作区文件和浏览器配置文件是可移植的,而工作区之外的系统包则不是。