# We rewrote our agent to run entirely in a Durable Object with Pi, Agents SDK and Code Mode
**作者**: Miguel Salinas
**日期**: 2026-07-28T16:19:18.000Z
**来源**: [https://x.com/Vercantez/status/2082138839888589200](https://x.com/Vercantez/status/2082138839888589200)
---

We recently finished moving the camelAI agent off of virtual machines. The agent now runs inside a Cloudflare Durable Object, its filesystem lives in SQLite and R2, and it writes JavaScript instead of bash. Most teams run coding agents in a full Linux VM or container sandbox, and we used to as well.
We wanted off VMs because giving every user an always-on machine with attached disk was too expensive to scale. The hard part is that coding agents assume Linux. They are trained to reach for bash, and the harness we launched on required a full VM, so getting here took three redesigns. The tradeoff is that the agent can now only do things we've built an explicit method for, which sounds limiting but has been good for the product.
I'm Miguel, CTO of camelAI. Our codebase recently went open source, so everything in this post is code you can read at github.com/qaml-ai/camelAI. I'll link to the relevant files as we go. Here's the progression.
## Step zero: the VM era
We launched on the Claude Code harness, which needs a full virtual machine to run. We tried several VM providers, none of them fit our persistence and performance requirements, and we ended up building our own container service. That post is still up, but we no longer run any of that infrastructure.
The container service worked, but it was heavy. An always-on VM for every user is expensive, and so is holding every user's files on fast attached disk. Scaling it means scaling real machines with real disks, which was going to be prohibitively expensive at the user counts we're aiming for. So instead of getting clever about VM orchestration, we started designing around not needing a VM at all.
## Step one: take the agent out of the VM
The Claude Code harness is inseparable from its VM, so the first move was building our own harness. We built it on pi, Mario Zechner's open-source coding agent. pi is a stack of libraries. The highest layer assumes a normal operating system, but the lower layers give you the agent primitives, like the agent loop and state management, without caring where they run. We didn't change any pi code. We imported those lower layers and built our own harness on top of them, running inside a Cloudflare Durable Object instead of a Linux environment.
A Durable Object is a small stateful compute instance that spins up on Cloudflare's edge, close to the user who created it. Each chat thread gets its own Durable Object, which brought latency down on its own compared to routing everything through a centralized VM host.
At this stage we kept the VMs, but the agent no longer lived inside one. It called into the VM remotely when it needed to run commands. Anthropic describes this same split for its managed agents, the brain separated from the hands. It gave us some nice properties:
- The agent starts responding before the VM is awake, because it doesn't wait on a machine to boot.
- The VM can go back to sleep while the agent keeps working, or never wake up at all if the turn doesn't need any commands.
- One brain can control multiple hands. A single agent could operate several VMs at once.
We call those hands projects. Each project came with a VM for executing commands and a git repo created programmatically through Cloudflare Artifacts, which is git-compatible storage you can provision on the fly from a Worker. The agent didn't really know it was running outside the VM. It still had bash and worked like any other coding agent.
The problem is that this fixed latency and nothing else. We still had a VM per user, so we still had all of the cost and scaling problems from the original design.
## Step two: remove the VM
The next version kept the same project structure but dropped the VM behind it. Each project is now backed by a filesystem that lives inside a Durable Object, with R2 behind it for larger files.
We didn't invent this. Cloudflare's agents team built Shell, an experimental filesystem and execution runtime for Workers, and we reused their code heavily. The mechanics are simple. A Durable Object's storage is a SQLite database with a 10 GB cap, and each row has a maximum size. Small files live directly in SQLite rows. Files over roughly 1.5 MB get written to R2, and the SQLite row just holds a pointer. To the agent it looks like a normal filesystem, but underneath it's a database and object storage, so persistence is stored data rather than infrastructure we have to keep alive.
Version history still runs through Artifacts, so every project keeps a git history without us hosting a git server.
## Step three: remove bash
Removing bash felt drastic. Coding agents are trained to reach for bash, and bash is why everyone runs them in VMs in the first place. It was also a problem beyond cost. An agent with bash and network access needs credentials to do anything useful, and our attempts at authenticated proxy URLs were getting hacky and hard to enforce.
So we removed it. Instead of bash, the agent writes JavaScript, executed through Code Mode and Cloudflare's dynamic Worker loaders. Each execution runs in a fresh V8 isolate that boots in milliseconds and uses a few megabytes of memory. The sandbox comes pre-loaded with the user's data connections and with methods for everything the platform can do. Credentials never enter the sandbox. The agent calls a connection's methods, and authentication happens on our side.
When you look at what agents actually use bash for, losing it costs less than you'd expect. Most of it is file operations, which the agent has native tools for. We give it read, write, and edit, plus our own grep and glob implementations. That covers the 80-20. The rest is specific commands for specific jobs, and those became explicit methods:
- wrangler deploy through a proxy became a deploy_project method we fully control. Since we know exactly when a deploy happens, we can hook it and open a live preview automatically. Before, we had to sniff proxied wrangler traffic to guess which thread had deployed something.
- Building the user's app and running Python notebooks became their own methods, both backed by short-lived containers.
We kept containers for those two jobs because they genuinely need Linux. User apps are built with Vite, Tailwind, and React Router, and adding dependencies means running bun install. We considered running builds inside a Worker, since the thing being built is itself a Worker, but that path isn't well supported, and Workers have a 128 MB memory limit and a fraction of a CPU. Builds would be slow and plenty of projects would blow past the memory cap. So instead a build spins up a container through the Cloudflare Sandbox SDK, copies the project in, runs the job, returns the result, and shuts the container down. Notebook runs work the same way. We still use full Linux, but only for the seconds of work that actually need it.
The honest downside is that we have to anticipate what the agent needs. With bash it could figure things out on its own. Now, if a capability is missing, we have to add it. In practice that pressure has been good for the product, because it forces us to think about what users are doing and build a first-class path for it instead of letting the agent improvise.
There was an unexpected benefit too. Bash is open-ended, and cheap models struggle in open-ended environments. With a smaller set of explicit methods they perform noticeably better, which matters because keeping camelAI cheap to run is the point of this architecture.
## Where that leaves us
The stack is now Durable Objects for the agent and its filesystem, R2 for large files, Artifacts for git history, pi as the harness, and Code Mode with dynamic Workers for execution. It deploys like any other Cloudflare app, and there are no external container services to manage.
Dynamic Workers are billed per execution, not per second of uptime. Thousands of executions cost about what a few minutes of container time costs on the services we used to evaluate. Latency is low because everything runs on the edge near the user, and scaling is Cloudflare's problem instead of ours.
Users still build and deploy full-stack apps to live URLs, and the agent still reads, writes, greps, and deploys. From the user's side, nothing changed.
## TL;DR
We started with the Claude Code harness on a self-built VM service, which was expensive and hard to scale. First we moved the agent itself into a Cloudflare Durable Object and let it control VMs remotely, which fixed latency but not cost. Then we replaced the VMs entirely with a filesystem stored in Durable Object SQLite and R2, based on Cloudflare's Shell project, with git history through Cloudflare Artifacts. Finally we removed bash and gave the agent a JavaScript sandbox via Code Mode and dynamic Workers, with explicit methods for deploys, builds, and notebooks. The result is cheaper by orders of magnitude, lower latency, simpler to operate, and easier for smaller models to drive. All of it is open source at github.com/qaml-ai/camelAI.
## 相关链接
- [Miguel Salinas](https://x.com/Vercantez)
- [@Vercantez](https://x.com/Vercantez)
- [170K](https://x.com/Vercantez/status/2082138839888589200/analytics)
- [Cloudflare Durable Object](https://developers.cloudflare.com/durable-objects/)
- [github.com/qaml-ai/camelAI](https://github.com/qaml-ai/camelAI)
- [building our own container service](https://camelai.com/blog/we-tried-every-container-service-then-built-our-own)
- [our own harness](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/chat-thread-do.ts)
- [Cloudflare Artifacts](https://developers.cloudflare.com/artifacts/)
- [a filesystem that lives inside a Durable Object](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/workspace-filesystem-do.ts)
- [Shell](https://www.npmjs.com/package/@cloudflare/shell)
- [Code Mode](https://blog.cloudflare.com/code-mode/)
- [dynamic Worker loaders](https://blog.cloudflare.com/dynamic-workers)
- [methods for everything the platform can do](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/code-mode-tools.ts)
- [grep and glob implementations](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/pi-container-tools.ts)
- [Cloudflare Sandbox SDK](https://github.com/cloudflare/sandbox-sdk)
- [github.com/qaml-ai/camelAI](https://github.com/qaml-ai/camelAI)
- [Upgrade to Premium](https://x.com/i/premium_sign_up)
- [12:19 AM · Jul 29, 2026](https://x.com/Vercantez/status/2082138839888589200)
- [170.4K Views](https://x.com/Vercantez/status/2082138839888589200/analytics)
- [View quotes](https://x.com/Vercantez/status/2082138839888589200/quotes)
---
*导出时间: 2026/7/29 08:58:35*
---
## 中文翻译
# 我们使用 Pi、Agents SDK 和 Code Mode 将 Agent 重写为完全运行在 Durable Object 中
**作者**: Miguel Salinas
**日期**: 2026-07-28T16:19:18.000Z
**来源**: [https://x.com/Vercantez/status/2082138839888589200](https://x.com/Vercantez/status/2082138839888589200)
---

我们最近完成了将 camelAI agent 从虚拟机上迁移的工作。该 agent 现在运行在一个 Cloudflare Durable Object 内部,其文件系统位于 SQLite 和 R2 中,并且它编写的是 JavaScript 而不是 bash。大多数团队在完整的 Linux 虚拟机或容器沙盒中运行编码 agent,我们以前也是如此。
我们想要摆脱虚拟机,因为为每个用户配备一台带有附加磁盘的始终在线的机器对于扩展来说太昂贵了。困难之处在于,编码 agent 假定存在 Linux。它们被训练为使用 bash,而我们启动的框架需要一个完整的虚拟机,因此达到这一步经历了三次重新设计。其权衡在于,agent 现在只能执行我们为其构建了显式方法的操作,这听起来有局限性,但对产品来说是有利的。
我是 Miguel,camelAI 的 CTO。我们的代码库最近开源了,所以这篇文章中的所有内容都是你可以在 github.com/qaml-ai/camelAI 上阅读的代码。我们将在进行过程中链接到相关文件。以下是演进过程。
## 第零步:虚拟机时代
我们在 Claude Code 框架上启动,这需要运行一个完整的虚拟机。我们尝试了几家虚拟机提供商,没有一家能满足我们的持久性和性能要求,最终我们构建了自己的容器服务。那篇文章还在,但我们不再运行任何此类基础设施。
容器服务可以工作,但它很繁重。为每个用户配备一台始终在线的虚拟机很昂贵,将每个用户的文件保存在快速的附加磁盘上也是如此。扩展它意味着扩展具有真实磁盘的真实机器,对于我们目标中的用户数量来说,这将变得极其昂贵。因此,我们没有在虚拟机编排上耍小聪明,而是开始设计根本不需要虚拟机的方案。
## 第一步:将 agent 从虚拟机中取出
Claude Code 框架与其虚拟机密不可分,所以第一步是构建我们自己的框架。我们基于 pi 构建了它,pi 是 Mario Zechner 的开源编码 agent。pi 是一组库的堆栈。最高层假定一个正常的操作系统,但较低层为你提供 agent 原语,如 agent 循环和状态管理,而不关心它们在哪里运行。我们没有更改任何 pi 代码。我们导入了那些较低层并在其之上构建了我们自己的框架,运行在 Cloudflare Durable Object 内部而不是 Linux 环境中。
Durable Object 是一个小型的有状态计算实例,它在 Cloudflare 的边缘启动,靠近创建它的用户。每个聊天线程都有自己的 Durable Object,与将所有内容通过中心化的虚拟机主机路由相比,这本身就降低了延迟。
在这个阶段我们保留了虚拟机,但 agent 不再位于虚拟机内部。当它需要运行命令时,它会远程调用虚拟机。Anthropic 对其托管 agent 描述了同样的分离,即大脑与双手分离。这给我们带来了一些很好的特性:
- Agent 在虚拟机唤醒之前就开始响应,因为它不需要等待机器启动。
- 当 agent 继续工作时,虚拟机可以重新进入睡眠状态,如果本轮对话不需要任何命令,它甚至可能根本不会唤醒。
- 一个大脑可以控制多双手。单个 agent 可以同时操作多个虚拟机。
我们称这些“手”为项目。每个项目都配有一个用于执行命令的虚拟机和一个通过 Cloudflare Artifacts 以编程方式创建的 git 仓库,Artifacts 是一种兼容 git 的存储,你可以从 Worker 中动态配置。Agent 并不太知道自己是在虚拟机之外运行的。它仍然拥有 bash,并且像任何其他编码 agent 一样工作。
问题在于这只解决了延迟,其他什么也没解决。我们仍然每个用户都有一个虚拟机,所以我们仍然拥有原始设计的所有成本和扩展问题。
## 第二步:移除虚拟机
下一个版本保留了相同的项目结构,但去掉了其背后的虚拟机。每个项目现在由一个位于 Durable Object 内部的文件系统支持,R2 位于其后用于存储更大的文件。
这不是我们发明的。Cloudflare 的 agents 团队构建了 Shell,这是一个用于 Workers 的实验性文件系统和执行运行时,我们大量重用了他们的代码。机制很简单。Durable Object 的存储是一个上限为 10 GB 的 SQLite 数据库,每一行都有最大大小限制。小文件直接驻留在 SQLite 行中。超过大约 1.5 MB 的文件会被写入 R2,而 SQLite 行仅保存一个指针。对于 agent 来说,它看起来像一个普通的文件系统,但在底层它是一个数据库和对象存储,因此持久性是存储的数据,而不是我们必须保持活跃的基础设施。
版本历史仍然通过 Artifacts 运行,因此每个项目都保留一个 git 历史,而无需我们托管 git 服务器。
## 第三步:移除 bash
移除 bash 感觉很激进。编码 agent 被训练为使用 bash,而 bash 正是每个人首先在虚拟机中运行它们的原因。这也是一个超越成本的问题。一个拥有 bash 和网络访问权限的 agent 需要凭证才能做任何有用的事情,而我们尝试使用经过身份验证的代理 URL 变得越来越 hacky 且难以强制执行。
所以我们移除了它。代替 bash 的是,agent 编写 JavaScript,通过 Code Mode 和 Cloudflare 的动态 Worker 加载器执行。每次执行都在一个全新的 V8 隔离实例中运行,该实例在几毫秒内启动并使用几兆字节的内存。沙盒预加载了用户的数据连接以及平台可以执行的所有操作的方法。凭证永远不会进入沙盒。Agent 调用连接的方法,身份验证在我们这一端进行。
当你查看 agent 实际使用 bash 做什么时,失去它的成本比你预期的要低。大部分是文件操作,agent 拥有用于此的原生工具。我们给它提供了 read、write 和 edit,加上我们自己的 grep 和 glob 实现。这涵盖了 80-20 原则。其余的是针对特定工作的特定命令,这些变成了显式方法:
- 通过代理进行 wrangler deploy 变成了我们可以完全控制的 deploy_project 方法。由于我们确切知道部署何时发生,我们可以挂钩它并自动打开实时预览。以前,我们必须嗅探代理的 wrangler 流量来猜测哪个线程部署了某些东西。
- 构建用户的应用程序和运行 Python 笔记本变成了它们自己的方法,两者都由短生命周期的容器支持。
我们为这两项工作保留了容器,因为它们确实需要 Linux。用户应用程序使用 Vite、Tailwind 和 React Router 构建,添加依赖意味着运行 bun install。我们考虑过在 Worker 内部运行构建,因为被构建的东西本身就是 Worker,但这条路径没有得到很好的支持,而且 Worker 有 128 MB 的内存限制和一小部分 CPU。构建会很慢,而且很多项目会突破内存上限。因此,构建改为通过 Cloudflare Sandbox SDK 启动一个容器,将项目复制进去,运行作业,返回结果,然后关闭容器。Notebook 运行以同样的方式工作。我们仍然使用完整的 Linux,但仅用于真正需要它的几秒钟工作。
诚实的缺点在于我们必须预判 agent 需要什么。有了 bash,它可以自己弄清楚事情。现在,如果缺少某个功能,我们必须添加它。实际上,这种压力对产品是有好处的,因为它迫使我们思考用户在做什么,并为其构建一流的路径,而不是让 agent 即兴发挥。
还有一个意想不到的好处。Bash 是开放式的,廉价模型在开放式环境中表现挣扎。通过一组较小的显式方法,它们的表现明显更好,这很重要,因为让 camelAI 运行成本低廉正是这种架构的重点。
## 现状
现在的技术栈是:用于 agent 及其文件系统的 Durable Objects,用于大文件的 R2,用于 git 历史的 Artifacts,作为框架的 pi,以及用于执行的 Code Mode 和动态 Workers。它的部署就像任何其他 Cloudflare 应用程序一样,没有外部容器服务需要管理。
动态 Worker 按执行次数计费,而不是按运行时间秒数计费。数千次执行的成本仅相当于我们以前评估的服务上几分钟容器时间的成本。延迟很低,因为一切都在靠近用户的边缘运行,而扩展是 Cloudflare 的问题,而不是我们的问题。
用户仍然构建并将全栈应用程序部署到实时 URL,agent 仍然读取、写入、grep 和部署。从用户的角度来看,什么都没有改变。
## 太长不看(TL;DR)
我们开始在自建的 VM 服务上使用 Claude Code 框架,这既昂贵又难以扩展。首先,我们将 agent 本身移入 Cloudflare Durable Object,并让它远程控制 VM,这解决了延迟问题但没有解决成本问题。然后,我们基于 Cloudflare 的 Shell 项目,用存储在 Durable Object SQLite 和 R2 中的文件系统完全取代了 VM,并通过 Cloudflare Artifacts 保留 git 历史。最后,我们移除了 bash,并通过 Code Mode 和动态 Workers 为 agent 提供了一个 JavaScript 沙盒,针对部署、构建和笔记本提供了显式方法。结果在成本上降低了几个数量级,延迟更低,操作更简单,也更容易让较小的模型驱动。所有内容都在 github.com/qaml-ai/camelAI 上开源。
## 相关链接
- [Miguel Salinas](https://x.com/Vercantez)
- [@Vercantez](https://x.com/Vercantez)
- [170K](https://x.com/Vercantez/status/2082138839888589200/analytics)
- [Cloudflare Durable Object](https://developers.cloudflare.com/durable-objects/)
- [github.com/qaml-ai/camelAI](https://github.com/qaml-ai/camelAI)
- [building our own container service](https://camelai.com/blog/we-tried-every-container-service-then-built-our-own)
- [our own harness](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/chat-thread-do.ts)
- [Cloudflare Artifacts](https://developers.cloudflare.com/artifacts/)
- [a filesystem that lives inside a Durable Object](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/workspace-filesystem-do.ts)
- [Shell](https://www.npmjs.com/package/@cloudflare/shell)
- [Code Mode](https://blog.cloudflare.com/code-mode/)
- [dynamic Worker loaders](https://blog.cloudflare.com/dynamic-workers)
- [methods for everything the platform can do](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/code-mode-tools.ts)
- [grep and glob implementations](https://github.com/qaml-ai/camelAI/blob/main/workers/main/src/pi-container-tools.ts)
- [Cloudflare Sandbox SDK](https://github.com/cloudflare/sandbox-sdk)
- [github.com/qaml-ai/camelAI](https://github.com/qaml-ai/camelAI)
- [Upgrade to Premium](https://x.com/i/premium_sign_up)
- [12:19 AM · Jul 29, 2026](https://x.com/Vercantez/status/2082138839888589200)
- [170.4K Views](https://x.com/Vercantez/status/2082138839888589200/analytics)
- [View quotes](https://x.com/Vercantez/status/2082138839888589200/quotes)
---
*导出时间: 2026/7/29 08:58:35*