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

# Harbor

> Run Harbor agent trials and evals on Boat 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.

[Boat](/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 Boat API, so your Harbor trials run on Boat 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 Boat API key. No prior Harbor or Boat 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`).
* **Boat** 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 Boat account">
    Sign in at [box.ascii.dev](https://box.ascii.dev) with GitHub and start a Boat plan, which includes a free 7-day trial. You need an account to create boxes. See the [Boat quickstart](/box/quickstart) for the full onboarding walkthrough.
  </Step>

  <Step title="Get a Boat API key">
    Create a key from the [API keys](/box/api-keys) tab of the Boat 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 Boat 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 Boat.
  </Step>
</Steps>

## A complete example

This standalone script provisions a Boat-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 | Boat 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 Boat-specific options:

| Option                    | Default                     | Description                                                                                          |
| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
| `api_key`                 | `os.environ["BOX_API_KEY"]` | Boat API key.                                                                                        |
| `base_url`                | public Boat API             | Boat 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

Boat 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 Boat 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 Boat dashboard's [Secrets](https://box.ascii.dev/box/dashboard?tab=environment) and [Repositories](https://box.ascii.dev/box/dashboard?tab=environment) tabs. See [Environments](/box/environments).

## Not available on Boat

Some Harbor features don't map to Boat 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="Boat quickstart" icon="rocket" href="/box/quickstart">
    Install Boat and create your first sandbox.
  </Card>

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