我們最近完成了將 camelAI 的 Agent 從虛擬機器上遷移的工作。現在 Agent 運行在 Cloudflare Durable Object 內,檔案系統使用 SQLite 和 R2,並且用 JavaScript 取代了 bash。大多數團隊在完整的 Linux VM 或容器沙箱中運行編碼 Agent,我們之前也是如此。
我們想擺脫 VM,因為給每個用戶分配一台始終運行的機器並附帶磁碟,成本太高,難以擴展。難點在於編碼 Agent 依賴於 Linux。它們被訓練成使用 bash,而我們最初發布的框架需要完整的 VM,因此我們經歷了三次重新設計才達到現在的狀態。權衡的結果是,Agent 現在只能做我們明確構建了方法的事情,這聽起來有限制,但對產品來說是有益的。
我是 Miguel,camelAI 的 CTO。我們的程式碼庫最近開源了,所以本文所述的一切都可以在 github.com/qaml-ai/camelAI 上看到。我會在文中連結到相關檔案。以下是演進過程。
第零步:VM 時代
我們基於 Claude Code 框架發布,該框架需要完整的虛擬機器來運行。我們嘗試了幾家 VM 提供商,但沒有一家能滿足我們的持久性和效能要求,最終我們構建了自己的容器服務。那篇文章仍然在線,但我們已不再運行任何相關基礎設施。
容器服務能工作,但很臃腫。每個用戶始終運行的 VM 成本高昂,而將每個用戶的檔案儲存在快速附加磁碟上同樣昂貴。擴展意味著需要擴展真實的機器和磁碟,這對於我們目標用戶數量來說成本高得無法承受。因此,我們沒有在 VM 編排上耍聰明,而是開始設計一種完全不需要 VM 的架構。
第一步:將 Agent 從 VM 中分離
Claude Code 框架與 VM 密不可分,因此第一步是構建我們自己的框架。我們基於 pi 構建,這是 Mario Zechner 的開源編碼 Agent。pi 是一組函式庫的堆疊。最高層假定一個正常的作業系統,但較低層提供了 Agent 原語,如 Agent 迴圈和狀態管理,而不關心它們在哪裡運行。我們沒有修改任何 pi 程式碼。我們導入了那些較低層,並在其之上構建了我們自己的框架,它運行在 Cloudflare Durable Object 內,而不是 Linux 環境中。
Durable Object 是一個小型的有狀態計算實例,在 Cloudflare 的邊緣節點上啟動,靠近創建它的用戶。每個聊天執行緒都有自己獨立的 Durable Object,相比將所有流量路由到集中式 VM 主機,這本身就降低了延遲。
在這個階段,我們仍然保留了 VM,但 Agent 不再運行在 VM 內部。它需要運行命令時,會遠端呼叫 VM。Anthropic 對其管理的 Agent 也描述了同樣的分離方式——大腦與雙手分離。這給我們帶來了一些好處:
- Agent 在 VM 啟動之前就開始回應,因為它不需要等待機器啟動。
- 當 Agent 繼續工作時,VM 可以進入休眠狀態,或者如果該輪不需要任何命令,VM 甚至可以完全不啟動。
- 一個大腦可以控制多雙手。單個 Agent 可以同時操作多個 VM。
我們稱這些「手」為專案。每個專案都附帶一個用於執行命令的 VM,以及通過 Cloudflare Artifacts 以程式化方式建立的 git 倉庫——這是一種相容 git 的儲存,可以從 Worker 動態配置。Agent 實際上並不知道自己運行在 VM 之外。它仍然擁有 bash,並像其他編碼 Agent 一樣工作。
問題是,這只解決了延遲問題,並沒有解決成本問題。我們仍然為每個用戶維護一個 VM,因此仍然存在原始設計中的所有成本和擴展問題。
第二步:移除 VM
下一個版本保持了相同的專案結構,但去掉了背後的 VM。現在每個專案都由一個位於 Durable Object 內的檔案系統支援,較大的檔案則使用 R2 作為後端。
這不是我們發明的。Cloudflare 的 Agent 團隊構建了 Shell,這是一個實驗性的 Worker 檔案系統和執行執行時期,我們大量複用了他們的程式碼。原理很簡單。Durable Object 的儲存是一個 SQLite 資料庫,容量上限為 10 GB,每行記錄有最大大小。小檔案直接儲存在 SQLite 行中。超過約 1.5 MB 的檔案會被寫入 R2,SQLite 行只儲存一個指標。對於 Agent 來說,它看起來像一個正常的檔案系統,但底層是一個資料庫和物件儲存,因此持久化的是儲存資料,而不是我們需要保持執行的基礎設施。
版本歷史仍然通過 Artifacts 實現,因此每個專案都保留了 git 歷史,而我們無需托管 git 伺服器。
第三步:移除 bash
移除 bash 感覺像是大刀闊斧的改革。編碼 Agent 被訓練成使用 bash,而 bash 正是大家首先在 VM 中運行它們的原因。同時,這也帶來了成本之外的問題。一個擁有 bash 和網路存取權限的 Agent 需要憑證才能做任何有用的事情,而我們嘗試的經過身份驗證的代理 URL 越來越 hacky 且難以執行。
因此,我們移除了 bash。Agent 不再使用 bash,而是編寫 JavaScript,通過 Code Mode 和 Cloudflare 的動態 Worker 載入器 執行。每次執行都在一個全新的 V8 隔離環境中運行,啟動時間僅需幾毫秒,記憶體佔用幾 MB。沙箱預先載入了用戶的資料連線,以及平台所能做的一切的方法。憑證永遠不會進入沙箱。Agent 呼叫連線的方法,身份驗證在我們這邊完成。
當你審視 Agent 實際使用 bash 的用途時,失去 bash 的代價比你想象的要小。大部分是檔案操作,Agent 有原生工具來處理。我們提供了讀取、寫入和編輯,以及我們自己的 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 啟動一個容器,將專案複製進去,運行任務,返回結果,然後關閉容器。筆記本運行也是如此。我們仍然使用完整的 Linux,但僅限於實際需要它的那幾秒鐘工作。
誠實的缺點是,我們必須預判 Agent 的需求。有了 bash,它可以自己解決問題。現在,如果缺少某個功能,我們必須添加它。實際上,這種壓力對產品是有利的,因為它迫使我們去思考用戶正在做什麼,並為他們構建一個一流的路徑,而不是讓 Agent 即興發揮。
還有一個意想不到的好處。Bash 是開放式的,而廉價模型在開放式環境中表現不佳。通過一組更小的明確方法,它們的表現明顯更好,這很重要,因為保持 camelAI 的低運行成本正是這個架構的初衷。
目前的狀態
現在的架構是:Durable Objects 用於 Agent 及其檔案系統,R2 用於大檔案,Artifacts 用於 git 歷史,pi 作為框架,Code Mode 配合動態 Workers 用於執行。它像任何其他 Cloudflare 應用一樣部署,無需管理外部容器服務。
動態 Workers 按執行次數計費,而不是按運行時間計費。數千次執行的花費大約相當於我們之前評估的服務上幾分鐘的容器時間。延遲很低,因為一切都在靠近用戶的邊緣節點上運行,擴展是 Cloudflare 的問題,而不是我們的問題。
用戶仍然可以構建和部署全端應用到即時 URL,Agent 仍然可以讀取、寫入、grep 和部署。從用戶的角度來看,一切都沒有改變。
TL;DR
我們從在自建 VM 服務上運行 Claude Code 框架開始,這既昂貴又難以擴展。首先,我們將 Agent 本身移入 Cloudflare Durable Object,並讓它遠端控制 VM,這解決了延遲問題,但沒有解決成本問題。然後,我們完全用儲存在 Durable Object SQLite 和 R2 中的檔案系統取代了 VM,基於 Cloudflare 的 Shell 專案,並通過 Cloudflare Artifacts 維護 git 歷史。最後,我們移除了 bash,通過 Code Mode 和動態 Workers 為 Agent 提供了一個 JavaScript 沙箱,並提供了明確的部署、構建和筆記本方法。結果成本降低了幾個數量級,延遲更低,運維更簡單,並且更容易讓小模型驅動。所有程式碼都在 github.com/qaml-ai/camelAI 上開源。





