编码 Agent 在示例仓库里很容易显得可靠:依赖已经安装,测试很快,没有生产密钥,也没有用户未提交修改。进入真实仓库后,危险通常不是它写不出代码,而是它能访问太多:误改无关目录、运行带副作用脚本、读取 .env、通过网络上传上下文,或让一个失控测试占满机器。

我现在把执行环境当成产品能力,而不是运行命令前的准备工作。任务契约先定义允许影响,沙箱再从文件、网络、秘密和资源四个维度执行边界。

编码 Agent 从任务契约、隔离沙箱到补丁与测试证据的架构

图 1:Agent 的自由度存在于沙箱内部;能否越过边界不依赖模型“自觉”。

每个任务先生成可执行契约

taskId: task-20260117-042
repository: console
baseSha: 8f12ac9
allowedPaths:
  - src/search/**
  - src/components/search/**
readOnlyPaths:
  - package.json
  - src/lib/content.ts
forbiddenCommands:
  - deploy
  - terraform apply
networkPolicy: registry-readonly
timeBudgetSeconds: 900
verification:
  - pnpm check
  - pnpm test -- search

契约由任务分解器和仓库策略共同生成,模型可以建议扩大范围,但扩大需要重新授权。提示里写“不要改其他文件”是沟通,文件系统策略才是边界。

用临时工作树保护用户现场

Agent 不直接在开发者当前工作区修改。执行器从 baseSha 创建独立 worktree 或快照,只挂载允许写目录,用户未提交文件不进入沙箱。任务结束交付 patch、提交候选和验证结果,由人决定怎样合并。

host repository (read-only metadata)
  └── sandbox worktree @ baseSha
      ├── writable: allowedPaths
      ├── read-only: dependency manifests
      └── hidden: .env, credentials, unrelated private files

如果 baseSha 已经落后,不能在沙箱里偷偷 merge 最新主分支。交付时显式报告基线,重新基于新 SHA 验证。

网络默认拒绝,按任务开放

多数代码修改只需要已缓存依赖和本地文档。需要安装包时,网络策略只允许公司 registry 的 GET,禁止任意域名与上传;需要访问 issue API 时,经工具代理返回最小字段,不把通用网络 socket 交给 Agent。

需求 开放方式
安装锁文件已有依赖 只读 registry + 完整性校验
查询官方文档 受控文档连接器,记录 URL
调用测试服务 短期测试身份 + 环境白名单
生产数据库/云控制面 沙箱内禁止
用户提供的未知 URL 先解析风险,单独批准

秘密通过代理使用,不进入上下文

测试需要凭证时,沙箱获得一次性、最小范围 token;工具代理执行请求并过滤响应。模型看不到长期 key,stdout 和补丁扫描也会阻止秘密外泄。任务结束立即撤销 token。

资源预算防止“正确但失控”

执行器限制 CPU、内存、进程数、磁盘和墙钟时间。每条命令有单独超时,输出达到上限后截断并保存 artifact。无限测试监听、递归扫描和巨量日志都不能拖垮宿主。

type CommandBudget = {
  timeoutMs: number;
  maxOutputBytes: number;
  maxProcesses: number;
  memoryMb: number;
};

超预算不是普通“测试失败”,交付中标记为环境/范围问题,避免模型通过反复重跑消耗更多资源。

交付物是一组证据

最终结果包含 baseSha、修改路径、diff、命令与退出码、测试报告、未验证项、越界请求和风险提示。沙箱日志能回答每个文件何时改变、哪个命令产生、网络访问过哪里。

我不指望沙箱证明代码一定正确。它解决的是另一类问题:即使 Agent 判断错误,影响也被限制在一个可回收环境;即使结果正确,团队也能看到它如何得到。对真实仓库而言,这种可控失败比偶尔惊艳的生成更重要。

沙箱是执行层,它之上还有两层约束:工具调用层面的授权与幂等,属于不要把生产密钥交给模型;评审层对“交付证据”的验收标准,属于AI 辅助编程之后的代码评审。三层各自负责一部分,Agent 才不会在任何一层获得完整万能权限。