Blog · 01 · 2026-10-10 · 5 min

Harbor on denv

Run any Harbor dataset with denv as the environment backend, import Harbor tasks as denv projects, and export denv environments as Harbor task directories.

Harbor is the task format labs mandate. A task is a directory: instruction.md, task.toml, an environment/ with a Dockerfile or a compose file, tests/test.sh that writes /logs/verifier/reward.txt, and a solution/. Datasets are published to Harbor Hub as org/name@tag, and harbor run drives one of about forty agents against them inside an environment backend: Docker, Daytona, Modal, E2B, Runloop, GKE, and others.

denv is one more backend on that list, with one difference. On every other backend an environment is a container that is rebuilt per episode. On denv it is a versioned, snapshotted, forkable system that keeps the five protections regardless of which agent runs inside.

There are three ways to use them together.

1. denv as the Harbor backend

Harbor on denv: each framework operation on the left becomes the denv call on the right.HARBOR · WHAT THE ADAPTER DOESharbor run -e denv.harbor:DenvEnvironmentstart()POST /v1/sandboxes · fork or imageexec()POST /v1/envs/{id}/execupload_dir()PUT /v1/envs/{id}/filesdownload_dir()GET /v1/envs/{id}/filesstop(delete)DELETE /v1/envs/{id}
Fig. 1 What the adapter does, call by call.

Install the package and point Harbor at it:

pip install "denv[harbor]"
export DENV_SERVER=https://api.denv.ai DENV_API_KEY=dak_…

harbor run --dataset terminal-bench/terminal-bench-2 \
           --agent claude-code \
           -e denv.harbor:DenvEnvironment

Nothing about the dataset or the agent changes. denv.harbor:DenvEnvironment implements Harbor's BaseEnvironment: start, stop, exec, upload_file, upload_dir, download_file, download_dir. Here is what each does on denv.

start. The task's [environment] config decides the sandbox:

In task.toml or environment/ What denv does
docker_image = "…" POST /v1/sandboxes {image} with volumes for /app, /logs, and the task workdir
docker-compose.yaml present the file is deployed as a project under your team; the agent runs in the first service, or the one marked x-denv.agent.service
only a Dockerfile built on the target by the sidecar, cached by content digest, then deployed as above
allow_internet = true egress: allow; otherwise egress is denied and the probe result is recorded
cpus, memory_mb become cpus and mem_limit on the agent service; Harbor's --cpus limit and --memory limit policies are honoured, guarantee is reported as unsupported

The sandbox returns when every service is healthy. The directories Harbor expects, /logs/agent, /logs/verifier, /logs/artifacts, /tests, /solution, are created before the agent starts.

exec. Harbor hands the adapter a shell string plus cwd, env, user, and a timeout. denv runs it through bash when the image has it and sh otherwise, with env exported first, as the requested user. Harbor's agents, oracle and verifier all pass through this one path. When Harbor sets a default user on the environment, that user is used; it is never root unless Harbor says user="root", which it does for setup and verification.

upload and download. Tar streams through PUT and GET /v1/envs/{id}/files. Directory download copies the directory's contents, the way docker cp src/. does, which is what Harbor's verifier expects when it fetches /logs/verifier into the trial directory.

stop. DELETE /v1/envs/{id} when Harbor asks to delete; otherwise the environment is kept for inspection and reaped by TTL.

Capabilities. The adapter reports disable_internet=True and docker_compose=True. It does not report mounted, so Harbor downloads logs rather than expecting a shared filesystem. GPU and TPU report false until device passthrough ships.

What a Harbor trial gains on denv

  • Every protection, without the task author doing anything. The agent service runs as the task's user, never root; caps are enforced; egress is denied and proven before the agent's first command. Harbor's own defaults leave three of Karotte's five protections unenforced; on denv they are properties of the environment.
  • Fork instead of rebuild. Harbor rebuilds the environment for each trial. With -k 5, five attempts means five builds. On denv the first trial deploys a version; the next four fork it in about a second each, data included, because DenvEnvironment keys the version on the task digest and reuses it.
  • A verdict log per trial. Each trial's environment has a deployment record: the pinned digests, the snapshot, the egress probe result, the caps. That record is the evidence you attach to a dataset when a lab asks how it was run.
  • Multi-service tasks that stay portable. Compose tasks run on denv unchanged. If a dataset must also run on Daytona or Modal, the single-container discipline those providers need is yours to keep; denv does not force it, and denv export --harbor flags tasks that exceed a target's limits.
denv verify: the agent runs in fork A as an unprivileged user with egress denied. Its processes are stopped, declared submission paths are copied to a root-owned location, special files are refused, fork B is made, the verifier image grades in fork B as a separate principal, and the reward and evidence land in the record.DENV VERIFY · THE GRADER NEVER SHARES A PROCESS TREE WITH THE AGENTagent runsagent runs fork A · uid 1000 · caps · egress ✗stopstop every agent process killedcollectcollect declared paths → root-owned copyrefuserefuse symlink · fifo · device → score 0forkfork fork B from Agradegrade verifier image · separate principalrecordrecord reward + evidence in the deploymentfork A is left as the agent left it, for inspection
Fig. 2 denv verify. Grading happens on a fork, as a separate principal, after the agent is stopped.

Limits that are real

Harbor's shared verifier runs tests/test.sh inside the same environment, as root, after the agent's turn. That is Harbor's design and the adapter honours it. If you want the grader as a separate principal on a fork, set [verifier] environment_mode = "separate" in task.toml; denv then forks the agent's environment at the end of the episode, stops the agent's processes in the fork, and runs the verifier there. The agent's environment is untouched. Both modes write the reward where Harbor expects it.

2. Import a Harbor task as a denv project

denv import harbor ./tasks/fix-backup-cron

Reads task.toml and environment/, writes a docker-compose.yaml with an x-denv block, creates a project of kind rl-environment, and deploys alpha. A compose task is used directly. A Dockerfile task becomes one service, built on your target. tests/test.sh becomes the x-denv.verifier command, solution/ is kept out of the image, and the task's allow_internet maps to x-denv.egress.

From there the task is a denv environment: push to redeploy, fork per rollout, snapshot mid-episode, promote through gates. You can keep publishing it to Harbor Hub from the same repo.

3. Export a denv environment as Harbor tasks

denv export --harbor ops-world@ver_01… ./dist

For each task declared in the project (a tasks/ directory with instruction.md and a verifier, or a pipeline that generates them), writes a Harbor task directory: task.toml with docker_image pinned to the version's digest, instruction.md, tests/test.sh wrapping the verifier, solution/ if present. Multi-service versions are exported as compose tasks, with a note of the memory and CPU a single-container target would need.

Export is how a denv environment reaches every trainer that reads Harbor: rLLM, verifiers, OpenEnv's Harbor environment, SkyRL's Harbor generator. You build and calibrate on denv, you publish in the format labs consume.

Conformance

The adapter is proven by running Harbor, not by unit tests of the adapter. make e2e-frameworks in the backend repo boots a control plane and a sidecar on one machine and runs:

harbor run -p tests/fixtures/harbor/hello -a oracle -e denv.harbor:DenvEnvironment

The oracle agent uploads the solution, runs it, the verifier writes reward 1, and Harbor downloads the logs. The run passes on a laptop with Docker and dir storage. We run it on every commit and will run Terminal-Bench 2 through it before calling the backend production-ready.

Status

Ships today. denv.harbor:DenvEnvironment for image-based and compose-based tasks; exec through bash/sh with env, cwd and user; file upload and download; allow_internet to egress; log directories pre-created; conformance with the oracle agent.

Committed by this post. Dockerfile-only tasks built on the target (today they are refused with a message to set docker_image); fork reuse across -k attempts keyed on task digest; cpus and memory_mb mapped to caps with enforcement policies reported; environment_mode = "separate" running the verifier on a fork as a separate principal; denv import harbor; denv export --harbor; Terminal-Bench 2 run through the backend with published results.