跳到正文

实战经验

以下五种情况都导致了真正的停机、成本飙升或真正的工作损失。一旦你看过它们,它们就很明显了。在那之前这些问题并不明显,所以才会一直发生。

这些数据来自运行生产Agent对抗云端沙箱的团队。这些都不是理论上的。这种模式在供应商、平台和实施中反复出现。

学习成果

你可以列出五个生产生命周期的陷阱,描述每个的失败模式,并应用修复模式。

重新连接后句柄陈旧

你重新连接到已有的沙箱。句柄是你之前用的那个,或者是从录音室录音重建的Harness。无论哪种情况,命令流都坏了。命令输入,垃圾输出,或者调用永远挂着。

句柄在断线后幸存下来。里面的会话不会。

Fix: 使用前先探测重新连接的句柄。

ts
const sandbox = await reconnect(sandboxId);
const probe = await sandbox.exec("echo probe");
if (probe.exitCode !== 0 || probe.stdout.trim() !== "probe") {
  sandbox = await createFromSnapshot(lastSnapshotId);
}

探针是只读且快速的。每次重新连接前运行一个Agent的成本远低于与死句柄通信的成本。

过期到期数据

沙箱报告expiresAt创建时间。如果你缓存了这个值,之后再对照,你是在对比存储时已经很旧的数据。更糟的是,如果你在缓存失效后API把衍生值(remainingTimeout = expiresAt - now())传递给提供者,可能会意外创建一个已经过期的沙箱。

Fix: 在生命周期决策前,务必向提供者索取新的有效期。

ts
const { expiresAt } = await sandbox.getStatus();
if (expiresAt < Date.now()) {
  await beforeStop?.(sandbox);
}

缓存的过期信息用于显示,而非控制流。

民调重置不活跃

你的生命周期工作流程每三十秒轮询一次沙箱状态。如果状态检查被计为活动,无活动窗口永远不会关闭。沙箱持续到硬到期。账单到了。

这是一个伪装成集成问题的纯纯函数漏洞。修复方法同时存在两个方面:活动跟踪器必须忽略状态调用,状态调用必须小心避免触发活动编码事件。

Fix: 活动跟踪器只统计用户主动发起的工作。

ts
function recordActivity(event: SandboxEvent) {
  if (event.kind === "user_message" || event.kind === "tool_call" || event.kind === "fs_change") {
    sandbox.lastActivityAt = Date.now();
  }
}

状态ping、健康检查、重新连接探针、计费显示:这些都不会重置定时器。

自动恢复循环

用户重新连接。沙箱会自动从上一次快照恢复。自动恢复触发生命周期检查,尚未发现任何活动,决定进行快照。快照触发休眠。休眠触发下一次自动恢复。

你把两段单独看起来正确的代码组成了一个无限循环。

Fix: 仅在初次进入时自动恢复。后续的重连会加入主动沙箱。

ts
if (isInitialEntry && sandbox.state === "hibernated") {
  await restore(sandbox.snapshotId);
}

状态机是你的好朋友。如果沙箱已经激活,挂上去是正确的选择。如果是休眠状态,就恢复。如果是其他状态,就等一等,否则失败。不要自动连锁过场。

状态分歧

沙箱状态存在三个地方:提供商的API、你的数据库、客户端的本地缓存。它们会分道扬镳。你向用户展示的内容有时会出错,而你信任的平台决定了它是以让你花钱的方式错了,还是以让你失去信任的方式错了。

Fix: 提供者API是真相的来源。从那里导出所有展示的内容。

ts
const { state } = await provider.getSandboxStatus(sandboxId);
ui.showState(state);

你的数据库就像一个缓存。客户端缓存就是缓存。事实也不是这样。有疑问时,去取。

**Warning:组合比individuals**还糟

每个陷阱本身就很糟糕。昂贵的bug来自于将它们组合起来。陈旧的句柄加上民调计入活动跟踪器意味着你一直在为一个无法沟通的沙箱买单。发散缓存加上自动恢复循环意味着你为一个用户创建了三个重复的沙箱。纵深防守是正确的立场。修复全部五个,即使其中一两个在你的环境中看起来不太可能。

动手试试

这是一个概念课。自我检视:

  1. 不回头,说出五个陷阱
  2. 先选最有可能咬你环境的那种。为什么?
  3. 每遇到一个陷阱,画出失败时间线。哪里有固定门能抓住它?

如果你是朝真正的云后端构建,先把修复门写进生命周期钩子里,再写云端沙箱。门对本地后端没有影响,但现在加装比以后改装便宜。

提交

这节课没有代码。

完成标准

  • [ ] 你可以说出全部五个陷阱
  • [ ] 你可以描述每种的固定模式
  • [ ] 你可以识别模块4生命周期钩子中每个修复会插入哪些门

**注意:构建混沌模式**

生产沙箱故障和本地开发失败看起来是不同的。为测试运行构建一个--chaos标志,每次会话随机注入一次失败:命令中途终止沙箱进程、重新连接时返回过时的句柄、强制缓存和提供者之间状态分歧,或者跳过一次状态更新。把你的全部 Agent 循环都用混沌模式运行。 首先出问题的是你忘了防御的陷阱。

非官方简体中文翻译 · 原课程来自 Vercel Academy