Automation Platform > Deployment & hosting
Running agents with an external orchestrator
# Running agents with an external orchestrator Use an existing CI system, Kubernetes controller, or internal scheduler to allocate compute while the Automation Platform routes and tracks each run. The worker supports two patterns for this setup: start a one-shot Direct worker for each job, or use a long-lived Command worker that delegates runs to your runtime. ## Choosing an orchestration pattern Both patterns keep execution on your infrastructure and require outbound connectivity to Warp. | Pattern | External orchestrator responsibility | Worker behavior | Use when | | --- | --- | --- | --- | | **One-shot Direct worker** | Starts a worker process and creates a run for its unique worker ID | Runs one task on the allocated host, then exits | Your scheduler allocates a VM, pod, or CI runner for each job | | **Command backend** | Exposes an API or command that can accept a task payload and start the agent | Stays connected and invokes your dispatch command for each task | Your runtime already has its own job API, queue, or compute lifecycle | One-shot mode works only with the Direct backend. It forces `max_concurrent_tasks` to `1` and waits for one accepted task to finish, fail, or be cancelled before the worker exits. If no task arrives, the worker stays connected until your orchestrator stops it. The Command backend is fire-and-forget. The dispatch command returns after the external runtime durably accepts the task. The remote agent, not the worker process, reports progress and completion to Warp. ## Running a one-shot Direct worker This example starts a worker and routes one cloud agent run to it from the same job. Use a unique worker ID so another worker cannot claim the run. ### Prerequisites * **Self-hosting enabled for your Enterprise team** - [Contact sales](https://www.warp.dev/contact-sales) if self-hosting is not enabled. * **The worker and CLI binaries** - Install `oz-agent-worker` from a [published release](https://github.com/warpdotdev/oz-agent-worker/releases) and install the Oz CLI by following the [CLI installation instructions](/reference/cli/#installing-the-cli). * **An agent API key** - Create one in the <a href={`https://oz.warp.dev/settings`}>Oz web app</a>. Store it in your orchestrator's secret manager as `WARP_API_KEY`. ### Start and route the run Add the following script to the job your orchestrator starts. Replace `CI_JOB_ID` with a unique job identifier from your system. ```bash title="run-agent.sh" #!/usr/bin/env bash set -euo pipefail : "${WARP_API_KEY:?Set WARP_API_KEY in the job environment}" : "${CI_JOB_ID:?Set CI_JOB_ID to a unique job identifier}" worker_id="external-${CI_JOB_ID}" oz-agent-worker \ --worker-id "$worker_id" \ --backend direct \ --one-shot & worker_pid=$! trap 'kill "$worker_pid" 2>/dev/null || true' EXIT oz agent run-cloud \ --host "$worker_id" \ --prompt "Run the test suite, fix failures, and open a pull request." wait "$worker_pid" trap - EXIT ``` The run may enter the queue before the worker finishes connecting. Warp assigns it after the matching worker ID is online. The job exits when the agent reaches a terminal state and the one-shot worker shuts down. Use Direct backend [setup and teardown commands](/platform/self-hosting/managed-direct/#setup-and-teardown-commands) to prepare the workspace on the allocated host. ## Delegating runs with the Command backend Use the Command backend when the external runtime owns job creation and cleanup. The worker invokes `dispatch_command` once for each assigned task and writes a versioned JSON payload to standard input. The payload includes: * `base_args` - The `oz agent run` argument vector for the external runtime. * `docker_image` and `sidecars` - The task image and required sidecar mounts. * `env` - Task environment variables and credentials. Keep this payload out of logs. * `run_id` and `server_root_url` - Values the agent uses to report status to Warp. The public [`command-backend` example](https://github.com/warpdotdev/oz-agent-worker/tree/main/examples/command-backend) includes dependency-free Python dispatch and cancellation scripts for an HTTP runtime. Copy those scripts to the worker host, then configure the worker: ```yaml title="worker.yaml" worker_id: "external-runtime" backend: command: dispatch_command: "python3 /opt/warp/dispatch.py" cancel_command: "python3 /opt/warp/cancel.py" dispatch_timeout: "60s" environment: - name: OZ_DISPATCH_URL value: "https://runtime.internal.example.com/agent-runs" - name: OZ_CANCEL_URL value: "https://runtime.internal.example.com/agent-runs/cancel" - name: OZ_DISPATCH_AUTH_HEADER ``` Start the worker with the authentication header and Warp API key supplied by your secret manager: ```bash export OZ_DISPATCH_AUTH_HEADER="Bearer YOUR_RUNTIME_TOKEN" export WARP_API_KEY="YOUR_AGENT_API_KEY" oz-agent-worker --config-file worker.yaml ``` Adapt the example script's `transform()` function to your runtime's request schema. Your runtime must launch `base_args` with the supplied task environment, image, and sidecars. After the CLI exits, it must run `oz harness-support --run-id RUN_ID report-shutdown` so Warp receives the terminal state. An exit code of `0` from `dispatch_command` means the external runtime accepted responsibility for the task. A nonzero exit or a dispatch timeout fails the task. `max_concurrent_tasks` limits simultaneous dispatch calls, not the number of agents running in the external runtime. ## Related pages * [Managed: Direct backend](/platform/self-hosting/managed-direct/) - Run agent tasks directly on a worker host. * [Unmanaged architecture](/platform/self-hosting/unmanaged/) - Invoke `oz agent run` directly when Warp does not need to route the run. * [Self-hosted worker reference](/platform/self-hosting/reference/) - Look up worker flags and backend configuration fields. * [Routing runs to self-hosted workers](/platform/self-hosting/#routing-runs-to-self-hosted-workers) - Route runs from the CLI, API, schedules, integrations, or the web app.Tell me about this feature: https://docs.warp.dev/platform/self-hosting/external-orchestrators/Connect an external job scheduler to self-hosted agents with a one-shot Direct worker or the Command backend.
Use an existing CI system, Kubernetes controller, or internal scheduler to allocate compute while the Automation Platform routes and tracks each run. The worker supports two patterns for this setup: start a one-shot Direct worker for each job, or use a long-lived Command worker that delegates runs to your runtime.
Choosing an orchestration pattern
Section titled “Choosing an orchestration pattern”Both patterns keep execution on your infrastructure and require outbound connectivity to Warp.
| Pattern | External orchestrator responsibility | Worker behavior | Use when |
|---|---|---|---|
| One-shot Direct worker | Starts a worker process and creates a run for its unique worker ID | Runs one task on the allocated host, then exits | Your scheduler allocates a VM, pod, or CI runner for each job |
| Command backend | Exposes an API or command that can accept a task payload and start the agent | Stays connected and invokes your dispatch command for each task | Your runtime already has its own job API, queue, or compute lifecycle |
One-shot mode works only with the Direct backend. It forces max_concurrent_tasks to 1 and waits for one accepted task to finish, fail, or be cancelled before the worker exits. If no task arrives, the worker stays connected until your orchestrator stops it.
The Command backend is fire-and-forget. The dispatch command returns after the external runtime durably accepts the task. The remote agent, not the worker process, reports progress and completion to Warp.
Running a one-shot Direct worker
Section titled “Running a one-shot Direct worker”This example starts a worker and routes one cloud agent run to it from the same job. Use a unique worker ID so another worker cannot claim the run.
Prerequisites
Section titled “Prerequisites”- Self-hosting enabled for your Enterprise team - Contact sales if self-hosting is not enabled.
- The worker and CLI binaries - Install
oz-agent-workerfrom a published release and install the Oz CLI by following the CLI installation instructions. - An agent API key - Create one in the Oz web app. Store it in your orchestrator’s secret manager as
WARP_API_KEY.
Start and route the run
Section titled “Start and route the run”Add the following script to the job your orchestrator starts. Replace CI_JOB_ID with a unique job identifier from your system.
#!/usr/bin/env bashset -euo pipefail
: "${WARP_API_KEY:?Set WARP_API_KEY in the job environment}": "${CI_JOB_ID:?Set CI_JOB_ID to a unique job identifier}"
worker_id="external-${CI_JOB_ID}"
oz-agent-worker \ --worker-id "$worker_id" \ --backend direct \ --one-shot &worker_pid=$!
trap 'kill "$worker_pid" 2>/dev/null || true' EXIT
oz agent run-cloud \ --host "$worker_id" \ --prompt "Run the test suite, fix failures, and open a pull request."
wait "$worker_pid"trap - EXITThe run may enter the queue before the worker finishes connecting. Warp assigns it after the matching worker ID is online. The job exits when the agent reaches a terminal state and the one-shot worker shuts down.
Use Direct backend setup and teardown commands to prepare the workspace on the allocated host.
Delegating runs with the Command backend
Section titled “Delegating runs with the Command backend”Use the Command backend when the external runtime owns job creation and cleanup. The worker invokes dispatch_command once for each assigned task and writes a versioned JSON payload to standard input.
The payload includes:
base_args- Theoz agent runargument vector for the external runtime.docker_imageandsidecars- The task image and required sidecar mounts.env- Task environment variables and credentials. Keep this payload out of logs.run_idandserver_root_url- Values the agent uses to report status to Warp.
The public command-backend example includes dependency-free Python dispatch and cancellation scripts for an HTTP runtime. Copy those scripts to the worker host, then configure the worker:
worker_id: "external-runtime"backend: command: dispatch_command: "python3 /opt/warp/dispatch.py" cancel_command: "python3 /opt/warp/cancel.py" dispatch_timeout: "60s" environment: - name: OZ_DISPATCH_URL value: "https://runtime.internal.example.com/agent-runs" - name: OZ_CANCEL_URL value: "https://runtime.internal.example.com/agent-runs/cancel" - name: OZ_DISPATCH_AUTH_HEADERStart the worker with the authentication header and Warp API key supplied by your secret manager:
export OZ_DISPATCH_AUTH_HEADER="Bearer YOUR_RUNTIME_TOKEN"export WARP_API_KEY="YOUR_AGENT_API_KEY"
oz-agent-worker --config-file worker.yamlAdapt the example script’s transform() function to your runtime’s request schema. Your runtime must launch base_args with the supplied task environment, image, and sidecars. After the CLI exits, it must run oz harness-support --run-id RUN_ID report-shutdown so Warp receives the terminal state.
An exit code of 0 from dispatch_command means the external runtime accepted responsibility for the task. A nonzero exit or a dispatch timeout fails the task. max_concurrent_tasks limits simultaneous dispatch calls, not the number of agents running in the external runtime.
Related pages
Section titled “Related pages”- Managed: Direct backend - Run agent tasks directly on a worker host.
- Unmanaged architecture - Invoke
oz agent rundirectly when Warp does not need to route the run. - Self-hosted worker reference - Look up worker flags and backend configuration fields.
- Routing runs to self-hosted workers - Route runs from the CLI, API, schedules, integrations, or the web app.