Cloudflare Computer push/pull API详解:10分钟搞懂手动同步与exec自动同步的边界
【免费下载链接】computerGive your agent a computer 👾项目地址: https://gitcode.com/GitHub_Trending/computer1/computer
Cloudflare Computer 是让 AI Agent 拥有一台"计算机"的开源虚拟文件系统,它的核心数据同步机制就是 push/pull API。本文将从新手视角讲清楚两件事:workspace.push()/workspace.pull()手动同步在什么场景该用,以及runtime.exec()命令执行时自动同步的"安全围栏"如何工作——帮你精准判断哪种同步方式适合你的应用。
一张图看懂 Cloudflare Computer 同步架构
整个系统只有两个主角:
- Durable Object(DO 侧):用 SQLite 存储权威的虚拟文件系统,是"事实来源",重启不丢数据;
- Container 侧:沙箱容器里的
computerd守护进程,把同一份文件系统通过 FUSE 挂载成真实目录,让命令像操作本地磁盘一样工作。
两者之间通过一条 WebSocket 通道跑push/pull 双向同步协议:每次变更都会被盖上单调递增的"修订号"(revision),谁有新变更就推给谁,接收方按增量合并,不用每次传整棵目录树。协议全貌见 docs/02_sync_protocol.md。
两条同步路径:手动 push/pull 与 exec 自动同步
手动同步:什么时候该自己调 push() 和 pull()
Workspace对外只暴露两个同步方法(实现见 packages/computer/src/workspace.ts):
| 方法 | 方向 | 作用 |
|---|---|---|
workspace.push() | DO → 容器 | 把宿主侧新写的文件推送到容器,返回推送条目数 |
workspace.pull() | 容器 → DO | 把容器侧产生的变更拉回 SQLite,返回{ applied, skipped } |
关键点:包内没有后台轮询线程,同步完全由你显式触发。以下场景必须手动调用:
- 纯文件系统流程:你在 DO 侧用
workspace.fs.writeFile()写了文件,想让容器里的命令"看见"它——先push(); - Agent 交接:Agent A 在容器里干完活,Agent B 接手前先
pull(),确保读到 A 的最终写入(文档称之为"显式同步点"); - 容器重启后:
computerd的本地数据库是进程级的,重启会丢本地状态,下一次 push 会被当作全新基线重新灌入,通常由重连逻辑自动兜底。
exec 自动同步:push → 执行 → pull 的括号结构
只要走命令执行路径,同步就自动发生,你不需要手动调用。packages/computer/src/shell.ts 把每次exec()包成一个固定结构:
push(预推)→ 在容器里 spawn 命令 → 事件流/结果返回 → pull(回拉)这个"括号"有三个新手容易忽略的细节:
- 预推是安全闸门,不是优化。push 失败会直接让
exec()拒绝执行,命令不会带着过期的工作区跑起来; - 回拉是幂等的。pull 按 256 条一批提交检查点,中途崩溃也只重放一小批,而且结果里带
sync.status:"complete"表示同步完成,"pending"表示命令本身成功了但回拉失败——可以之后用retryPendingSync()恢复,不会重跑你的命令; - 重连只重试一次。同步操作天然幂等(靠水位线保证),所以传输中断后可以安全重放;但"命令可能已经启动"的场景绝不重放,宁可报错也不让你执行两遍。
执行结果的pushed/pulled计数就是这对括号的统计,API 定义见 docs/05_runtime_interface.md。
同步边界速查表:哪些自动、哪些不自动
| 操作 | 是否自动同步 | 说明 |
|---|---|---|
workspace.runtime.exec("...") | ✅ 自动 | 命令前后各同步一次(push 括号 + pull 括号) |
workspace.fs.writeFile()等宿主侧写 | ❌ 不自动 | 需手动push(),或等下一次 exec 的预推顺带发出 |
| 容器内 FUSE 写文件 | ⏳ 暂存 | 每次写都盖章 revision,等下一次pull()才回到 SQLite |
worker-javascript/worker-shell后端 | 🚫 无需同步 | 直接读写宿主权威存储,sync: "none",计数恒为 0 |
| 挂 R2/Artifacts 的只读挂载 | 🚫 拒绝回写 | 容器侧写入只读挂载点会被跳过,体现在skipped[] |
一句话总结边界:命令执行自带同步围栏;凡是绕过 exec 直接操作文件系统的场景,同步责任就落在调用者身上。
同步协议为什么快:三个关键设计
- 增量而非快照:每次变更打上 revision,同步只传"对端没见过"的部分;同一路径改 5 次,线上只走 1 条合并后的记录(coalesce,见 packages/dofs/src/sync/coalesce.ts);
- 按内容分块去重:文件按 512 KiB 切块,用 SHA-256 哈希寻址。两个路径放同一份
node_modules只传一次;改大文件只传动过的块。协商机制直接借用了 git 的 haves/wants 思路——hasObjects()问"你有什么",fetchObjects()/pushObjects()补传缺的; - 忽略列表省钱:
fetchChanges默认忽略node_modules,一次npm install不会把几万个碎片文件灌回 DO。容器里照样能用这些文件,只是不跨线上行。
完整 RPC 面(6 个方法:push/fetchChanges/watermarks/readEntry/hasObjects/fetchObjects)定义在 packages/rpc/src/interface.ts,帧格式说明见 docs/08_capnweb_interface.md。
冲突语义:最后是"谁最后写赢"
多容器共享同一个 Workspace 时,同步采用last-write-wins:先拉远端再推本地,但不做内容合并——两个容器改同一文件,谁的 push 先到 DO 谁的版本存活,另一个的修改被静默覆盖。这与不带锁的 NFS 共享挂载语义一致。
对新手来说,安全姿势有三条:
- 一个 Workspace 同时只有一个活跃写者——绝大多数 Agent 场景都符合;
- 多 Agent 分区写:A 只写
/workspace/a/,B 只写/workspace/b/,冲突从结构上不可能发生; - 交接处显式 pull:把 workspace 文件当作各 Agent 的私有草稿区,而非共享可变状态。
详见 docs/02_sync_protocol.md 的"Conflict semantics"一节。
最佳实践清单
- 📌 只读文件(配置、脚手架):宿主侧写好后 push 一次即可,之后无需再管;
- 📌 长任务 exec:不用关心同步,看
sync.status为"complete"即收敛; - 📌 构建产物(
node_modules、dist):保持默认忽略,避免无谓流量; - 📌 多 Agent 流水线:在交接点手动
pull(),别假设自动围栏存在。
去哪里看源码
| 资料 | 路径 |
|---|---|
| 同步协议设计文档 | docs/02_sync_protocol.md |
| 手动同步门面(Workspace 类) | packages/computer/src/workspace.ts |
| exec 自动同步括号 | packages/computer/src/shell.ts |
| 双向同步驱动(pullOnce / pushOnce / tick) | packages/rpc/src/sync-driver.ts |
| RPC 线格式定义 | packages/rpc/src/interface.ts |
| 运行时接口与结果类型 | docs/05_runtime_interface.md |
掌握这条"手动 vs 自动"的边界,你就已经能正确驾驭 Cloudflare Computer 的数据一致性了——exec 里放心跑,exec 外自己管。
【免费下载链接】computerGive your agent a computer 👾项目地址: https://gitcode.com/GitHub_Trending/computer1/computer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考