Skip to content

[rollout] feat: add native Harbor backend - #267

Closed
JoyboyBrian wants to merge 47 commits into
mainfrom
brian/harbor
Closed

[rollout] feat: add native Harbor backend#267
JoyboyBrian wants to merge 47 commits into
mainfrom
brian/harbor

Conversation

@JoyboyBrian

@JoyboyBrian JoyboyBrian commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What

  • Add NativeHarborBackend for running one native Harbor trial per rollout with local, package, or Git task resolution, bounded concurrency, controller endpoint injection, and Docker, Daytona, and SkyPilot support.
  • Persist Harbor-authored ATIF directly so reasoning, tool calls, observations, subagent references, and agent metadata do not pass through the lossy chat-message conversion path; recursively redact credential-bearing agent.extra leaves and overlay controller-reported metrics.
  • Copy only artifacts already collected by Harbor into the standard rollout artifact root before successful-trial cleanup, including per-step artifact trees.
  • Add native-backend submit preflight handling, lifecycle and retry safeguards, documentation, and focused unit coverage.

Why

Native Harbor backend development paused before the SDK gained the single-sample URL-routed protocol, ATIF persistence, file artifacts, output downloads, and newer Harbor runtime support. Replaying the old branch diff would miss those contracts and could either discard Harbor's richer native trajectory or delete collected artifacts during cleanup. This brings the backend forward against the current SDK architecture while keeping Harbor responsible for selecting and downloading sandbox artifacts.

Tracks OSM-1653.

How to Test

  • uv run ruff check .
  • uv run ruff format --check .
  • uv run pyright osmosis_ai/
  • uv run pytest
  • uv run pytest tests/unit/rollout/test_native_harbor_backend.py tests/unit/cli/test_native_preflight.py tests/unit/rollout/test_trajectory_save.py -q

Checklist

  • PR title follows [module] type: description format
  • Appropriate labels added (e.g. enhancement, bug, breaking)
  • ruff check . and ruff format --check . pass
  • pyright osmosis_ai/ passes
  • pytest passes (new tests added if applicable)
  • Public API changes are documented
  • No secrets or credentials included

Summary by cubic

Adds a NativeHarborBackend to run one native harbor trial per rollout with URL-identity routing, native ATIF persistence, a protocol gateway, and bounded server admission. Improves reliability and preflight, archives native diagnostics, and stabilizes Responses streaming. Tracks OSM-1653.

  • New Features

    • NativeHarborBackend with bounded concurrency via TrialQueue and server admission (max_queue_depth); Docker/Daytona/SkyPilot support; controller URL/key injected from rollout context (no per-call headers).
    • Resolve tasks from dataset metadata["harbor_task"] (local/package/git) via resolve_task, optional metadata["harbor_model"]; map the task verifier’s reward to the single sample; reject rewards from failed trials; disable native retries.
    • Persist harbor-authored ATIF (process-local), redact sensitive agent.extra, overlay controller metrics; copy only harbor-collected artifacts; keep trials if outputs can’t be safely saved; post and archive native diagnostics via result.extra_fields (sidecar when no sample).
    • Native protocol gateway on the rollout server (OpenAI/Anthropic/OpenCode chat + streaming + Responses) with URL-identity routing through the controller; stabilize Responses stream IDs and route OpenCode Responses through the controller.
    • Submit preflight verifies native wiring via a rollout-server backend marker, forwards constructor args (positional/keywords/negative indices), validates agent bindings, accepts full native configs, prewarms tasks, and warns on unpinned task refs.
  • Migration

    • Datasets must include metadata["harbor_task"]; system_prompt/user_prompt are ignored. Pin task refs for reproducibility.
    • No AgentWorkflow/Grader; the harbor task defines instruction, environment, and reward. The server captures the final result without a grader URL.
    • Default agent is terminus-2 with summarization disabled; override via agent_kwargs if needed.

Written for commit e0e5baf. Summary will update on new commits.

Review in cubic

@JoyboyBrian JoyboyBrian added enhancement New feature or request rollout Remote Rollout module labels Jul 29, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread osmosis_ai/eval/common/cli.py Outdated
Comment thread osmosis_ai/eval/common/cli.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread osmosis_ai/eval/common/cli.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 13 files

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread osmosis_ai/rollout/backend/native_harbor/backend.py
Comment thread osmosis_ai/eval/common/cli.py Outdated
Comment thread osmosis_ai/eval/common/cli.py Outdated
Comment thread osmosis_ai/eval/common/cli.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 13 files

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread osmosis_ai/rollout/backend/native_harbor/backend.py Outdated
Comment thread osmosis_ai/eval/common/cli.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

No issues found across 15 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

3 issues found across 18 files (changes from recent commits).

Confidence score: 3/5

  • In osmosis_ai/rollout/server/native_harbor_gateway.py, the SSE wrapper emits only data: frames, so clients routing on event names can miss response.created and delta events, breaking real-time parsing and downstream handlers — preserve and forward each upstream SSE event: field alongside payloads.
  • In osmosis_ai/rollout/backend/base.py, HTTP admission is still unbounded while LocalBackend has finite workers, which can let request queues grow until memory pressure rises or controller deadlines expire before execution — add an admission cap/backpressure path aligned with pool and queue limits.
  • In docs/native-harbor-backend.md, /health documents a capacity block that health() does not currently return, which can mislead operators and automation that depend on those fields — either implement the documented capacity snapshot or update the docs to match current behavior.

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="osmosis_ai/rollout/server/native_harbor_gateway.py">

<violation number="1" location="osmosis_ai/rollout/server/native_harbor_gateway.py:122">
P2: Responses streaming clients that dispatch on SSE event names will not receive `response.created`, `response.output_text.delta`, or other event types because this wrapper emits only `data:` frames. Preserve each event's `type` as an `event:` line when forwarding the stream.</violation>
</file>

<file name="osmosis_ai/rollout/backend/base.py">

<violation number="1" location="osmosis_ai/rollout/backend/base.py:33">
P2: Local rollouts remain unbounded at the HTTP admission layer even though `LocalBackend` has a finite execution pool, allowing enough pending requests to grow memory usage or expire controller deadlines while waiting. A finite backend such as `LocalBackend` should override `max_queue_depth` (or the default should derive a bound from `max_concurrency`) rather than inheriting this unbounded fallback.</violation>
</file>

<file name="docs/native-harbor-backend.md">

<violation number="1" location="docs/native-harbor-backend.md:449">
P2: The documented /health response includes a `capacity` block with live fields (`in_flight`, `queue_depth`, `available`, `accepting`) that the backend's `health()` method does not return. If this capacity snapshot is intended, it must be implemented; otherwise remove the block from the doc example and the surrounding claim to avoid misleading readers about available/accepting admission state.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

try:
if hasattr(response, "__aiter__"):
async for event in response:
yield f"data: {_response_event_json(event)}\n\n"

@cubic-dev-ai cubic-dev-ai Bot Jul 30, 2026

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.

P2: Responses streaming clients that dispatch on SSE event names will not receive response.created, response.output_text.delta, or other event types because this wrapper emits only data: frames. Preserve each event's type as an event: line when forwarding the stream.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At osmosis_ai/rollout/server/native_harbor_gateway.py, line 122:

<comment>Responses streaming clients that dispatch on SSE event names will not receive `response.created`, `response.output_text.delta`, or other event types because this wrapper emits only `data:` frames. Preserve each event's `type` as an `event:` line when forwarding the stream.</comment>

<file context>
@@ -0,0 +1,197 @@
+    try:
+        if hasattr(response, "__aiter__"):
+            async for event in response:
+                yield f"data: {_response_event_json(event)}\n\n"
+        else:
+            for event in response:
</file context>
Fix with cubic

execution pool can override this to make ``POST /rollout`` fail fast
instead of letting controller deadlines expire in an unbounded queue.
"""
return None

@cubic-dev-ai cubic-dev-ai Bot Jul 30, 2026

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.

P2: Local rollouts remain unbounded at the HTTP admission layer even though LocalBackend has a finite execution pool, allowing enough pending requests to grow memory usage or expire controller deadlines while waiting. A finite backend such as LocalBackend should override max_queue_depth (or the default should derive a bound from max_concurrency) rather than inheriting this unbounded fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At osmosis_ai/rollout/backend/base.py, line 33:

<comment>Local rollouts remain unbounded at the HTTP admission layer even though `LocalBackend` has a finite execution pool, allowing enough pending requests to grow memory usage or expire controller deadlines while waiting. A finite backend such as `LocalBackend` should override `max_queue_depth` (or the default should derive a bound from `max_concurrency`) rather than inheriting this unbounded fallback.</comment>

<file context>
@@ -22,5 +22,20 @@ def max_concurrency(self) -> int:
+        execution pool can override this to make ``POST /rollout`` fail fast
+        instead of letting controller deadlines expire in an unbounded queue.
+        """
+        return None
+
+    @property
</file context>
Fix with cubic

Comment thread osmosis_ai/rollout/backend/native_harbor/backend.py Outdated
}
```

Controllers currently ignore the additional health fields, so capacity and

@cubic-dev-ai cubic-dev-ai Bot Jul 30, 2026

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.

P2: The documented /health response includes a capacity block with live fields (in_flight, queue_depth, available, accepting) that the backend's health() method does not return. If this capacity snapshot is intended, it must be implemented; otherwise remove the block from the doc example and the surrounding claim to avoid misleading readers about available/accepting admission state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/native-harbor-backend.md, line 449:

<comment>The documented /health response includes a `capacity` block with live fields (`in_flight`, `queue_depth`, `available`, `accepting`) that the backend's `health()` method does not return. If this capacity snapshot is intended, it must be implemented; otherwise remove the block from the doc example and the surrounding claim to avoid misleading readers about available/accepting admission state.</comment>

<file context>
@@ -93,82 +185,282 @@ Each row points at a Harbor task through a first-class `metadata` key. The datas
+}
+```
+
+Controllers currently ignore the additional health fields, so capacity and
+protocol surfacing are forward-compatible rather than full negotiation. The
+real Miles/eval capacity-mismatch measurement remains an E2E dependency.
</file context>
Fix with cubic

Comment thread tests/unit/rollout/test_server_app_trajectory.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 4 files (changes from recent commits).

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread tests/unit/rollout/test_native_harbor_backend.py Outdated
Comment thread osmosis_ai/rollout/backend/native_harbor/backend.py Outdated
@JoyboyBrian

Copy link
Copy Markdown
Contributor Author

Superseded by #271, which contains the exact same final source tree but is rebuilt on the latest main with a clean, reviewable commit history. Closing this PR in favor of #271.

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

Labels

enhancement New feature or request rollout Remote Rollout module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants