小岛AIAI 航行日志

小岛 / 岛刊社 / 文章

Cursor builds 快 3 倍,最值钱的是失败不生效

小岛AI

预热环境只是表面,最近成功版本、提交溯源和密钥分层才决定云 Agent 能否稳定交付。附一张迁移验收单。

AI 编程Agent 工程AI 安全

一个云端 Agent 还没读到 issue,先花了几分钟安装依赖。

克隆仓库、跑 pnpm install、拉 Docker 镜像、生成代码、编译几个本地包。模型一个 token 都没吐,机器已经忙成了工地。要是同一时间启动十个 Agent,十台机器再把这套仪式各演一遍。

这两天 Cursor 推出了 builds,把这些动作提前放到后台做。它会持续准备可直接启动的开发环境副本,新 Agent 不再从空机器起步。Cursor 给出的数字很抓眼,内部环境启动快了 10 倍,首个 token 最快提前 3 倍。

好家伙,冷启动终于有人认真治了。

但我看完整篇发布和 Builds 文档 后,最在意的不是 3 倍。真正值钱的是另一句,新 Build 如果失败,不会替换正在使用的环境,Agent 会继续从最近一次成功的 Build 启动。

让 Agent 起跑快,解决的是等待。让坏环境不能晋级,解决的才是交付。

Cursor 公布的环境启动与首个 token 提速对比

首个 token 只是表面账

云 Agent 的等待时间很容易被低估。

大家平时盯模型延迟,首个 token 几秒出来,生成速度每秒多少 token。可工程任务开始前,还有一段不属于模型的灰色时间。机器要启动,仓库要克隆,依赖要下载,私有包要鉴权,代码生成器要跑完,测试数据库也得站起来。

这些动作单看都不神秘,叠在一起就很烦。更麻烦的是,它们会跟并发量一起复制。一个 Agent 冷启动两分钟,十个 Agent 不是大家一起等两分钟那么简单。它们可能同时挤包仓库、镜像仓库和构建缓存,把偶发网络抖动放大成一排失败任务。

Cursor 的做法并不玄学。Build 在后台克隆默认分支,执行安装命令,保存磁盘状态、环境版本和每个仓库对应的提交 SHA。成功后,它成为活动环境,平台保留一份预热副本。新 Agent 从正在运行的机器分叉,不必每次都从磁盘和网络重新拼一套世界。

官方还引用了 Faire 的使用数据。他们每周启动超过 2,000 次自动化 Agent 运行,复杂仓库已经能在几秒内启动。这个量级下,少等几十秒当然很香。可如果只把 builds 理解成更大的依赖缓存,还是看窄了。

缓存回答的是「能不能少做一遍」。生产系统还得回答「这份东西凭什么能用」。

失败不能晋级,比缓存更重要

一个依赖更新把安装脚本弄坏,新的 Dockerfile 少复制了一个配置,私有包凭证过期。传统的即时启动会把这些问题直接甩给下一次任务。Agent 接到修 bug 的活,结果半小时都耗在修自己的开发环境上。

更糟的是,不同任务可能撞到不同失败。有人命中旧缓存,有人拉到新依赖,有人恰好赶上包仓库超时。你看到三个 Agent 给出三个结果,第一反应也许是模型不稳定,实际是三块跑道根本不一样。

Cursor 给 Build 加了一条很朴素的发布规则。触发、准备、快照、激活。只有成功完成安装的快照,才会成为新的活动 Build。失败版本有日志,可以单独启动 Agent 去复现和修,但不会偷偷顶掉上一份可用环境。

厉害了,这已经不是启动优化,而是给开发环境补了一条 CI。

应用代码合并前要过测试,容器镜像发布前要过扫描,Agent 环境也该有自己的晋级门。它至少要证明仓库能克隆、依赖能装完、生成步骤能执行、关键服务能拉起。证明不了,就留在草稿区,不许当成整个团队的新地板。

Cursor 的 Build 面板保留失败日志、环境版本和提交 SHA

这里还有个容易被忽略的细节。每次 Agent 运行都会记住自己用了哪一个 Build。Build 又记录环境版本和各仓库的确切提交 SHA。以后一个任务昨天能跑、今天跑不了,不必只盯 prompt 和模型版本,也能沿着环境版本往回查。

很多 Agent 平台的可观测性只记了模型、token、工具调用和最终答案。环境却像空气,出问题前没人觉得它需要版本号。等到 node-gyp、系统库、浏览器版本或者某个生成器变了,大家才发现那次成功根本复现不出来。

段错误。

Agent 的运行轨迹不只是一串消息,它还应该绑定一块可重放的地面。

安装和启动,得拆成两种命令

预构建环境最容易踩的坑,是把所有东西都塞进快照。

Cursor 在文档里把命令分成三类。install 在每次 Build 时执行,用来安装依赖、生成代码、编译产物和预热磁盘缓存。start 与 terminals 在每次 Agent 运行时执行,用来启动 Docker、数据库、隧道和共享终端里的应用进程。

这条边界很关键,因为 Build 只保存磁盘状态。正在运行的进程、shell 临时导出的变量和内存缓存,快照时都会停掉。把数据库启动塞进安装阶段,看着构建成功,Agent 真正醒来时只会得到一堆没有进程接手的文件。

我自己的判断是,迁移前先把环境动作按寿命拆开。

几小时甚至几天都不变的依赖下载、编译产物和工具安装,适合进 Build。每次会话都要重新确认的分支代码、临时端口、数据库进程和隧道,留在启动阶段。只对单个任务有效的访问令牌、工单编号和用户身份,更不能烤进共享快照。

这里顺手带出另一个硬要求,安装命令要幂等。也就是同一条命令多跑几遍,结果应该一致,不会因为目录已经存在就翻车,也不会重复写出一堆脏状态。Cursor 的 Builds 文档 明确要求 install 能重复运行,而且它可能基于已准备过的磁盘状态继续执行。

如果一条 setup.sh 只有在全新机器上才能成功,它不是自动化脚本,只是一段碰巧跑通过的回忆。

密钥别跟着快照一起旅行

环境预热越彻底,越容易有人想把凭证也提前塞进去。否则私有包拉不下来,内部 artifact 取不到,构建还是会卡住。

可共享快照一旦混进个人密钥,速度问题解决了,安全事故开始排队。

Cursor 的处理方式值得抄作业。Build 可以使用团队级和环境级密钥,满足私有注册表、artifact 存储和安装阶段的访问需求。用户密钥只在 Agent 启动时注入,不会出现在共享快照里。此前 Cursor 在 云 Agent 开发环境 里也给 Dockerfile 增加了构建密钥支持,密钥只作用于构建步骤,不会进入运行环境。

这不是产品细节,是环境合同的一部分。能被整个团队复用的凭证,才有资格参与构建。属于某个人、某个工单或某次会话的权限,只能在运行时短暂出现。

同一条思路还能延伸到网络出口。开发环境不该默认拥有整个公司的网络视野。Cursor 支持按环境限制出站域名,并把密钥作用域隔离在单个环境。一个只需要改前端文案的 Agent,没必要顺手拿到生产数据库和财务系统的入口。

说真的,让 Agent 快速拿到一把万能钥匙,确实也能快 3 倍。快进事故现场。

快照会过期,稳定不能变成陈旧

保留最近成功版本听着很稳,但它也有代价。

如果默认分支上午已经合了十个提交,Agent 下午还从昨天的 Build 起跑,它可能在一套过期代码上认真修一个已经不存在的 bug。稳定和新鲜度天然有拉扯,不能只选一个词写进宣传页。

Cursor 给默认分支 Build 配了过期阈值,默认是 24 小时。超过阈值后,Agent 启动时可以拉取最新代码。阈值设成 0,每次都会更新。功能分支则复用 Build 里的依赖和磁盘状态,再检出指定分支。如果分支改了依赖,Agent 仍要在测试前刷新环境。

这个设计有点子牛逼的地方,是它没假装快照能冻结整个软件世界。Build 固定的是一个可验证起点,不是宣布依赖和代码从此停止变化。

团队迁移时真正要定的是一份新鲜度预算。一天只改几次的后端服务,24 小时或许够用。每小时几十次合并的 monorepo,24 小时就像拿上周的地图开今天的路。依赖锁文件变更、基础镜像安全更新、环境密钥轮换,也都应该主动触发重建,别全等定时器来捞。

另外,失败回退不能静默太久。一直用最近成功版本确实不会中断任务,但如果连续三天构建都失败,团队其实已经欠下了一笔环境债。面板要告警,Build 年龄要进监控,任务结果也该把环境年龄带上。

能回退,不等于可以装作没坏。

上线前,先写一张环境合同

8 月 17 日起,Cursor 会为新旧环境默认启用 builds,而且不额外收费。开关变成默认值之前,我建议别急着看那条 3 倍曲线,先把自己的环境合同写出来。

它不需要很长,能回答这些字段就够了。

build:
  source_commit: <每个仓库的提交 SHA>
  environment_version: <环境版本>
  install_command: <可重复执行的安装命令>
  success_checks: <依赖、生成、编译与基础测试>
  secret_scope: <仅团队级或环境级>
  stale_after_hours: <允许的代码陈旧时间>

runtime:
  start_command: <每次会话启动的服务>
  user_secrets: <只在运行时注入>
  network_allowlist: <任务真正需要的出口>
  build_id: <本次任务绑定的 Build>

这不是 Cursor 官方模板,是一份可以直接改的最小验收单。配置放进 .cursor/environment.json 或团队自己的环境仓库都行,重点是它要能评审、能回滚、能跟任务结果关联。

指标也别只记首个 token。

环境准备时间能告诉你预热有没有生效,Build 成功率能暴露安装链路是否健康,活动 Build 年龄能提醒代码是否陈旧,任务首轮通过率与人工接管率才接近真正交付。再把失败按模型、工具、代码和环境分开,团队才不会把 npm install 超时算到模型头上。

可以用一个很粗但好用的式子看总账。

有效交付时间
= 排队时间
+ 环境准备时间
+ Agent 执行时间
+ 失败重试与人工接管时间

builds 主要砍掉第二项,也会通过稳定环境减少第四项。至于任务能不能完成,仍取决于模型、工具权限、测试质量和验收规则。预热环境不是魔法,只是终于把一块长期被忽略的工程地基补上了。

坦率讲,我喜欢这类更新胜过又一个榜单冠军。模型多涨两分,大家会刷屏一天。把环境做成有版本、能晋级、可回退的制品,没那么性感,却会在接下来几个月里少掉很多莫名其妙的失败。

开头那台忙着装依赖的机器,终于不用每次都从空地搭工棚了。

但更重要的是,坏掉的工棚不会被挂上「标准环境」的牌子。