Skip to content

Set up a distributed drain

A distributed drain spreads one backlog across machines. The owner keeps the task store and decides what runs. A replica pulls one task at a time over SSH, runs it locally, and hands back a pull request. The owner lands it.

  • One owner per workspace. Sharing Git history does not share Orbit state. If two machines already own the same repository, collapse them to one first; the runbook shows how.
  • Matching machines. Every replica runs the same Orbit version and distributed-drain protocol revision and fingerprint as the owner. A leaf runs the owner’s captured workflow.required_validation_commands; a difference from the replica’s list warns when the drain starts. If the owner turns on before-PR review (review.timing = "before_pr"), every replica must be able to run the owner’s pre-landing reviewer (workflow.purpose_crews.pre_landing_review, or workflow.purpose_crews.review while that is unset): each pulled task is then reviewed, and the reviewer’s fixes committed, on the replica before its pull request opens. Crews may differ: a replica only receives tasks whose crew it can run. Operating systems may differ too: a task tagged os:macos (or os:linux, os:windows) only goes to a machine running that OS, and waits in the owner’s backlog until one asks for work. The crew’s executor can also restrict its OS. A task tagged ui additionally needs a verified browser kit on the executing host (orbit browser-kit status). A task’s requires_labels go only to a machine whose machine.labels, plus its OS, include every one; the replica declares its labels with each pull, so the owner knows them once it has pulled. Owner and replicas must run releases that agree on this field: an older peer is refused as protocol skew.
  • SSH is the access control. Anyone who can ssh to the owner owns it. To shut a replica out, remove its key from the owner’s authorized_keys.
  • Landing stays on the owner. Approve each handoff on the owner’s dashboard, or set workflow.distributed_completion = "done" on the owner to land every validated handoff, the way --complete lands its own tasks.

The owner can also require review after the replica opens its PR, with review.timing = "before_landing", or only for selected replica machine IDs with review.before_landing_hosts. The replica runs the captured reviewer and hands back evidence for the reviewed head; it never merges.

Admission uses a live executor census. Within a dispatch band, work only the requesting host can run leads ahead of priority; among the remaining work, scarcer tasks lead within a priority. workflow.admission_affinity defaults to "band"; choose "priority" to keep exclusive work within its priority, or "off" for the plain order. This also applies to the owner’s local drain.

On the second machine, run orbit init and pick a task prefix different from the owner’s, then register the checkout as a replica:

Terminal window
orbit init
cd <repo>
orbit workspace init --role replica --owner <owner-machine-id>

orbit config get machine.id on the owner prints its machine ID. Then register the owner on this machine, as in Connect Your Agent:

Terminal window
orbit host add <owner-ssh-target>
# Confirm that the owner is reachable and reports its version and protocol.
orbit host list

Check that the two machines match. On each one:

Terminal window
orbit --version
# The owner's value decides. With review.timing at before_pr,
# the replica must be able to run the reviewer crew.
orbit config get review.timing
orbit config get workflow.purpose_crews.pre_landing_review
orbit config get workflow.purpose_crews.review
orbit doctor

Then probe the owner. On the owner, run this read-only check; it reports the first reason a real pull would be refused. Read protocol_schema from the installed binary instead of copying a revision from this page. Use that value for caller_schema only after confirming the replica runs the matching build; it is the distributed-drain revision, not the MCP protocol revision. Replace <replica-version> with the replica’s orbit --version value and set caller_before_pr to true when its review.timing is before_pr, else false:

Terminal window
DRAIN_SCHEMA=$(
ORBIT_OPERATOR=1 orbit tool run orbit.drain.probe --input '{}' |
node -pe 'JSON.parse(require("fs").readFileSync(0))
.protocol_schema'
)
ORBIT_OPERATOR=1 orbit tool run orbit.drain.probe --input "{
\"caller_version\": \"<replica-version>\",
\"caller_schema\": $DRAIN_SCHEMA,
\"caller_before_pr\": false
}"

On the replica:

Terminal window
orbit run auto --pull <owner-machine>/<ws_id> --for 8h --concurrency 3

The selector is the owner workspace’s selector from federated discovery. orbit run auto --pull <workspace> --host <owner> resolves the same selector from the owner’s live workspace list. Each task the replica claims runs as a local run that implements, validates, pushes, and opens a pull request, then hands it to the owner, which moves the task to review.

When the drain starts, it checks which crews this machine can run: enabled, with the provider CLI installed. Tasks on other crews stay in the owner’s backlog for another machine. orbit run show <drain-run> lists the runnable crews and why the others were left out. An authentication failure sends its task back to backlog and excludes that provider account’s crews. A crew with its own env has its own account, so other accounts remain usable. Delayed authentication probes can re-admit the affected account within the same drain.

orbit run auto --stop on the replica stops new claims. Running tasks still finish and hand off, and it delivers any result still waiting to reach the owner. It is safe to repeat, and orbit doctor on the replica warns when results are waiting.

orbit run cancel <drain-run> --confirm cancels a running pull drain gracefully. It stops new claims immediately and returns claims it has not launched to the owner’s backlog. Launched leaves keep running until they finish and their outcomes reach the owner; the drain reports cancelling during that wait, then ends cancelled. The command returns immediately; follow the wait with orbit run show <drain-run>.

Add --force to stop the drain and its running leaves without waiting for them to finish. Their claims return to the owner’s backlog with the reason. A leaf whose stop cannot be confirmed keeps its claim on the owner, is reported, and makes the command exit 1. Only that drain’s leaves are affected.

Cancelling a task leaf directly returns its task to the owner’s backlog and keeps its candidate available to resume. Add --block to keep that task blocked for manual recovery instead.

A task that fails on a replica goes to blocked on the owner, with a summary naming the replica’s run. orbit run show <run> on the replica has the full detail. After several failures in a row the drain stops asking for work. Fix the cause, move the blocked tasks back to backlog, and start a new drain. Or ask your agent on the replica what went wrong: the orbit-orchestrate skill reads the run’s evidence and names the cause.

Nothing reclaims a stuck claim on its own. On the owner’s dashboard, open the task and use Recover claim → blocked or Recover claim → backlog; both ask for a reason.

  • Not a fleet. A replica works only while someone runs orbit run auto --pull on it; a clean probe starts nothing.
  • Not auto-recovery. A dead replica holds its claims until you recover them on the owner.
  • Not follower merge. Replicas never merge; landing is the owner’s.

The distributed drain runbook covers protocol details, receipts, and every refusal. For a drain on one machine, see Run a Delivery Window.