Skip to main content
HarborBackend runs an Osmosis AgentWorkflow or a registered native Harbor agent inside a Harbor trial. Every rollout gets a copied task directory, a task-defined environment, and Harbor-managed agent and verifier phases.
SDK v0.3 removed the pre-v0.3 HarborBackend and renamed HarborBackendV2 to HarborBackend. The current class has a different constructor and no compatibility alias. Follow the v0.3 migration guide before upgrading an existing harness.

Install and Import

Install the Harbor and rollout-server features:
In v0.3.0 and later, the Harbor extra installs a compatible uv builder and the backend prefers the executable installed with the active Python interpreter. A separate global uv installation is not required. Import the backend from its Harbor submodule:
HarborBackend is not re-exported from osmosis_ai.rollout or osmosis_ai.rollout.backend. From 0.3.7, the harbor extra installs Harbor together with its Daytona and OpenSandbox environment dependencies, so neither provider requires additional packages.

Harbor Version Compatibility

From 0.3.8, the harbor extra supports Harbor >=0.20.0,<0.25. Upgrading Harbor alone does not change the SDK’s published harbor-v1 source-image identities or require rebuilding those images. When moving existing tasks or native agent configuration to Harbor 0.23:
  • Remove unsupported native_agent_kwargs; agents with an options schema reject unknown keys and invalid types.
  • Use portable step names that remain unique when compared case-insensitively, and repository-relative Git task paths that stay inside the repository.
  • Make every value in a native verifier’s reward.json a finite JSON number, such as {"reward": 1}; quoted numbers and non-numeric fields fail parsing. The SDK grader already writes numeric rewards.
  • Allow Mini-SWE setup to download its managed Python 3.12 toolchain.
Tools that read raw native trajectories must support the installed Harbor version’s schema. For example, Terminus in Harbor 0.23 writes ATIF v1.8; the SDK retains this native evidence separately from its canonical trajectory.

Choose a Task Mode

task_mode determines who owns the instruction, how the default task is selected, and whether the request prompt reaches the task. Both modes use Harbor trials and provide the same isolation. Mode selection changes task semantics, not the sandbox technology.

Template Mode

Template mode starts every rollout from one task directory. When the request contains a prompt, the backend copies the task and replaces instruction.md with that prompt before Harbor starts the trial. An AgentWorkflow reads the original messages from ctx.prompt; How Native Agents Receive the Prompt describes what a native agent reads.
An instruction.md file is optional for the configured template when every rollout supplies a prompt. Add a placeholder instruction if you also want the directory to pass Harbor’s standalone task validation. A tests/test.sh is optional when the backend supplies an Osmosis Grader.
The examples on this page run trials in OpenSandbox, which starts each trial from the prebuilt image set as docker_image in task.toml. See Managed and Self-Hosted Environments for that requirement and for Daytona. Template mode is the Harbor path most similar to LocalBackend: one workflow handles changing dataset prompts. Choose it when those prompts need the same isolated operating system, tools, services, or filesystem setup on every rollout.
In template mode, metadata["harbor_task"] can select a different task for one request, but the request prompt still replaces that task’s instruction. The backend logs a warning when it overwrites an instruction from a dynamically selected task.

How Native Agents Receive the Prompt

A native Harbor agent receives one task string rather than a message list. The agent places that string in its own prompt as the user’s task and keeps its own system prompt: Terminus 2 inserts it under Task Description:, mini-swe-agent passes it as --task, and OpenCode sends it as the opencode run message. When every message has the system or user role, string content, and no other fields, the backend writes the message contents to instruction.md in order, separated by a blank line and without role labels. A dataset system_prompt therefore becomes the opening of the task text, not the agent’s system prompt. Prompts with other roles, non-string content such as images, or extra message fields keep the JSON form so no structured information is lost. This fallback is not conversation replay or native multimodal input. Dataset mode always keeps the task’s authored instruction. For example, this dataset row:
becomes this instruction.md:
When rows run on native agents, write system_prompt as task directions rather than an assistant persona. The agent already has its own persona, so a line such as “You are a helpful multiplier” reads as a second identity inside the task. Label or separate worked examples from the real input, because the user content follows the system content directly.

Dataset Mode

Dataset mode treats each Harbor task as the complete unit of work. The task retains its own instruction.md, environment, configuration, and verifier.
Configure the root directory and a native Harbor agent or Osmosis workflow:
From 0.3.8, you can omit the task IDs in prewarm_lifespan() to discover tasks under tasks_dir automatically. Pass IDs explicitly to prewarm a subset or to support earlier SDK versions. Each Osmosis metadata-mode dataset row selects the Harbor task by directory name:
The backend deliberately removes any request prompt in dataset mode. Harbor passes the selected task’s instruction to the native agent or bundled AgentWorkflow. The task’s verifier normally computes the reward; if tests/test.sh is absent, a configured Osmosis Grader supplies the verifier instead.

Integrate an Existing Harbor Dataset

If a local dataset already works with harbor run, its task directories are the reusable boundary:
1

Keep the task directories

Point tasks_dir at the local dataset root. Each child must be a Harbor task with task.toml, instruction.md, an environment/, and the verifier files required by that task.
2

Choose a supported agent track

Use agent="terminus-2", "mini-swe-agent", "opencode", "pi", or "oracle" when that registered native agent fits. Otherwise package your agent behavior as an Osmosis AgentWorkflow and pass its class or import path.
3

Create the selector dataset

Upload an Osmosis metadata-mode dataset whose harbor_task_id values match the task directory names. The Platform dataset selects trials; the Harbor task directories remain with the rollout code.
4

Run evaluation before training

Confirm that the native verifier emits the reward channel and that trainable agents produce a usable trajectory before starting training.
This path reuses task content, not the complete Harbor job definition. HarborBackend does not read Harbor JobConfig, dataset filters, attempt counts, arbitrary agent configs, or harbor run CLI flags. Osmosis supplies request scheduling, the model endpoint, result polling, and rollout identity.

Dynamic Task Sources

In either mode, a request can set metadata["harbor_task"] instead of using the configured default selection: Pin package references and Git commits for reproducibility. Instruction ownership still follows task_mode: template mode replaces the fetched task’s instruction, while dataset mode keeps it.
Treat these fields as trusted control-plane input, not as untrusted dataset content. The rollout server resolves, downloads, and stages the referenced task outside the trial sandbox, and a task’s Dockerfile and verifier scripts are executable content. Accept harbor_task, git_url, and git_commit_id values only from task sources you control.

Agent Tracks

agent selects one of two execution tracks: An AgentWorkflow project must contain pyproject.toml and an importable Python package, and the task image must support Python. Pass code_dir when the project cannot be inferred, or pass a prebuilt bundle wheel. Keep the task image (its docker_image or Dockerfile) focused on task dependencies; the backend installs the rollout bundle and can preinstall its declared dependencies into the copied image. Registered native names: Use workflow_config with an AgentWorkflow. Use native_agent_kwargs only with a registered native agent. model_name defaults to openai/osmosis-rollout; per-request metadata can override it with harbor_model. For agent="mini-swe-agent", pass native_agent_kwargs={"reasoning_effort": "high"} to set reasoning effort while retaining the rollout Chat Completions endpoint. The top-level effort takes precedence over the effort in an inline config or YAML config_file; other settings are preserved. config and config_file are mutually exclusive.
A custom agent name that works in a separate Harbor installation is not automatically available through HarborBackend. The backend accepts only the registered native names above or an Osmosis AgentWorkflow.

Native OpenCode

Starting with 0.3.4, use agent="opencode" to run existing Harbor tasks. The SDK connects OpenCode to the active rollout model endpoint and disables automatic compaction and pruning to preserve the training trajectory.
Pass this backend to create_rollout_server() as in the dataset-mode example above; managed runs receive their sandbox credentials from the Platform, as described below. To pin OpenCode, pass native_agent_kwargs={"version": "1.18.27"}; the pin applies during execution and prewarm. Pass additional OpenCode configuration through native_agent_kwargs["opencode_config"]; the SDK controls endpoint, authentication, and compaction settings. From 0.3.8, OpenCode reuses a working runtime preinstalled under /opt/osmosis-agent/bin when stdbuf is available. If you pin a version, the installed version must match. Otherwise setup falls back to installing OpenCode. This applies to execution and prewarm. model_name must use the non-empty provider/model form; the default openai/osmosis-rollout already satisfies this. Override it per request with metadata["harbor_model"]; null, empty, and non-string values are rejected.

Native Pi

Use agent="pi" to run existing Harbor tasks with the Pi coding agent. The SDK connects Pi to the active rollout chat-completions endpoint and disables Pi’s automatic context compaction so the training trajectory stays append-only.
The SDK also installs Pi’s subagent extension with two subagent definitions, general and worker. Pi can delegate work by calling its subagent tool, for example {"agent": "general", "task": "<child prompt>"} for one child or {"tasks": [...]} for parallel children. Each subagent inherits the rollout model, so child Pi processes call the same endpoint as the parent. The SDK converts Pi’s session log into the rollout trajectory, including token usage reported by subagent runs. As with OpenCode, model_name must use the non-empty provider/model form.

Reward Source and Precedence

Harbor always treats a task-provided verifier as authoritative: There is no custom_tests_dir in v0.3. Keep native verifier files under each task’s tests/ directory. A native verifier must write Harbor’s reward channel, normally through /logs/verifier/reward.txt or reward.json with a numeric reward key (Harbor 0.23 requires every value in that file to be a finite number). The backend does not guess another channel.

Constructor Reference

Prewarming, Capacity, and Cancellation

prewarm_lifespan() runs prewarm before the server accepts traffic. You can also call await backend.prewarm() directly. Dockerfile builds depend on the selected environment; OpenSandbox uses prebuilt images. Template mode prewarms its configured task. From 0.3.8, dataset mode discovers tasks under tasks_dir when task_ids is omitted; pass IDs to select a subset. Choose a mode with the constructor’s prewarm argument, or override it for one call with prewarm(..., prewarm="full") or prewarm_lifespan(..., prewarm="full"): Progress logs identify each completed task, elapsed time, and any failure phase. Isolated task failures let the server start with those tasks unavailable; rollouts selecting them fail with the recorded reason. Widespread failures or repeated authentication, proxy, or quota errors abort startup. Inspect backend.prewarm_summary and backend.failed_prewarm_tasks for the outcome. For OpenSandbox, a positive OPENSANDBOX_MAX_SANDBOXES limits prewarm concurrency to half that value, rounded down with a minimum of one. The Platform can supply this limit in managed runs. Prewarm uses the Harbor queue, separately from the rollout trial limit. With no explicit queue, its capacity is at least 8 even when the rollout limit is lower.

Trial Limit

The trial limit is the number of Harbor trials one rollout server runs at once. Starting with SDK 0.3.11, orchestrator is optional and HarborBackend resolves the limit as follows: Omit orchestrator so Platform runs use their limit. If you pass a TrialQueue with a lower n_concurrent, the rollout server runs fewer trials than the caller sends. The server logs a startup warning when that happens. A limit below 1 stops the server from starting. At startup, the rollout server logs one line with the trial limit and its source, for example Trial limit: 32 at once (set by the platform for this run). The server’s /health response reports the limit as concurrency.max_concurrent. Local osmosis eval run caps in-flight rollouts at the smaller of that value and max_queue_depth.

Queue Depth and Cancellation

max_queue_depth limits waiting rollouts so the server can return HTTP 429 instead of growing an unbounded queue. rollout_status() reports queued, running, grading, or recently finished state; cancel_rollouts() cancels queued or running work by ID, prefix, or all. rollout_status() remains available for backend-local diagnostics, but server result polling reads progress from the active RolloutContext. Custom backends must await RolloutContext.set_status() to publish status; callers can use RolloutClient to observe progress and cancel one rollout. If you pass a TrialQueue, keep its default RetryConfig(max_retries=0). Queue-level attempt retries are not supported by the backend’s terminal-event contract; resubmit with a new rollout ID instead.

Server Shutdown

From 0.3.6, stopping a rollout server with SIGTERM invokes backend cleanup automatically:
  1. The server closes rollout admissions and allows active work 10 seconds to finish.
  2. It requests cancellation and allows up to 120 seconds for cleanup, then directly cancels remaining rollout tasks and waits up to 10 more seconds.
  3. HarborBackend.shutdown() cancels remaining trials and waits up to 180 additional seconds for cleanup, including artifact archiving.
Harbor tears down sandboxes during trial cancellation according to the environment’s delete setting. If cleanup times out, the backend reports the number of unfinished rollouts to stdout. This is not a count of remaining sandboxes or confirmation that deletion succeeded. Budget up to 320 seconds for these SDK cleanup stages, plus time for Uvicorn to finish in-flight HTTP requests before app shutdown and for process shutdown overhead. Set the host’s termination grace period to cover that total; 320 seconds is not a bound on the entire shutdown. Forced termination can interrupt cleanup; Daytona’s auto-stop policy remains a separate backstop when enabled.

Managed and Self-Hosted Environments

Choose the Harbor environment explicitly with environment_config. The examples above use OpenSandbox, which starts each trial from a prebuilt image as described below. Select Daytona for tasks that Harbor should build from a Dockerfile. Omitting environment_config keeps Harbor’s Docker default and does not select a managed sandbox automatically. Managed evaluation, training, and dev server runs receive Platform-managed OpenSandbox and Daytona credentials, so a rollout that selects either environment needs no sandbox secret. The managed OpenSandbox credential is issued per run: it reaches only the sandboxes that run creates and stops working when the run ends. Harbor builds the selected task’s Dockerfile for Daytona; you do not manually build or push an image, configure registry credentials, or select a cluster. To use your own Daytona account for managed runs, add DAYTONA_API_KEY to [secrets].required in both the evaluation and training configs, keeping any other required names. Store the credential with:
For local osmosis eval run, supply DAYTONA_API_KEY through the process environment or --secrets-file; a Platform secret record alone does not make it available locally. Never put secret values in the TOML config. osmosis eval run accepts every Harbor environment for local eval and keeps the entrypoint’s usual environment_config. OpenSandbox, Daytona, and Docker outside macOS cannot reach your machine’s loopback interface. For these environments, the CLI automatically starts a managed cloudflared tunnel so the sandbox can reach the local model bridge. To use your own tunnel instead, pass it with --advertise-url. See eval run. EnvironmentType.DOCKER also remains useful in a self-hosted SDK harness with a Docker daemon, but the managed rollout server does not provide that daemon. A self-hosted harness can select EnvironmentType.DAYTONA as well; the harbor extra already carries its dependencies, so only Daytona credentials are additionally required.

OpenSandbox Routing and Prebuilt Images

Select OpenSandbox with:
For built-in OpenSandbox environments (import_path=None), SDK 0.3.7 applies these defaults: SDK 0.3.9 sets request_timeout_sec from OPENSANDBOX_REQUEST_TIMEOUT_SEC when that value is a positive whole number of seconds, and otherwise keeps Harbor’s default. Explicit kwargs take precedence, and the SDK leaves the config object you pass in unchanged. Custom import_path environments receive no routing defaults. Platform runs configured with managed OpenSandbox receive their sandbox credentials automatically; do not add those managed credentials to [secrets].required. For local evaluation or a self-hosted server, export your OpenSandbox server’s OPENSANDBOX_DOMAIN and OPENSANDBOX_API_KEY in the process environment. The CLI’s ambient-credential redaction of logs.txt does not cover OPENSANDBOX_API_KEY, so review logs before uploading a local run. OpenSandbox requires a prebuilt docker_image under [environment] in each task’s task.toml. It does not build environment/Dockerfile or run Docker Compose tasks. For example, a Python-only task can start from:
Use an image containing your task’s dependencies and pin it by digest for reproducibility. SDK Dockerfile patching does not change the prebuilt image. From 0.3.7, tasks without a Dockerfile skip patching automatically; explicitly setting patch_dockerfile_with_sdk=True logs one warning per task instead of failing. Without a preinstalled SDK environment, a bundled workflow or Osmosis grader installs the wheel and its dependencies into the image’s system Python. The image needs Python 3.12 or later and working uv or python3 -m pip. To preinstall dependencies, build an image with a static uv at /opt/osmosis/uv, a Python 3.12 virtual environment at /opt/osmosis/venv, and all bundle requirements installed there. When that virtual environment exists, trials use /opt/osmosis/uv to install only the bundle wheel with --no-deps. Both the binary and preinstalled dependencies are required. Build managed-run images for linux/amd64.

Daytona Sandbox Lifecycle Defaults

In 0.3.3, built-in Daytona environments (type="daytona", import_path=None, and delete=True) default to auto_stop_interval_mins=60 and auto_delete_interval_mins=0. Daytona stops a sandbox after 60 minutes of provider-observed inactivity and deletes it immediately after stopping. This is a cleanup backstop for a crashed rollout server; normal trial teardown and agent or grader timeouts still apply. Activity means interactions recognized by Daytona, not CPU work inside the sandbox. Harbor command polling normally refreshes activity while the server drives a command. The policy does not check server health, and other clients’ activity can keep a sandbox alive after a crash. For trials with longer periods without Daytona-visible activity, pass a larger interval in environment_config.kwargs, or set it to 0 to disable auto-stop:
Pass this config to HarborBackend(environment_config=environment_config, ...). The SDK preserves explicit lifecycle values and adds no defaults for delete=False, a custom import_path, or other providers. Disabling auto-stop leaves cleanup to normal teardown or your own cleanup policy.
Harbor 0.22 and 0.23 force immediate deletion for snapshot-backed Daytona sandboxes even with a nonzero deletion interval, and reject nonzero deletion intervals for GPU tasks. The default of 0 works with both paths.

Next Steps

Execution Backend Overview

Compare LocalBackend, Harbor template mode, and Harbor dataset mode.

v0.3 Migration

Replace the legacy Harbor constructor and execution model.
Last modified on November 6, 2026