架构重构背景与去 VM 痛点
前阵子,我们正式把 camelAI Agent 从传统的虚拟机(VM)架构里搬了出来。现在,Agent 直接运行在 Cloudflare Durable Object 中,文件系统寄存在 SQLite 和 R2 上,执行逻辑也不再调 Bash 脚本,而是直接写 JavaScript。市面上绝大多数团队在跑 Coding Agent 时,都会选择完整的 Linux VM 或容器沙箱,我们过去也不例外。
之所以痛下决心砍掉 VM,原因很简单:给每位用户都分配一台带挂载磁盘、常驻运行的虚拟机,成本高得根本算不过来账,规模越大越撑不住。但难就难在,现有的 Coding Agent 几乎都是基于 Linux 假设训练出来的——模型张口闭口就是跑 Bash,而我们最初选用的 Agent 框架又强依赖完整 VM 环境。为了走到今天这一步,我们先后迭代演进了整整三版架构。虽然这种方案带来了一定折中——Agent 现在只能调用我们显式封装好的 API 方法,听上去似乎限制了自由度,但从产品落地和稳定性的角度看,这反而是一笔极划算且极正向的交易。
我是 camelAI 的 CTO Miguel。前段时间我们的代码库已经正式开源,本文提到的所有设计和实现,都能在开源代码库中找到源码。接下来我会按演进路径,结合具体模块带大家拆解整个改造过程。
阶段零:虚拟机时代与自建容器服务
项目早期,我们直接基于 Claude Code Harness 搭建。由于这套 Harness 依赖完整的虚拟机环境,我们先后试过几家 VM 托管商,但没有任何一家能在数据持久化和性能表现上满足我们的要求。逼不得已,我们甚至自己动手写了一套容器托管服务。(虽然那篇技术博文还在,但这套底座我们早就全量下线了。)
那套容器服务虽然能跑通,但体量太重了。让每位用户独占一台常驻 VM 成本高昂,更不用说还得在高速挂载盘上持久化存储用户的所有文件。想做大用户量,就意味着要硬抗物理服务器和云盘的算力账单,这在我们预期的用户体量面前简直是天方夜谭。于是,我们决定不再花心思去搞什么精妙绝伦的 VM 调度优化,而是换个思路:能不能直接把 VM 彻底从架构里拔掉?
阶段一:大脑剥离与 Durable Object 结合
Claude Code Harness 和它的 VM 绑定得太死,所以第一步,我们必须开发一套属于自己的 Harness。我们选择了 Mario Zechner 开源的 Coding Agent 工具库 pi。pi 本质上一组分层清晰的库:顶层会假设宿主是个标准操作系统,但底层库只提供 Agent 循环、状态管理等原生原语,完全不关心具体运行环境。我们没有修改 pi 的任何核心代码,直接引入其底层模块,并在 Cloudflare Durable Object 内部搭出了我们自己的 Harness,脱离了 Linux 环境的限制。
Durable Object(简称 DO)是一种有状态的微型计算实例,直接在离用户最近的 Cloudflare 边缘节点上按需拉起。我们给每个对话 Thread 独立分配一个 DO,光是省去把请求往中心化 VM 主机路由的耗时,就把交互延迟大幅拉了下来。
在这个阶段,虽然后台依然保留了 VM,但 Agent 的“大脑”已经挪出了虚拟机。只有当真正需要执行命令时,Agent 才通过远程调用的方式控制 VM 动手。这种设计恰好契合 Anthropic 在托管 Agent 中提出的 “大脑与双手分离” 理念,并带来了非常惊艳的优势:
- 响应零等待:Agent 生成首字响应时无需等待虚拟机冷启动拉起。
- 按需休眠:如果这一轮对话不需要执行终端命令,虚拟机完全不用唤醒;即便调了命令,工作完成后虚拟机也能立即退回休眠状态。
- 一脑多手:单个 Agent 大脑可以同时操控多台虚拟机协作。
大脑与双手分离的远程控制链路
-
Durable Object 逻辑处理
Agent 大脑在边缘 DO 节点秒级启动并生成交互响应。
-
远程指令下发
仅在需跑终端命令时,向远端 VM 发送远程控制请求。
-
任务完成即刻休眠
VM 执行完毕返回结果后迅速进入休眠,节省常驻算力开销。
我们将被操控的“双手”称为项目(Project)。每个项目分配一台用于跑命令的 VM,同时通过 Cloudflare Artifacts 动态创建 Git 仓库(Artifacts 是 Cloudflare 提供的兼容 Git 的存储服务,支持在 Worker 中按需即时开辟)。在这个阶段,Agent 自身其实感觉不到有什么变化——它依然可以肆意跑 Bash 终端,工作方式与常规 Coding Agent 别无二致。
但这个方案治标不治本:它解决的只有延迟问题,成本却丝毫没降。每个人一台常驻 VM 的老大难问题依然存在,扩容压力与账单风险原封不动。
阶段二:干掉虚拟机与 SQLite + R2 文件系统
在第二版演进中,我们保留了项目(Project)的组织结构,但把挂在后台的 VM 彻底给废除了。现在,每个项目的文件系统都直接寄宿在 Durable Object 内部,超大文件则无缝挂载到 R2 对象存储。
这套方案并非我们凭空捏造,而是大量复用了 Cloudflare Agents 团队开发的实验性项目 Shell(一个专为 Workers 打造的文件系统与执行运行时)。它的底层原理非常清爽:Durable Object 内部自带一个单库上限 10GB 的 SQLite 数据库,单行数据也有体积限制。因此小文件直接存进 SQLite 的数据行中;一旦文件超过 1.5MB,就自动转存到 R2 对象存储,SQLite 里只留一份指针引用。在 Agent 眼里,这依然是个标准的文件系统,但底层已经彻底脱胎换骨为**“数据库与对象存储的组合”**——持久化变成了静态数据存取,再也不用维持重型服务器基础设施常驻了。
至于版本控制,依然由 Artifacts 接管,这样每个项目无需自建或托管 Git 服务,就能天然拥有完整的 Git 历史记录。
阶段三:全面移除 Bash 终端与 JS 沙箱安全机制
把 Bash 砍掉听起来是个很疯狂的决定。毕竟现有的 Coding Agent 几乎都是盯着 Bash 训练出来的,大家之所以硬着头皮跑 VM,底层原因也就是为了给 Bash 提供宿主。此外,Bash 带来的隐患远不止成本:一个拥有完整网络访问权限和 Bash 终端的 Agent,要干活就必须拿到各种 API 凭证,而我们之前尝试搞的鉴权代理 URL 越写越别扭,安全策略极难严丝合缝地实施。
所以我们干脆一不做二不休,把 Bash 彻底卸了。取而代之的是让 Agent 直接编写 JavaScript 代码,并通过 Code Mode 以及 Cloudflare 的 Dynamic Worker Loaders 动态执行。每次代码运行都在一个全新的 V8 Isolate 沙箱里完成,冷启动仅需数毫秒,内存占用极微小。沙箱内预装了用户的数据连接以及平台能力对应的显式方法,任何敏感凭证绝不透传给沙箱——Agent 只需调用连接的方法,具体的鉴权过程全部在我们安全受控的后端闭环。
仔细盘点一下 Agent 平时跑 Bash 究竟在干什么,就会发现丢掉 Bash 的损失远比想象中要小得多。绝大部分动作无非是文件读写与搜索,这些动作直接给 Agent 暴露原生工具(读取、写入、修改,加上我们自定义的 grep 和 glob 实现)就能搞定,直接收割二八定律里的 80% 的需求。至于剩下的 20% 特例命令,我们全部收拢收窄,封装成了显式 API:
- 部署流程:原本通过代理层跑 wrangler deploy,现在收归为完全受控的 deploy_project 方法。因为能精准掌握部署触发时机,我们可以在部署完成瞬间自动挂载 Live Preview 预览窗口;而在以前,我们不得不去抓代理层里的 wrangler 流量,靠猜来判断是哪个 Thread 触发了部署。
- 应用构建与 Notebook 运行:构建前端应用和执行 Python Notebook 分别收拢为独立 API,底层均由短命容器保底支撑。
之所以在这两个场景下保留容器,是因为它们是真的离不开 Linux。用户创建的应用通常基于 Vite、Tailwind 和 React Router 栈,安装新依赖就免不了跑 bun install。我们曾考虑过直接在 Worker 内部跑 Build(毕竟构建产物本身也是 Worker),但这条路基础设施支持极差,况且 Worker 限制 128MB 内存和极低的 CPU 算力分片,构建过程极其卡顿,分分钟爆内存。因此,现在的方案是:构建触发时,通过 Cloudflare Sandbox SDK 秒级拉起一个临时容器,把代码同步进去,跑完 Build 返回结果后立即销毁容器。Notebook 的执行逻辑完全一致。我们依然在使用完整的 Linux,但仅限于那真正需要它的区区几秒钟。
坦白讲,这套方案也有显而易见的短板:我们必须提前预判并收录 Agent 所需的所有能力。以前有 Bash 时,Agent 可以自己摸索命令解决问题;而现在一旦缺失某种能力,我们就必须手动补上 API。不过在实践中,这种倒逼机制反而对产品大有裨益——它逼着我们深入思考用户的真实操作路径,去打造一等公民级别的原生体验,而不是放任 Agent 在终端里乱搞凭感觉发挥。
全新架构成本收益与总结
梳理一下我们目前的全新技术栈:用 Durable Objects 承载 Agent 逻辑及核心文件系统,用 R2 存储大文件,用 Artifacts 管理 Git 变更历史,用 pi 库作为基础 Harness,再配合 Code Mode 与 Dynamic Workers 完成代码安全执行。整套架构的部署流程和普通 Cloudflare 应用完全一致,再也不需要去运维和治理任何复杂的外部容器服务。
Dynamic Workers 按实际执行次数计费,而不是按时长按秒扣钱。跑几千次代码执行的成本,甚至只相当于过去容器服务跑几分钟的开销。由于所有逻辑均在靠近用户的 Cloudflare 边缘节点就近运行,交互延迟低到极致,至于高并发扩容问题,直接丢给 Cloudflare 基础设施即可。
在用户感知层,他们依然可以正常构建、部署全栈应用并获取公网 URL;Agent 也依然能流畅地读写文件、搜索代码、一键部署。从用户视角来看,体验没有受到任何削弱,甚至更快更顺畅了。