Skip to main content
Harbor is a Python framework for running coding-agent tasks and evals. It runs each agent inside an environment, which is an isolated filesystem and shell the agent reads, writes, and executes in. Harbor lets you choose where that environment actually runs. Box is Ascii’s cloud sandbox: a full Linux machine you create over an API, run commands in, and read and write files in. harbor-box connects the two. It is a Harbor environment adapter (BoxEnvironment) that maps Harbor’s lifecycle, command, and file operations onto the Box API, so your Harbor trials run on Box machines instead of a local Docker container, E2B, or Modal.
New to both? You only need two things to follow this guide: Python 3.12+ and a Box API key. No prior Harbor or Box experience required.

How it fits together

  • Harbor owns your task/eval logic and calls an environment to do filesystem and shell work.
  • harbor-box is the adapter you plug into Harbor (it imports as harbor_box_environment).
  • Box is the machine the work actually happens on.
your Harbor task ──> Harbor BaseEnvironment ──> harbor-box (BoxEnvironment) ──> Box machine

Set it up

1

Create a Box account

Sign in at box.ascii.dev with GitHub and start the base plan, which includes a free 7-day trial. You need an account to create boxes. See the Box quickstart for the full onboarding walkthrough.
2

Get a Box API key

Create a key from the API keys tab of the Box dashboard. Keep it secret: store it in an environment variable, never in source control.
export BOX_API_KEY=box_your_real_key_here
3

Install the package

pip install harbor-box
This pulls in Harbor (harbor>=0.14.0) along with httpx and tenacity. Requires Python 3.12+.
4

Wire it into Harbor

Point Harbor’s environment at Box by passing the adapter’s import path when you run a task:
harbor task run path/to/task.yaml \
  --environment-import-path harbor_box_environment:BoxEnvironment
That’s it: Harbor trials for this task now run on Box.

A complete example

This standalone script provisions a Box-backed environment and exercises the operations Harbor relies on: running commands, reading files, and writing files. It runs on its own so you can verify your setup before wiring the adapter into a larger Harbor run.
hello_box.py
import asyncio
import tempfile
from pathlib import Path

from harbor.models.task.config import EnvironmentConfig
from harbor.models.trial.paths import TrialPaths
from harbor_box_environment import BoxEnvironment


async def main() -> None:
    with tempfile.TemporaryDirectory() as raw:
        tmp = Path(raw)
        # Harbor requires an environment definition. Box runs its default image;
        # the Dockerfile's WORKDIR drives the agent's working directory.
        env_dir = tmp / "environment"
        env_dir.mkdir()
        (env_dir / "Dockerfile").write_text("FROM ubuntu:24.04\nWORKDIR /workspace\n")
        trial_paths = TrialPaths(tmp / "trial")
        trial_paths.mkdir()

        env = BoxEnvironment(
            environment_dir=env_dir,
            environment_name="harbor-hello",
            session_id="hello-session",
            trial_paths=trial_paths,
            task_env_config=EnvironmentConfig(workdir="/workspace"),
            ttl_seconds=600,  # auto-stop the Box after 10 min of inactivity
        )

        await env.start(force_build=False)  # provisions a Box and waits until ready
        try:
            # Write a file, run a command against it, and read the result back.
            await env.upload_file(__file__, "/workspace/hello_box.py")
            result = await env.exec(
                "echo 'hello from Box' > notes.txt && cat notes.txt && uname -a"
            )
            print("exit:", result.return_code)
            print(result.stdout)
        finally:
            await env.stop(delete=True)


if __name__ == "__main__":
    asyncio.run(main())
BOX_API_KEY=box_... python hello_box.py
You should see the file contents, the machine’s uname output, and exit code 0.

What the adapter maps

Harbor BaseEnvironment operationBox behaviour
start / stopCreates and waits for a Box, or stops and archives it.
execExecutes a bounded command in the Box work directory, with optional cwd, env, user, and timeout.
upload_file / download_fileTransfers files to and from the Box (base64 for binary).
upload_dir / download_dirRecursively transfers directory trees.
ensure_dirsCreates the configured work and mount directories.
is_file / is_dirChecks for paths inside the Box.

Configuration

BoxEnvironment(...) takes Harbor’s standard arguments (environment_dir, environment_name, session_id, trial_paths, task_env_config) plus these Box-specific options:
OptionDefaultDescription
api_keyos.environ["BOX_API_KEY"]Box API key.
base_urlpublic Box APIBox API base URL.
ttl_seconds86400Auto-stop TTL in seconds. None disables auto-stop.
no_envTrueCreate no-env boxes (strong isolation). Set False to use your account’s environment.
request_timeout_seconds30Per-request HTTP timeout.
clientnoneA preconfigured AsyncBoxClient, if you want to supply your own.
BOX_API_KEY is required: pass api_key=... or set the environment variable. The adapter calls preflight() and fails fast if it is missing.

Preparing the environment

Box runs a ready image; it doesn’t build Docker images. The adapter prepares each box from your Harbor environment directory instead:
  • Files in the environment directory are uploaded into the box’s working directory at start, so fixtures, configs, and scripts are in place before the agent runs.
  • The Dockerfile is read only for its final-stage WORKDIR; build steps (RUN / COPY / …) are not executed; install dependencies from a setup command (await env.exec(...)) or your agent.
  • Environment variables come from the Harbor environment config and are forwarded into the box; per-command env is passed through exec.
task_env_config = EnvironmentConfig(workdir="/workspace", env={"DATABASE_URL": "postgres://..."})
To start many trials from the same prepared filesystem, prepare one box, stop it so its snapshot completes, then fork it; each clone keeps the entire filesystem. See Fork box.

Isolation

Boxes are created no-env by default: a trial gets none of your Box account’s secrets, credentials, or cloned private repos, and can’t act on your account or other boxes. This is the right default for evals and for boxes you hand to others. Pass no_env=False to use your account’s environment instead, configured in the Box dashboard’s Secrets and Repositories tabs. See Secrets & Setup.

Not available on Box

Some Harbor features don’t map to Box today and raise a clear error: network policies (no-network / allow-lists), Docker image builds / Compose, and GPUs / TPUs / Windows. Run those tasks on a backend that supports them.

Resources

PyPI package

harbor-box on PyPI.

Source on GitHub

Issues, evals, and the adapter source.

Box quickstart

Install Box and create your first sandbox.

Box API keys

Create and manage the key this adapter needs.