第 12 章

第 12 章:沙箱——本地与远程执行环境

第 12 章:沙箱——本地与远程执行环境

评测的可信度取决于执行环境:agent 必须在隔离的沙箱里解题,结果才可复现、宿主机才安全。Harbor 预集成了 4 种本地运行时与 27 种远程沙箱,本章介绍它们的安装与选配方式、能力差异,并逐方法拆解 BaseEnvironment 接口——读完你就能接入自己的沙箱 provider。

沙箱在评测中的角色

Harbor 的每次 trial 都运行在一个隔离环境中:agent 只能看到任务环境,宿主机、模型凭据、其他 trial 的状态都被挡在外面。这带来两个直接收益:隔离——agent 的任意 shell 操作(包括失败与破坏性操作)都不伤及宿主、互不干扰;可复现——任务的 environment 定义(如 environment/Dockerfile)描述了完整环境,换机器、换 provider 重放结果一致。

本地运行时(docker 等)启动快、零云成本,适合开发调试;远程沙箱通过 provider API 运行,释放本地资源、支持更高并发,适合大规模跑分。

命名上有个官方自嘲:Harbor 在 CLI 与配置里把沙箱叫 "environments"——--env/-e 旗标与 environment.type 配置键指的都是沙箱。"名字不好,我们知道。但现在改已经太晚了。"

安装与运行

沙箱依赖默认不安装,按需安装(extra 名即沙箱名,以 Daytona 为例):

uv tool install "harbor[daytona]"   # 单装某个沙箱,名字可替换
uv tool install "harbor[cloud]"     # 一次装全部预集成沙箱

本地运行时(如 docker)不需要额外的 Harbor Python 依赖,但其运行时或 CLI 本身仍需安装。从源码运行 Harbor 时,用 --extra 安装可选依赖:

export DAYTONA_API_KEY="..."
uv run --no-dev --extra daytona harbor run \
  -t hello-world/hello-world \
  -a codex -m openai/gpt-5.6-sol \
  -e daytona

云端沙箱的典型用法(-e 选沙箱,-n 控并发):

export DAYTONA_API_KEY="..."
harbor run \
  -d terminal-bench@2.0 \
  -a codex -m openai/gpt-5.6-sol \
  -e daytona \
  -n 32

等价的 config.json:

{
  "n_concurrent_trials": 32,
  "environment": {
    "type": "daytona"
  },
  "agents": [
    { "name": "codex", "model_name": "openai/gpt-5.6-sol" }
  ],
  "datasets": [
    { "name": "terminal-bench", "version": "2.0" }
  ]
}

provider 支持时会执行preflight 预检,否则在 provider 启动时报缺失配置。provider 凭据(如 DAYTONA_API_KEY)通过环境变量传入,哪些变量能进入沙箱请参阅 Environment variables 文档——凭据应留在 Harbor 进程,不要流进 agent 可见的范围。

预集成沙箱清单

本地运行时(4 种):docker(默认)、podman、apple-container、singularity。

远程沙箱(27 种):ack(使用 kubeconfig)、beam、blaxel、cua-cloud(extra 名为 cua)、cwsandbox、daytona、e2b、ec2、gke、hf-sandbox、hyperbrowser、islo、langsmith、modal、novita、opensandbox、openshift(使用 oc CLI)、runloop、skypilot(early access)、tensorlake、use-computer、vercel、runta、prime、mosaic、smol、sail。

本地/远程只是文档上的分组,在 Harbor API 中并非不同类型——切换 provider 时,任务的 environment 定义保持不变(受能力差异约束,见下节)。

常用 CLI 选项

Flag 用途 默认值
-e、--env 选择内置沙箱或自定义导入路径 docker
--ek、--environment-kwarg 传递 provider 特有的构造参数,可重复 None
-n、--n-concurrent 限制并发 trial 数 4
--force-build / --no-force-build 重建或复用任务环境 --no-force-build
--delete / --no-delete trial 结束后删除或保留沙箱 --delete
--cpus、--memory 选择 auto、limit、request、guarantee 或 ignore auto
--override-cpus 等 本次运行覆盖任务的 CPU/内存/存储/GPU/TPU 配置 任务配置
--env-file 加载宿主凭据等环境变量 None

注意 provider 的生命周期规则可能覆盖 --no-delete。完整选项以 harbor run --help 为准。

能力矩阵

不同沙箱支持的能力不同,且可能依赖任务模式或 provider 设置。以下矩阵按三类汇总(节选常见项,完整清单见官方页面)。

任务与硬件能力:

能力 支持的环境
Docker Compose docker、podman、daytona、modal、ec2、gke、islo、langsmith、novita、blaxel、beam、hyperbrowser、vercel、runta、prime、sail
GPUs daytona、modal、gke、beam、opensandbox、prime、docker
TPUs gke
Windows docker、daytona、cua-cloud、use-computer
Host-mounted logs docker、podman、apple-container、singularity
Stream over SSH daytona、docker、modal、tensorlake

网络管控能力(评测防 reward hacking 的关键):

能力 支持的环境
禁用外网 docker、podman、daytona、e2b、modal、runloop、langsmith、ec2、gke、novita、islo、tensorlake、cwsandbox、blaxel、opensandbox、beam、skypilot、hyperbrowser、vercel、runta、prime、mosaic、smol、sail
精确域名放行 docker、podman、daytona、e2b、modal、runloop、langsmith、novita、islo、tensorlake、blaxel、beam、hyperbrowser、vercel、runta、prime、mosaic、smol、sail
IPv4 CIDR 放行 docker、podman、daytona、modal、novita、tensorlake、beam、hyperbrowser、prime、mosaic、smol、sail
运行期策略变更 docker、podman、daytona、e2b、modal、novita、islo、beam、hyperbrowser、vercel、runta、prime、sail

CPU 与内存能力:

能力 支持的环境
CPU / 内存 limit docker、podman、apple-container、modal、gke、openshift、skypilot、cwsandbox、opensandbox、ec2、runta、prime、smol
CPU / 内存 request daytona、e2b、modal、runloop、gke、openshift、novita、islo、tensorlake、cwsandbox、beam、skypilot、hyperbrowser、vercel、runta、mosaic、sail 等

三个资源策略语义要分清:limit 是硬上限,request 是预留/选择容量,guarantee 要求两者兼备。一旦 provider 声明了资源能力,Harbor 会在 trial 开始之前拒绝不支持的策略,而不是跑到一半失败。

自定义沙箱:BaseEnvironment 接口逐方法详解

预集成清单之外的 provider,通过实现 BaseEnvironment 接入。与自定义 agent 一样不注册名字,把 模块路径:类名 传给 --env(-e)即可,provider SDK 需与 Harbor 装在同一 Python 环境:

harbor run ... -e my_sandbox:MySandbox --ek region=us-west-2

配置文件与 Python API 中对应的键是 environment.import_path,构造参数走 environment.kwargs。下面是完整的接口实现模板:

from pathlib import Path

from harbor.environments.base import BaseEnvironment, ExecResult
from harbor.environments.capabilities import EnvironmentCapabilities


class MySandbox(BaseEnvironment):
    @staticmethod
    def type() -> str:
        return "my-sandbox"

    @property
    def capabilities(self) -> EnvironmentCapabilities:
        return EnvironmentCapabilities()

    def _validate_definition(self) -> None:
        # 校验任务的 environment/ 目录。
        ...

    async def start(self, force_build: bool) -> None:
        # 构建或创建沙箱,然后准备 prebuilt-image 任务。
        ...
        await self._upload_environment_dir_after_start()

    async def stop(self, delete: bool) -> None:
        ...

    async def exec(
        self,
        command: str,
        cwd: str | None = None,
        env: dict[str, str] | None = None,
        timeout_sec: int | None = None,
        user: str | int | None = None,
    ) -> ExecResult:
        env = self._merge_env(env)
        user = self._resolve_user(user)
        # 调用 provider 执行命令,返回 stdout、stderr 与返回码。
        ...

    async def upload_file(self, source_path: Path | str, target_path: str) -> None:
        ...

    async def upload_dir(self, source_dir: Path | str, target_dir: str) -> None:
        ...

    async def download_file(self, source_path: str, target_path: Path | str) -> None:
        ...

    async def download_dir(self, source_dir: str, target_dir: Path | str) -> None:
        ...

各方法的职责与实现要点:

方法 职责
type() 返回沙箱类型名,即 CLI -e 与 environment.type 使用的标识
capabilities 声明 provider 真正强制执行的能力(如是否支持 Docker Compose);只声明真实支持的,多报会导致 Harbor 放行实际不可行的任务策略
_validate_definition() 校验任务的 environment/ 目录,缺必需文件(如 environment/Dockerfile)时拒绝;无文件要求可为空实现
start(force_build) 构建或创建沙箱;对 prebuilt-image 任务做相应准备;收尾调用 _upload_environment_dir_after_start() 上传环境目录
stop(delete) 按 delete 决定销毁还是保留沙箱
exec(...) 核心执行入口:先 self._merge_env(env) 再 self._resolve_user(user),然后调用 provider,返回含 stdout、stderr、return code 的 ExecResult
upload_file / upload_dir / download_file / download_dir 文件与目录双向传输,agent 与 verifier 的产物都靠它们进出沙箱
_merge_env(env) 合并沙箱级、agent 阶段、verifier 阶段三组环境变量——每个 exec() 实现都必须先调用它
_resolve_user(user) 应用 Harbor 的默认执行用户——同样每个 exec() 实现都必须调用

两个补充要求:若 capabilities 声明了 docker_compose,还需实现多容器(Compose)任务所需的按服务执行与文件传输方法;建议实现 preflight(),在 trial 排队前检查 provider 凭据,把"钥匙没配"拦截在最早时刻。凭据始终保存在 Harbor 进程内。

本章小结

  • 每次 trial 运行在隔离沙箱中,隔离与可复现是评测可信度的前提;CLI 与配置中沙箱叫 "environment"(-e、environment.type)。
  • 沙箱依赖默认不装:uv tool install "harbor[xxx]" 按需安装,"harbor[cloud]" 全量安装,本地运行时只需装好 docker 等 CLI。
  • 预集成清单:4 种本地运行时(docker 默认、podman、apple-container、singularity)加 27 种远程沙箱。
  • 常用旗标:-e 选沙箱、--ek 传 provider 参数、-n 控并发、--cpus/--memory 选资源策略(auto/limit/request/guarantee/ignore)。
  • 能力矩阵分任务硬件、网络管控、CPU 内存三类;limit 是硬上限、request 是预留、guarantee 二者兼备;不支持的策略会在 trial 开始前被拒绝。
  • 自定义沙箱实现 BaseEnvironment,以 模块路径:类名 传给 -e 使用。
  • exec() 实现必须先调用 _merge_env() 与 _resolve_user();capabilities 只声明真实强制执行的能力。
  • start() 收尾记得 _upload_environment_dir_after_start();声明 docker_compose 需补齐按服务的方法;preflight() 用于提前校验凭据。

延伸阅读