Skip to content

Run Harbor Compose tasks on local Docker - #2547

Open
xeophon wants to merge 31 commits into
mainfrom
harbor-compose
Open

xeophon wants to merge 31 commits into
mainfrom
harbor-compose

Conversation

@xeophon

@xeophon xeophon commented Sep 6, 2026

Copy link
Copy Markdown
Member

Harbor runs each task's authored Docker Compose project on local Docker. The Harbor environment owns startup, readiness, and cleanup; the agent borrows an ordinary DockerRuntime attached to the project's main container.

DockerRuntime.attach() inspects an existing container and provides the shared command, process, file, and callback interfaces without taking ownership of container removal. Compose preserves authored images, working directories, entrypoints, dependencies, health checks, networks, and volumes, with explicit task/runtime overrides applied to main.

Projects are removed on completion, startup failure, or cancellation. Whole-episode retries create fresh projects through env.retries. This supports public-network CPU tasks on local Docker; separate grading retains its ordinary fresh verifier runtime.

Note

Add Harbor Compose, Podman, and Apptainer runtime support

  • Introduces HarborComposeRuntime to run Harbor tasks using local Docker Compose. It validates the service topology, applies runtime resource overlays, starts the project, exposes the owner service port, and manages cleanup.
  • Refactors common container CLI logic into ContainerRuntime in container.py and adds PodmanRuntime and ApptainerRuntime alongside the existing DockerRuntime.
  • Updates EgressProxy in egress.py to support tokenized HTTP(S) host callbacks, allowing loopback services inside local containers to be accessed via the proxy.
  • Adds runtime_factory parameter to Agent.run and Rollout.open in agent.py and rollout.py to enable per-attempt runtime construction for Harbor tasks.
  • Changes mcp.reachable_url in launch.py to use runtime-native port exposure for non-colocated services, tunneling only host-local services accessed by remote consumers.
  • Risk: HarborComposeRuntime in harbor/runtime.py rejects restricted-network or GPU configurations. DockerRuntime.host_url in docker.py routes all loopback URLs through proxy callback URLs, changing behavior for unrestricted runtimes.

Changes since #2547 opened

  • Introduced ComposeProject class in harbor.compose to own Compose project lifecycle [d9e6bd1]
  • Modified HarborEnv.run to own Compose projects and pass borrowed runtimes to agents [d9e6bd1]
  • Added DockerRuntime.attach classmethod async context manager to support borrowing existing containers [d9e6bd1]
  • Removed runtime_factory parameter from Agent.run, _EpisodeAgent.run, and Rollout.__init__ [d9e6bd1]
  • Updated DockerRuntime.cleanup to skip container removal for borrowed containers [d9e6bd1]
  • Added documentation clarifying Compose project ownership model in Harbor [d9e6bd1]
  • Fixed port selection in ComposeProject compose attach/runtime step to always use localhost address [58a4790]
  • Removed precomputed caching from ComposeProject in favor of on-the-fly computation [58a4790]
  • Reorganized documentation structure in Harbor guide [58a4790]

Macroscope summarized d66e109.


Note

Medium Risk
New Compose project lifecycle and borrowed-container semantics affect sandbox startup, networking, and cleanup; mis-cleanup or attach bugs could leak containers or break agent connectivity.

Overview
Adds local Docker Compose execution for Harbor tasks that ship environment/docker-compose.yaml, with runtime.type = "docker" and public-network CPU only.

ComposeProject owns the Compose lifecycle: validates topology (no container_name, no host networking), merges Harbor prebuilt/base files with task overrides and resource limits on main, runs docker compose up --wait, publishes the owner service on loopback, and tears the project down on exit, failure, or cancellation (with atexit backstop). Retries get a fresh project via --env.retries.

DockerRuntime.attach() lets the agent borrow the main container without removing it on cleanup; cli gains optional subprocess env for Compose infra variables.

HarborEnv.run starts Compose when the compose file exists, passes the attached runtime into agents.agent.run, and keeps separate-verifier defer/collect behavior on the same path. Docs describe ownership, limits (no GPU/restricted network, no sidecar collect), and that entrypoint keepalive applies only outside Compose.

Reviewed by Cursor Bugbot for commit 58a4790. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T15:14:28.484338Z d66e109 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment on lines +69 to +71
overlay["services"].setdefault(owner, {})["extra_hosts"] = {
"host.docker.internal": "host-gateway"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High harbor/runtime.py:69

When main uses network_mode: service:<sidecar>, the agent cannot resolve the host.docker.internal hostname in callback URLs, so model/interception requests fail. The mapping is added only to owner; add it to both main and the namespace owner.

-        overlay["services"].setdefault(owner, {})["extra_hosts"] = {
-            "host.docker.internal": "host-gateway"
-        }
+        for service in {owner, "main"}:
+            overlay["services"].setdefault(service, {})["extra_hosts"] = {
+                "host.docker.internal": "host-gateway"
+            }
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/tasksets/harbor/runtime.py around lines 69-71:

When `main` uses `network_mode: service:<sidecar>`, the agent cannot resolve the `host.docker.internal` hostname in callback URLs, so model/interception requests fail. The mapping is added only to `owner`; add it to both `main` and the namespace owner.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 28dffe7255

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment on lines +68 to +71
overlay = {"services": {"main": main}}
overlay["services"].setdefault(owner, {})["extra_hosts"] = {
"host.docker.internal": "host-gateway"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the host callback alias to the main container

When main uses network_mode: service:<sidecar>, owner becomes that sidecar and this installs host.docker.internal only in the sidecar. Although the containers share a network namespace, their /etc/hosts files are separate, so the agent running in main cannot resolve the hostname returned by host_url() and consequently cannot reach the interception/model or host-local MCP endpoints. Keep any owner-specific setup needed for forwarding, but add the alias to main as well.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread verifiers/v1/tasksets/harbor/compose.py
Comment thread verifiers/v1/tasksets/harbor/compose.py
@macroscopeapp

macroscopeapp Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a substantial Harbor-to-local-Docker Compose execution path with new container ownership, networking, cleanup, and retry behavior. Unresolved comments identify concrete runtime failures and potential host-level security exposure, so the change warrants human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@xeophon
xeophon changed the base branch from main to feat/local-container-runtimes September 6, 2026 18:10
Comment thread verifiers/v1/tasksets/harbor/compose.py
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6bff76b193

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment thread verifiers/v1/tasksets/harbor/env.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 141d878fba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/tasksets/harbor/env.py Outdated
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
await self._compose("config", "--quiet")
self._created = True
# Finish an interrupted Compose launch before deleting its partial project.
await run_shielded(self._compose("up", "--detach", "--wait"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate task-authored Compose privileges behind explicit trust

When a Harbor package comes from an untrusted or compromised registry, this starts its Compose file without validating host bind mounts, Docker-socket mounts, privileged, host PID namespaces, or devices. Unlike the existing constrained DockerRuntime, Compose honors those settings, and writable bind mounts can modify arbitrary host files, so merely loading such a task can yield host-level code execution before the agent starts (Docker documents this bind-mount security implication). Reject host-capability settings or require an explicit trusted-Compose opt-in before executing the file.

Useful? React with 👍 / 👎.

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d66e109f55

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +99 to +101
if "image" in services["main"] or "build" in services["main"]:
# A template default must not replace an authored image or skip its build.
base["services"]["main"].pop("image", None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow Compose-managed Dockerfiles through task loading

When a Compose task defines main.build with the conventional environment/Dockerfile and no [environment].docker_image, this build-preservation branch is unreachable: parse_task() first calls resolve_image(), which raises at taskset.py:531-539 under the default ignore_dockerfile=false. Consequently a common build-based Compose topology cannot be loaded even though this adapter explicitly preserves and runs its build; exempt Compose-managed builds from the legacy Dockerfile rejection.

Useful? React with 👍 / 👎.

Comment on lines +110 to +111
"--project-directory",
str(environment),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run Compose from a per-rollout environment copy

When an authored service uses a writable relative bind such as ./state:/state, this project directory resolves the host path inside the cached task.data.task_dir/environment, which dataset_dir() reuses for every episode. Distinct project names and down --volumes do not isolate bind-mounted files, so one solver or retry can mutate the starting state observed by later or concurrent rollouts; copy the environment into the runtime's temporary directory and use that copy as the Compose project directory.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d66e109. Configure here.

Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
from verifiers.v1.tasksets.harbor.taskset import HarborTask


class HarborComposeRuntime(DockerRuntime):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm why do we need a new runtime for this?

Base automatically changed from feat/local-container-runtimes to main September 17, 2026 20:04
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Comment thread verifiers/v1/tasksets/harbor/compose.py
Comment thread verifiers/v1/tasksets/harbor/compose.py Outdated
Comment thread verifiers/v1/tasksets/harbor/runtime.py Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

async with project as runtime:
await agents.agent.run(
task.defer_scoring() if separate else task,
runtime=runtime,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High harbor/env.py:54

Retryable rollout failures receive only one attempt when a Compose project is configured, even if --env.retries or agent retries are set. Passing the already-open project through runtime= makes it a borrowed runtime, so Agent.run disables its retry loop; create the project per attempt via a runtime factory instead of wrapping the entire run call.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/tasksets/harbor/env.py around line 54:

Retryable rollout failures receive only one attempt when a Compose project is configured, even if `--env.retries` or agent retries are set. Passing the already-open project through `runtime=` makes it a borrowed runtime, so `Agent.run` disables its retry loop; create the project per attempt via a runtime factory instead of wrapping the entire `run` call.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Comment on lines +113 to +116
if "image" in services["main"] or "build" in services["main"]:
# A template default must not replace an authored image or skip its build.
base["services"]["main"].pop("image", None)
base_file = directory / "base.json"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High harbor/compose.py:113

An authored main image or build runs the template's command: ["sh", "-c", "sleep infinity"] instead of its default CMD/ENTRYPOINT, so the application never starts and the declared topology can fail. Remove the template command alongside its image when preserving an authored image or build.

 if "image" in services["main"] or "build" in services["main"]:
     # A template default must not replace an authored image or skip its build.
     base["services"]["main"].pop("image", None)
+    base["services"]["main"].pop("command", None)
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/tasksets/harbor/compose.py around lines 113-116:

An authored `main` image or build runs the template's `command: ["sh", "-c", "sleep infinity"]` instead of its default CMD/ENTRYPOINT, so the application never starts and the declared topology can fail. Remove the template `command` alongside its `image` when preserving an authored image or build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants