CheinTian / notes
Clipping · 8 分钟

We rewrote our agent to run entirely in a Durable Object with Pi, Agents SDK and Code Mode

We recently finished moving the camelAI agent off of virtual machines. The agent now runs inside a Cloudflare Durable Object, its filesystem...

x.com @Vercantez

📝 摘要

这篇文章讲述了 camelAI 团队如何将编码智能体从虚拟机架构迁移到完全运行在 Cloudflare Durable Object 上的过程。作者 Miguel(camelAI CTO)详细描述了三次架构迭代的演进路径:从 Claude Code harness + 自建容器服务,到将 agent "大脑"迁入 Durable Object 而 VM 远程执行命令,再到完全移除 VM 并用 Durable Object SQLite + R2 实现文件系统,最后移除 bash 改用 JavaScript 沙箱执行。核心架构转变是:从"每个用户一个 VM"到"每个聊天线程一个 Durable Object",文件系统从附加磁盘变为 SQLite + R2,代码执行从 bash 变为 Code Mode + 动态 Worker 加载器。结果:成本降低数个数量级、延迟更低、运维更简单、且对小型模型更友好。

核心概念: 通过将编码智能体从 VM 迁移到 Cloudflare Durable Object + JavaScript 沙箱,实现了数量级的成本降低和性能提升,同时用显式方法替代 bash 反而让小型模型表现更好。

关键技术栈 / 关键要点:

  1. Cloudflare Durable Object — 每个聊天线程一个有状态计算实例,在边缘节点运行,降低延迟
  2. pi 框架 — Mario Zechner 的开源编码智能体库,提供 agent loop 和状态管理等底层原语
  3. 文件系统替代方案 — Durable Object SQLite(小文件直接存储)+ R2(大文件指针存储),替代传统附加磁盘
  4. Code Mode + 动态 Worker — 用 V8 isolate 沙箱执行 JavaScript 替代 bash,毫秒级启动,MB 级内存
  5. 显式方法替代 bash — 将 bash 命令抽象为 deploy_project、build、run_notebook 等受控方法,凭据不进入沙箱
  6. Cloudflare Artifacts — git 兼容存储,提供版本历史,无需自建 git 服务器
  7. Cloudflare Sandbox SDK — 构建和笔记本运行等必须 Linux 的场景仍使用短生命周期容器

关键结论 / 实践建议: 用显式方法替代开放式 bash 看似限制了 agent 的能力,但实际压力迫使产品团队为用户行为构建一阶路径,反而提升了产品质量;同时,更小的动作空间让廉价模型表现更好,这对控制运营成本至关重要。


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.