Agent 在会话中生成的产物写到了临时目录而非仓库,Agent 重启后无法重建,直接删除了引用该文件的代码。问题根源是 Agent 将「环境破损」当「代码错误」处理,而非尝试恢复构建产物。
我离开时构建是绿的,Agent 的总结也声称重构已完成。十二小时后,免费服务器在夜间自动回收,同样的代码库在 CI 中失败,报错是缺少一个生成的文件。恢复任务的 Agent 没有尝试重建缺失的产物,而是删除了引用它的 import 语句,悄无声息地抹掉了我们刚刚上线的功能。这个失败值得剖析,因为它不是代码 bug,也不是模型故障,而是关于持久化的工作流假设——这类假设在大多数 Agent 配置中从未被测试过。
表象看起来像一个糟糕的补丁
我的第一个假设是 Agent 生成了一个细微错误的 diff,所以我从它的日志中回放了上一个会话。日志显示生成步骤成功、测试通过、以及一次提交,其中引用了一个名为 src/generated/api_client.ts 的文件。但那个文件从未出现在提交中,因为 Agent 把它写到了工作区而不是代码库,而一次全新的克隆根本无法知道它曾经存在。导致构建破坏的那个补丁,是对破碎环境的理性响应:Agent 看到一个无法解析的 import,就把它删掉了——把症状当成了原因。
从干净的树开始复现
复现这个失败是转折点。一次干净的 checkout 加上 git clean -fdx 会立即失败,而肮脏的工作区仍然通过,这证明了工作区携带着代码库中没有的状态。真正的原因是关于持久化的假设,而修复必须改变这个假设,而不是代码。免费服务器天生就是 ephemeral 的,这是特性而非缺陷;缺陷在于我的工作流——因为我让 Agent 把服务器的本地磁盘当作生成产物的持久化存储,而且没有给它任何方式来验证它的世界已经被重置。
这个情况让我想起缓存失效:Agent 对工作区的心理模型就是缓存,而重启就是它从未注意到的失效事件。当 token 免费时,成本模型就变了,Agent 可以跑很长的循环并无限重试,这让它更不可能为自己的状态做检查点。瓶颈从计算成本转移到了状态持久化,而这正是这类失败藏身的地方。
三种扭转局面的技术
第一,在责怪 Agent 之前先从干净的树复现,因为肮脏的工作区可以掩盖无数的环境原罪。第二,明确盘点生成的产物,让缺失的文件成为可检测的条件,而不是在构建时才 Surprise。第三,每次服务器重启都跑一次冷启动演练,因为重启是你能得到的最便宜的测试。
下面是我现在在任何 Agent 任务前运行的冷启动检查脚本,配合一个声明哪些产物必须存在的小型清单:
#!/usr/bin/env bash
set -euo pipefail
# cold_start_check.sh — verify a workspace can rebuild from scratch.
# Run this before letting a coding agent start a task.
REPO_DIR="${1:-.}"
MANIFEST="${REPO_DIR}/.agent/artifacts.json"
if [[ ! -f "$MANIFEST" ]]; then
echo "No artifact manifest found. Create one before running agents." >&2
exit 1
fi
cd "$REPO_DIR"
# 1. Ensure the tree is clean enough to reproduce a fresh build.
if [[ -n "$(git status --porcelain)" ]]; then
echo "Workspace is dirty. Commit or stash changes before the cold-start check." >&2
exit 1
fi
# 2. Verify every declared generated artifact exists.
python3 - "$MANIFEST" <<'PY'
import json, pathlib, sys
manifest = json.loads(pathlib.Path(sys.argv[1]).read_text())
missing = [p for p in manifest["required"] if not pathlib.Path(p).exists()]
if missing:
print("Missing generated artifacts:", ", ".join(missing), file=sys.stderr)
print("Regenerate with:", manifest.get("regenerate", "unknown command"), file=sys.stderr)
sys.exit(1)
print(f"All {len(manifest['required'])} generated artifacts are present.")
PY
# 3. Run the build with a clean output directory.
rm -rf .build
./build.sh
echo "Cold-start check passed."
{
"required": [
"src/generated/api_client.ts",
"schema.prisma",
"pnpm-lock.yaml"
],
"regenerate": "pnpm generate"
}
将构建命令调整为你项目的对应命令,并保持清单足够小以便于人工审核。把清单加入代码库,把脚本加入 Agent 的 preflight 步骤,这样缺失的产物就会变成一条清晰信息的硬失败,而不是无声的删除。
一个合理的反对意见是:生成的文件本来就应该直接提交,对于小项目这是更好的答案。这套检查存在于提交生成产物不合理的场景,比如嵌入了机器特定路径或密钥的文件,或者每次部署都需要重新生成的产物。在这些情况下,清单就是 Agent 和环境之间的契约,而冷启动检查就是执行机制。
我还在 Agent 的指令中加入了完成规则,让它在无法证明工作区可以从头重建的情况下不能报告成功:
在报告任务完成之前:
1. 运行 ./cold_start_check.sh .
2. 如果失败,用 `pnpm generate` 重新生成产物,然后重新运行检查。
3. 永远不要依赖只存在于当前会话中的文件。
声明:本文是 MonkeyCode 产品推广的一部分。MonkeyCode 是一个开源项目,目前提供免费模型访问(1000 万 token)和免费服务器选项,两者都与这个故事相关。免费服务器使得 ephemeral 工作区的失败易于复现,而免费模型访问使得长周期 Agent 循环变得可负担,但两者都要求同样的纪律约束。把服务器当作一次性环境使用,声明你的产物,并在信任结果之前从头验证。
局限性,以及谁不应该使用这个方法
这个方法有局限。冷启动检查只能看到代码库内的文件,所以它不会捕获 Agent 保存在自己外部内存或数据库中的状态。它还假设环境是可重现的;如果服务器镜像在重启之间改变了工具版本,检查可能通过但构建仍然会失败。团队如果只有一台从不重启的长生命周期服务器,可能不需要这个,而从不生成产物的 Agent 会发现清单是一场空洞的仪式。
受益的受众是在 ephemeral 基础设施上运行长周期 Agent 任务的人,而这正是免费服务器吸引的受众。教训不是免费基础设施不可靠;而是廉价资源改变了失败发生的位置。当你去掉了 token 和计算的成本,剩下的约束就是状态,而状态必须被显式声明才能被调试。如果你想压力测试这个模式,MonkeyCode 是开源的,它的免费服务器选项给你一个低成本环境来破坏东西。