Skip to content

Run Harbor Compose tasks on Prime and Modal - #2549

Open
xeophon wants to merge 2 commits into
harbor-composefrom
harbor-compose-cloud
Open

Run Harbor Compose tasks on Prime and Modal#2549
xeophon wants to merge 2 commits into
harbor-composefrom
harbor-compose-cloud

Conversation

@xeophon

@xeophon xeophon commented Sep 6, 2026

Copy link
Copy Markdown
Member

Extends Harbor Compose tasks to Prime and Modal VM sandboxes, using one project owner and the existing Docker execution machinery across local and cloud runs.

Depends on #2547, which builds on #2528.

The shared container runtime can execute its Docker CLI through an owned provider runtime. Process streams, signalling, detached jobs and bounded file reads use the same container implementation; binary transfers stage through the provider filesystem. Harbor supplies Compose templates, startup environment overrides and directory-transfer helpers. Prime and Modal adapters handle VM provisioning, Docker startup and confirmed termination.

Compose retains authored service networking. Prime requires vm=True; Modal uses its VM runtime and publishes main's service port through its encrypted tunnel, including shared network namespaces. GPU tasks are unsupported, and Modal Compose requires public networking.

Artifacts and collect hooks can target individual services. For separate grading, main is stopped after harness cleanup before collecting sidecar evidence. Collection enforces one configurable byte budget across services and rejects overlapping restore paths. The solver project or VM is removed before the fresh verifier starts.

Note

Run Harbor Compose tasks on Prime and Modal VMs

  • Adds HarborComposeRuntime support for hosting a Docker Compose project inside a Prime or Modal VM. The VM runtime becomes the host; ContainerRuntime delegates command execution, file I/O, and process management to that host, and binary data is staged through files on the provider filesystem.
  • Provisions Docker tooling inside the VM at start: PrimeComposeVM installs Docker and launches dockerd; ModalComposeVM uses a VM-backed sandbox with Docker-in-Docker on a private Unix socket.
  • Extends RuntimeProcess with a non-blocking poll, adds Runtime.service and Runtime.stop_service defaults, and adds a Task.cleanup hook invoked by Rollout.close before runtime teardown.
  • Allows artifacts and collect hooks to target a named Compose service (CollectHook.service, Artifact.service), collects main evidence in finalize and sidecar evidence in cleanup, and enforces a shared artifact_max_bytes budget across services.
  • Behavioral Change: PrimeComposeVM.expose now raises SandboxError (Prime SDK does not support port exposure); GPU Compose is rejected universally; restricted networking is rejected for local Docker only; collect rejects duplicate or cross-service overlapping destinations; VM teardown now blocks up to 60 s waiting for confirmed sandbox termination; DockerRuntime.run_background no longer dereferences an absent proxy under restricted networking.

Macroscope summarized c1ce953.


Note

Medium Risk
Changes rollout teardown order, remote VM Docker bootstrap, and grading artifact collection; Prime Compose explicitly cannot expose ports, which may break tool servers that rely on expose on that path.

Overview
Harbor Docker Compose tasks can run on local Docker, Prime VMs (vm=true), or Modal’s experimental VM runtime (network_access=true), not only unrestricted local Docker. One Compose project still drives all services; remote runs stage the task environment into the VM, bootstrap Docker there, and route docker compose / exec through a shared host-backed ContainerRuntime.

The container stack gains Runtime.service / stop_service, per-service views in HarborComposeRuntime, and poll() on live processes. Host delegation fixes remote CLI I/O (staged binary read/write, longer process startup deadlines).

Sidecar artifacts and collect hooks are supported: each entry’s service selects the source; separate grading collects main in finalize, then task.cleanup (new rollout hook) stops main and collects sidecars. artifact_max_bytes caps total collected bytes; overlapping paths across services are rejected.

Docs drop the “sidecar unsupported” gap and describe Prime/Modal Compose constraints (no GPU Compose, Prime can’t publish VM ports externally, Modal tunnels main’s service port).

Reviewed by Cursor Bugbot for commit c1ce953. 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-06T18:12:36.177253Z 5d98ce7 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/cloud.py Outdated
Comment on lines +306 to +311
while (
services.get(owner, {})
.get("network_mode", "")
.startswith("service:")
):
owner = services[owner]["network_mode"].split(":", 1)[1]

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/cloud.py:306

A cyclic network_mode: service: chain makes this loop spin forever before docker compose config runs, blocking the event loop and preventing the sandbox from being torn down. Track visited services and raise a SandboxError when a service repeats so invalid topology is rejected without hanging.

-                while (
-                    services.get(owner, {})
-                    .get("network_mode", "")
-                    .startswith("service:")
-                ):
-                    owner = services[owner]["network_mode"].split(":", 1)[1]
+                seen: set[str] = set()
+                while (
+                    services.get(owner, {})
+                    .get("network_mode", "")
+                    .startswith("service:")
+                ):
+                    if owner in seen:
+                        raise SandboxError("Cyclic Compose network_mode service chain")
+                    seen.add(owner)
+                    owner = services[owner]["network_mode"].split(":", 1)[1]
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/tasksets/harbor/cloud.py around lines 306-311:

A cyclic `network_mode: service:` chain makes this loop spin forever before `docker compose config` runs, blocking the event loop and preventing the sandbox from being torn down. Track visited services and raise a `SandboxError` when a service repeats so invalid topology is rejected without hanging.

Comment thread verifiers/v1/tasksets/harbor/cloud.py Outdated
self.owner.ops.env,
)
try:
async with asyncio.timeout(30):

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.

🟡 Medium harbor/cloud.py:130

When docker compose exec exits before the wrapper writes pidfile, open_process ignores that failure and keeps polling cat for up to 30 seconds instead of surfacing the process's exit status promptly. Monitor the outer RuntimeProcess while waiting for the PID and raise its failure as soon as it exits.

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

When `docker compose exec` exits before the wrapper writes `pidfile`, `open_process` ignores that failure and keeps polling `cat` for up to 30 seconds instead of surfacing the process's exit status promptly. Monitor the outer `RuntimeProcess` while waiting for the PID and raise its failure as soon as it exits.

@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: d1d815d794

ℹ️ 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 642 to +643
source=entry.source,
service=effective_artifact_service(entry),

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 Resolve sidecar paths from their authored workdir

When a local Docker Compose task declares a relative artifact for a non-main service whose working_dir is not /, accepting the service here collects the wrong absolute path. HarborComposeRuntime.service() hardcodes every sidecar's workdir to /, and DockerRuntime.run() passes that value via docker exec --workdir, so relative artifacts such as output.json are looked up as /output.json instead of, for example, /app/output.json (and sidecar collect hooks similarly run from /). Inspect each sidecar's configured/container working directory as the remote adapters do before enabling these declarations.

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/prime.py
Comment thread verifiers/v1/tasksets/harbor/cloud.py Outdated
@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 Prime and Modal VM-backed Harbor Compose execution and substantially changes shared runtime, rollout lifecycle, networking, teardown, and multi-service artifact paths. The new infrastructure scope and unresolved concerns around remote bootstrap, sandbox cleanup, and sidecar artifact handling warrant human review.

Not approved because:

  • 6 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 force-pushed the harbor-compose-cloud branch from d1d815d to 5d98ce7 Compare September 6, 2026 18:08
if self._owner is not None:
raise SandboxError("Only the main Compose service publishes a runtime port")
url = (
await self._host.expose(port)

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:223

Prime-hosted Compose runs fail whenever runtime.expose() is needed: this delegates to PrimeComposeVM.expose(), which unconditionally raises SandboxError, so SERVICE_PORT can never be published for non-colocated tools. Route Prime Compose exposure through the Compose service's published port instead of the host's unsupported expose() implementation.

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

Prime-hosted Compose runs fail whenever `runtime.expose()` is needed: this delegates to `PrimeComposeVM.expose()`, which unconditionally raises `SandboxError`, so `SERVICE_PORT` can never be published for non-colocated tools. Route Prime Compose exposure through the Compose service's published port instead of the host's unsupported `expose()` implementation.

Comment thread verifiers/v1/runtimes/container.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: 5d98ce71ff

ℹ️ 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".

"-c",
(
"export DEBIAN_FRONTEND=noninteractive; apt-get update -qq && "
"apt-get install -y -qq --no-install-recommends docker.io docker-cli docker-compose iptables "

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 Install available Docker packages in the Prime VM

Every Prime Compose run boots the hard-coded python:3.11-slim Debian-family image and executes this install, but its standard APT repositories do not provide a package named docker-cli (the CLI is supplied by docker.io, while Compose v2 needs the appropriate plugin package). Because APT aborts the whole transaction when any requested package is unavailable, PrimeComposeVM.start() reports Docker bootstrap failed before starting dockerd, making the newly advertised Prime Compose runtime unusable.

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

Comment thread verifiers/v1/rollout.py
logger.warning(
"runtime teardown failed (rollout %s)", trace.id, exc_info=True
)
trace.is_completed = True

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.

🟡 Medium v1/rollout.py:553

While task.cleanup() runs, the rollout remains marked live even though every timing span is closed, so _stage() falls back to "boot" and the dashboard shows cleanup/sidecar collection as booting. Mark trace.is_completed = True before cleanup begins so live status reports completion instead.

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

While `task.cleanup()` runs, the rollout remains marked live even though every timing span is closed, so `_stage()` falls back to `"boot"` and the dashboard shows cleanup/sidecar collection as booting. Mark `trace.is_completed = True` before cleanup begins so live status reports completion instead.

if sandbox is None:
return
# Preserve the handle for the cleanup backstop until termination is confirmed.
async with asyncio.timeout(60):

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/modal.py:34

When sandbox.terminate.aio() or sandbox.wait.aio(...) exceeds the 60-second timeout, teardown raises while cleanup still depends on this runtime instance. Once Compose unwinds, the runtime can disappear from the WeakSet, leaving the paid Modal VM running until its 24-hour provider timeout. Retain the sandbox in a strong cleanup mechanism or complete termination independently of the runtime object's lifetime before propagating the exception.

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

When `sandbox.terminate.aio()` or `sandbox.wait.aio(...)` exceeds the 60-second timeout, `teardown` raises while cleanup still depends on this runtime instance. Once Compose unwinds, the runtime can disappear from the `WeakSet`, leaving the paid Modal VM running until its 24-hour provider timeout. Retain the sandbox in a strong cleanup mechanism or complete termination independently of the runtime object's lifetime before propagating the exception.

Comment thread verifiers/v1/utils/platform.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.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

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 044e2b0. Configure here.

Comment thread verifiers/v1/tasksets/harbor/taskset.py
Comment thread verifiers/v1/tasksets/harbor/taskset.py
Comment thread verifiers/v1/tasksets/harbor/runtime.py
Comment thread verifiers/v1/tasksets/harbor/prime.py
@xeophon
xeophon force-pushed the harbor-compose-cloud branch from 044e2b0 to c1ce953 Compare September 10, 2026 08:49
@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.

collected = await collect(
runtime,
self.data.artifacts,
max_bytes=self.data.artifact_max_bytes - used,

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/taskset.py:262

Retrying cleanup can fail after a sidecar artifact was collected successfully: finalize counts the existing archive in used, then recollects it with the reduced artifact_max_bytes budget, so an archive larger than half the budget exceeds the retry limit. Make artifact collection idempotent by skipping already-recorded artifacts (or otherwise excluding them from the retry budget).

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

Retrying `cleanup` can fail after a sidecar artifact was collected successfully: `finalize` counts the existing archive in `used`, then recollects it with the reduced `artifact_max_bytes` budget, so an archive larger than half the budget exceeds the retry limit. Make artifact collection idempotent by skipping already-recorded artifacts (or otherwise excluding them from the retry budget).

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.

1 participant