> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ascii.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Harbor

> Run Harbor agent trials and evals on Box with the harbor-box environment adapter: a zero-to-one guide for newcomers to either tool.

[Harbor](https://pypi.org/project/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](/box/quickstart) 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.

<Note>
  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.
</Note>

## 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

<Steps>
  <Step title="Create a Box account">
    Sign in at [box.ascii.dev](https://box.ascii.dev) with GitHub and start a Box plan, which includes a free 7-day trial. You need an account to create boxes. See the [Box quickstart](/box/quickstart) for the full onboarding walkthrough.
  </Step>

  <Step title="Get a Box API key">
    Create a key from the [API keys](/box/api-keys) tab of the Box dashboard. Keep it secret: store it in an environment variable, never in source control.

    ```bash theme={null}
    export BOX_API_KEY=box_your_real_key_here
    ```
  </Step>

  <Step title="Install the package">
    ```bash theme={null}
    pip install harbor-box
    ```

    This pulls in Harbor (`harbor>=0.14.0`) along with `httpx` and `tenacity`. Requires Python 3.12+.
  </Step>

  <Step title="Wire it into Harbor">
    Point Harbor's environment at Box by passing the adapter's import path when you run a task:

    ```bash theme={null}
    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.
  </Step>
</Steps>

## 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.

```python title="hello_box.py" theme={null}
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 10 min after creation
        )

        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())
```

```bash theme={null}
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` operation | Box behaviour                                                                                          |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `start` / `stop`                   | Creates and waits for a Box, or stops and archives it.                                                 |
| `exec`                             | Executes a bounded command in the Box work directory, with optional `cwd`, `env`, `user`, and timeout. |
| `upload_file` / `download_file`    | Transfers files to and from the Box (base64 for binary).                                               |
| `upload_dir` / `download_dir`      | Recursively transfers directory trees.                                                                 |
| `ensure_dirs`                      | Creates the configured work and mount directories.                                                     |
| `is_file` / `is_dir`               | Checks 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:

| Option                    | Default                     | Description                                                                                          |
| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
| `api_key`                 | `os.environ["BOX_API_KEY"]` | Box API key.                                                                                         |
| `base_url`                | public Box API              | Box API base URL.                                                                                    |
| `ttl_seconds`             | `86400`                     | Auto-stop TTL in seconds. `None` disables auto-stop.                                                 |
| `no_env`                  | `True`                      | Create [no-env boxes](#isolation) (strong isolation). Set `False` to use your account's environment. |
| `request_timeout_seconds` | `30`                        | Per-request HTTP timeout.                                                                            |
| `client`                  | none                        | A 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`.

```python theme={null}
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](/box/api/reference/boxes/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](https://box.ascii.dev/box/dashboard?tab=secrets) and [Repositories](https://box.ascii.dev/box/dashboard?tab=repos) tabs. See [Secrets & Setup](/box/secrets).

## 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

<CardGroup cols={2}>
  <Card title="PyPI package" icon="python" href="https://pypi.org/project/harbor-box/">
    `harbor-box` on PyPI.
  </Card>

  <Card title="Source on GitHub" icon="github" href="https://github.com/ariana-dot-dev/harbor-box">
    Issues, evals, and the adapter source.
  </Card>

  <Card title="Box quickstart" icon="rocket" href="/box/quickstart">
    Install Box and create your first sandbox.
  </Card>

  <Card title="Box API keys" icon="key" href="/box/api-keys">
    Create and manage the key this adapter needs.
  </Card>
</CardGroup>
