HomeBlogPricingCareersDocsGitHubSlack community
Field notes/Engineering/Run multi-container agent evals on Tensorlake with Harbor

Run multi-container agent evals on Tensorlake with Harbor

Harbor 0.23.0 can run Docker Compose tasks on Tensorlake. Each task boots as one MicroVM with a Docker daemon inside, so an agent’s MCP server, database, browser, or game server runs next to it as its own container.

Harbor 0.23.0 can run Docker Compose tasks on Tensorlake. Agent eval tasks often need more than one container. An agent may need a Model Context Protocol (MCP) server, a database, a browser, or a game server running next to it. We added support for these tasks, so each one boots as one MicroVM with a Docker daemon inside.

01 · Multi-container environments for RL

In the Harbor repository, 11 benchmarks and four example tasks ship a docker-compose.yaml. Each one runs at least one service next to the agent container. The benchmarks include SWE-Lancer, OSWorld, tau3-bench, CooperBench, Kumo, and FinanceAgent, which need databases, desktop environments, or simulated users.

Two of the examples show the pattern. The hello-mcp example runs an MCP server in a sidecar, and the agent has to connect to http://mcp-server:8000/mcp, call a tool, and write the result to a file. The sidecar-artifacts example runs a small web server in a second container. The agent has to send it three orders, and the grader reads the server’s request log from that container, not from the agent’s.

The task authors wrote these as multi-service compose environments. You could fold every service into one image, but then you are no longer running the benchmark as its author wrote it. To run these tasks faithfully, an environment provider has to run every service as its own container.

02 · How it runs: one MicroVM, one dockerd, N services

A Tensorlake sandbox is a MicroVM with its own Linux kernel. Harbor calls this mode DinD, short for Docker-in-Docker, but on Tensorlake the outer environment is a MicroVM, not a container. Harbor boots the VM from an image that has Docker installed, and Docker runs inside it as it would on any Linux server. You will still see the DinD name in the code and the image names below.

topology · one trial
Your machine (harbor CLI)
  └── Tensorlake Sandbox (MicroVM, booted from a Docker-capable image)
        ├── dockerd            ← supervised by systemd
        └── docker compose
              ├── main         ← the agent runs here
              ├── mcp-server   ← sidecar services from the task's compose file
              └── …

One trial goes like this:

  1. 1
    Harbor boots the sandbox from a host image with Docker Engine and the compose plugin installed.
  2. 2
    It starts docker.service through systemd and polls docker info until the command succeeds.
  3. 3
    It uploads the task’s environment/ directory and Harbor’s own compose overlays into the VM.
  4. 4
    It runs docker compose build, creates the containers, applies the task’s network policy to the sandbox, and starts the containers. Then it waits for the main service to be running.
  5. 5
    From then on, agent commands run through docker compose exec in main, and the agent’s normal file uploads and downloads also go through main.
  6. 6
    When the trial ends, Harbor runs docker compose down and deletes the sandbox.

03 · Harbor builds the host image once and shares it

Every compose task needs the same host image, so Harbor builds it once and reuses it. The Dockerfile is short: it starts from tensorlake/ubuntu-systemd and installs Docker Engine, containerd, Buildx, and the compose plugin.

The image name ends in a 12-character hash of that Dockerfile text, for example harbor-dind-host-3f9a1c2b7d4e. Before building, Harbor checks the Tensorlake registry for that name within your organization. If the image is there, Harbor boots it and skips the build. If not, Harbor builds it once and registers it in Tensorlake, and later trials boot it directly.

If you start several trials at once, Harbor serializes the host-image build step on your machine. The first trial builds and registers the image; the others can reuse it once the build finishes. Change the Dockerfile text and you get a new name, registered next to the old one, and trials on the old image keep working.

CUSTOM HOST IMAGE

If you need a different host image, pass it with --ek dind_image=your_image_ref. The default image relies on systemd to supervise dockerd, so a custom image should do the same.

04 · Compose-specific behavior

Compose mode changes two things you will notice: when Harbor applies the network policy, and how long boot takes.

Harbor applies the network policy after setup

A compose task needs internet access during setup, so Docker can pull images and build the environment. So Harbor creates the sandbox with outbound access open, runs docker compose build, and creates the containers without starting them.

For a task that restricts network access, whether no-network or allowlist, Harbor then applies that policy to the running sandbox. Only after Tensorlake accepts that update does Harbor start the compose services. No task service starts while the sandbox is still open. A public task skips this step and starts its services right after the build.

network policy · setup → agent execution
Setup
 
Tensorlake network ── open
  └── MicroVM
        └── dockerd
              ├── pull images
              ├── build images
              └── create containers
 
Agent execution
 
Tensorlake network ── task policy
  └── MicroVM
        └── docker compose
              ├── main
              ├── api
              └── db

Tensorlake enforces the policy outside the MicroVM, the same way it does for a single-container task. So the policy covers all outbound traffic from the VM, including every container under dockerd. Traffic between containers stays inside the VM and keeps working.

Allowlist policies work too. Tensorlake accepts hostnames, wildcard hostnames, IPv4 addresses, and IPv4 ranges.

Harbor can also update the policy while the compose environment is running. So the agent and verifier phases can use different network policies without restarting the containers.

Boot takes longer in compose mode

Compose mode does more work before the agent starts: dockerd has to come up, the task image builds inside the VM, and sidecar images download. So Harbor waits up to 120s for docker info to succeed, and up to 300s for the Compose services to start. A single-container sandbox needs neither wait.

Harbor also asks Tensorlake for at least 2 CPUs and 4 GB of memory for every task that declares resources. If your task.toml asks for more, Harbor passes it through.

05 · Run a compose task on Tensorlake

Set your API key and clone the Harbor repository. Then run the MCP example with the oracle agent:

~ $ harbor
export TENSORLAKE_API_KEY=your_tensorlake_api_key_here
harbor run -p examples/tasks/hello-mcp -e tensorlake --agent oracle

The oracle agent runs the task’s reference solution. A reward of 1.0 means the MCP sidecar came up, the health check passed, the agent container reached it by service name, and the verifier read the result.

In this MCP example, the agent writes its answer into its own container and the grader reads it there, so the sidecar is only a helper. In the next example, the proof of work never touches the agent’s container:

~ $ harbor
harbor run -p examples/tasks/sidecar-artifacts -e tensorlake --agent oracle

The agent sends three orders to an API running in the api sidecar. After the agent finishes, Harbor runs a command inside api to snapshot its request counter, copies that file and the request log out of api, and grades them in a separate verifier container. All of the grading evidence comes from the api sidecar. Compose support is therefore more than starting two containers, because the evaluation itself can depend on state outside the agent’s container.

To run a full benchmark, swap the path for a dataset:

~ $ harbor
harbor run --dataset swelancer -e tensorlake --agent claude-code \
  --model anthropic/claude-opus-5 --n-concurrent 8 -r 2

The -r 2 flag allows up to two retries for each trial that fails with a retryable error.

SW
WRITTEN BYShanshan WangApplied AI · Tensorlake
LinkedIn →Twitter / X →
Read next —FROM THE LOG
◆ THE SANDBOX DIGEST

Subscribe for release notes, benchmarks, deep dives.

One dispatch per month from the Tensorlake team — no spam.