Skip to main content
You need a GitHub repository, the Orbi CLI and scheduler, an authenticated model provider, and a local user session to run the delivery loop. This page takes you from a fresh clone to a fully verified delivery: prerequisites, the model provider, the one-time setup, and a minimal smoke that proves the whole loop — a GitHub Issue picked up, implemented, tested, opened as a PR, independently reviewed, and merged. Every command works at any clone path — nothing here depends on a specific machine layout.

One-line installation

New users only need to run the installer; it installs the CLI, creates the config, and starts the interactive setup:
For a reviewable alternative to curl | bash, download and inspect first:
The installer requires git, gh, and a scheduler user session (systemd on Linux, launchd on macOS); uv provisions Python 3.14 when needed. It also installs the Pi CLI for you (via npm install -g --ignore-scripts @earendil-works/pi-coding-agent) — Node ≥ 22.19, which provides npm, is the one prerequisite it does not install: install Node yourself from the official download page and the installer verifies the version. For manual installations, orbi setup creates orbi.toml when absent — from the adjacent .orbi.example.toml when one exists, otherwise from the example shipped inside the CLI package — without overwriting it.

PyPI installation

The CLI is published on PyPI as orbi-cli — sdist + wheel, requiring Python ≥ 3.14. The distribution name is orbi-cli; the installed command stays orbi. uv tool install orbi-cli installs it into an isolated tool environment (uv supplies a compatible Python 3.14 interpreter when the system one is older); inside an activated Python ≥ 3.14 environment, pip install orbi-cli is the plain pip path:
The ≥ 3.14 floor applies to the interpreter that runs the install: an explicit --python 3.13 on the uv tool install call fails with the uv resolver message orbi-cli==<version> depends on Python>=3.14; pip install orbi-cli inside an activated older environment reports No matching distribution found for orbi-cli. The repair in both cases is the same: select a compatible interpreter — point uv tool install at --python 3.14 (uv provisions it) or run pip inside an environment built on Python ≥ 3.14. Uninstall the tool install with uv tool uninstall orbi-cli (the full removal story lives in Operations); these installs give you the orbi command only. To run Orbi itself, use the one-line installer above or the editable checkout install in step 2 — Orbi’s deployment drives an editable install so the CLI follows the synced checkout.

Prerequisites

You install these yourself — orbi setup only checks them (it verifies git, gh, uv and the scheduler user session on the machine — systemd on Linux, launchd on macOS; uv provisions the required Python interpreter). It never installs system packages, never installs gh, and never runs gh auth login (you log in first, setup only verifies the login). orbi check is the read-only prerequisite gate over the same list plus the Python version, the pi CLI, the config, per-repo access, the git transport and the model provider — it fails fast with the reason, the repair action and the official docs link. Install uv with the official installer (verified against the uv docs):
Install gh from the official GitHub CLI repository — the minimum tested version is 2.94.0: the Runner’s queue scans use gh api --paginate --slurp (added in gh 2.48.0) and the native blockedBy dependency field (added in gh 2.94.0). A too-old package passes orbi setup and then fails every tick with unknown flag: --slurp / Unknown JSON field: "blockedBy" and no Issue is ever claimed — Ubuntu 24.04’s distribution package (2.45.0) is such a case; Fedora (2.97.0) and Arch (2.100.0) currently satisfy the minimum natively. On Ubuntu/Debian, install or repair from the official apt repository (the official Linux install instructions):
Verify with gh --version. Pi’s npm package declares a Node ≥ 22.19.0 floor in its engines, but npm does not enforce it: on an older Node the npm install above still reports success, and then every pi invocation crashes with a bundle-level SyntaxError — the hardest-to-diagnose failure on the install path. Debian trixie and Ubuntu 24.04 ship distro-default Node versions older than that floor. The repair is to install Node ≥ 22.19 and reinstall Pi with the command above; orbi check executes pi --version (a PATH hit alone is not enough) and names this floor in its fix= when the probe fails. Pi must be configured with a provider that can serve a coding agent stably (system prompt + tool schemas + long sessions). Verify the endpoint with one real call before dispatching work — do not assume the key or the model service works:
If this fails, fix the model endpoint first; the Runner will fail fast on every task otherwise.
The local-llm-kv-cache proxy is an optional enhancement (faster prefix reuse for local llama.cpp models), not a core prerequisite — see Optional components.

1. Clone the repository

2. Install the CLI

The official usage is the installed orbi console script (the PEP 621 packaging in pyproject.toml maps orbi = orbi:main), installed as an editable uv tool install — the official local deployment (Issue #152). From the clone directory, pass --python a COMPATIBLE interpreter (Issue #861): the system python3 when it is 3.14 or newer (Fedora 43 and current Arch ship it), otherwise 3.14 so uv provisions or selects one — Ubuntu 24.04 ships Python 3.12, and an exact-path pin to that system interpreter exits 1 with a dependency resolution failure, while the bare python3 and 3.14 forms let uv select or provision a satisfying interpreter (a managed CPython 3.14 on such a host):
uv tool keeps the CLI in an isolated tool environment; the executable lands in ~/.local/bin (on the PATH of the scheduler unit). Two deployment modes (Issue #330, PR #334): the install command is identical in both — orbi is always installed ONCE from the orbi clone — but the config differs. In the bootstrap mode (this page’s default) repo_dir is the orbi checkout itself: the deployment home and the delivery checkout are one directory. In the external single-repo mode, deploy_home points at the orbi checkout and repo_dir points at a foreign user repo X, so orbi delivers Issues of X without ever being reinstalled from X. The config difference is in step 3 and in the External single-repo mode section below. Why --editable matters: a non-editable install copies the source into the tool environment’s site-packages. The service’s ExecStartPre fast-forwards the checkout to the latest main before every start — with a copied source the CLI would keep executing the stale copy (bootstrap deadlock: Issue #152). With the editable install the tool env imports orbi directly from the clone directory, so the next CLI process (the next timer-triggered Runner) picks up the merged code automatically: ordinary Python source and systemd template/migration changes need NO reinstall and no upgrade command. Since the src layout (Issue #168) the editable finder maps the WHOLE src/orbi/ package directory — a newly added package module needs NO reinstall (the #158 stale-module-list incident is fixed at the root). When the remaining PACKAGING inputs change (a dependency, the entry point, the package name, the build backend or the Python version in pyproject.toml), the Runner refreshes the install automatically at every start (before any slot or claim) — a failing refresh fails the start fast with the structured cli_install_failed line (reason + the exact fix command). That in-Runner refresh is unreachable when the console script cannot even import orbi (the #248 scene: the src-layout migration of #168 left the installed editable finder stale, so the Runner died at the import stage before the refresh could run) — for that class of breakage the service’s FIRST ExecStartPre step self-heals OUTSIDE Python: it probes orbi --version and, on probe failure, runs the same editable force-reinstall — before the second step (orbi sync-engine-source, Issue #535), which needs that working CLI (see Operations for the full mechanics). orbi doctor reports the consistency (cli_source: clean or cli_source: DRIFT with the structured cli_source_drift line), and orbi setup verifies or reinstalls it idempotently. The release package carries no third-party runtime dependency, no token and no user directory — the config file and the user systemd dir stay machine-local. The checkout root carries no orbi.py (a flat file named like the package would shadow the installed package for every process with the checkout root on sys.path); the direct-execution entry python3 -m orbi.cli runs the exact same code as the console script and stays a development/compatibility path.

3. Create the configuration

The repository ships a committed example; the real config is local state (gitignored). orbi setup creates orbi.toml from the shipped example when it is absent. Configuration is kept in orbi.toml (relative paths resolve from the config file directory). ORBI_CONFIG selects another file, and orbi check validates it. The complete reference is Configuration reference. A minimal first-run configuration is:
For external single-repo deployments, set deploy_home to the Orbi checkout and follow the configuration reference.

External single-repo mode (deploy_home)

Issue #330 (PR #334) decoupled orbi’s deployment home from the delivery checkout, so orbi can be installed ONCE from the orbi repository and then pointed at any user repository X: an ai-ready Issue opened in X is picked up, implemented in a worktree of X, and delivered as a PR against X.
  • Bootstrap mode (default): repo_dir is the orbi checkout itself — the deployment home and the delivery checkout are the same directory (deploy_home absent, defaulting to repo_dir). Steps 1–8 of this page describe exactly that mode.
  • External single-repo mode: repo_dir is the user repo X and deploy_home is the orbi checkout. The two checkouts play different roles:
    • deploy_home (orbi checkout) — the CLI self-update source (the editable uv tool install, the ExecStartPre fast-forward and the CLI self-heal), the systemd/ unit templates (the {{ORBI_REPO_DIR}} placeholder renders THIS path), labels.toml, and the prompt defaults;
    • repo_dir (user repo X) — the delivery checkout: Issues are claimed from X, task worktrees are created from X’s frozen origin/<base_branch>, PRs open against X, and the slot locks live in <repo_dir>/.orbi/slots/.
Install once (step 2 above, from the orbi clone), then put the config file in the deploy home — the installed systemd unit reads its config from <deploy_home>/orbi.toml (the {{ORBI_REPO_DIR}} placeholder renders the deploy home path), so orbi.toml and the optional .orbi/env live in the orbi checkout, not in X:
(Task worktrees land in <repo_dir>/.worktrees/ — the delivery checkout — and workspace_root is the directory the implementer agent is told contains the repositories it may touch (the {{WORKSPACE_ROOT}} prompt placeholder); unset prompt / prompt_review resolve against deploy_home.) For a second engine instance, give its config a distinct unit_name (for example unit_name = "website"), then run orbi setup. It installs and checks orbi-website@1/2.service and orbi-website@1/2.timer; no hand-written systemd units or ORBI_CONFIG override is needed. Setup also keeps Runner-created <repo_dir>/.worktrees/ out of checkout cleanliness using the repository’s local git exclude. Everything else — steps 4–8 (model provider, orbi setup, the manual tick, the smoke walkthrough, the timers) — is unchanged: orbi setup reads the labels, the CLI install and the unit templates from deploy_home and checks the delivery checkout repo_dir (see One-time setup).

One machine, multiple deployments (Issue #747)

One machine can host several independent Orbi deployments — each its own checkout, its own orbi.toml, its own timer set. Every deployment config MUST declare a distinct unit_name:
orbi setup / orbi install-units then manage exactly this deployment’s units (orbi-core@1/2.service and orbi-core@1/2.timer); a second deployment with unit_name = "website" manages orbi-website@1/2.* on the same machine. Only ONE deployment per machine may leave unit_name unset — it owns the shared orbi@1/2 names. Never hand-write systemd unit files. The per-tick drift self-heal compares only the installed template units of the configured unit_name, so a hand-written orbi-core.service is invisible to EVERY deployment: template updates never reach it and nothing reports the gap. orbi doctor makes the bypass visible — it lists such files under unmanaged_units with the ORBI_CONFIG each one points at:
The repair is the fix line: set the deployment’s unit_name, run orbi install-units so the managed @ units take over, then retire the hand-written files.

One deployment per task pool

Independent deployments may coexist only when their task pools are disjoint: each source_repos task pool is served by exactly one deployment, whether the deployments run on one machine or on different machines. A different checkout, a different unit_name or a different active_milestone does NOT isolate a shared pool — those deployments still scan and claim the same Issues, and the local slots and claim locks of separate deployment directories cannot coordinate. Deployments with disjoint source_repos — for example one deployment per repository — remain fully supported (Issue #1518). For parallel deliveries on the same repository, use ONE deployment and raise its max_concurrency (step 8 and Operations): the extra Runner instances share that deployment’s slots and its claim/recovery state, so the slots still serialize claims correctly. Raising max_concurrency adds concurrency only — it does not widen the fresh-claim scope and does not move the active_milestone boundary (step 6). The failure is concrete: an independent deployment cannot see another deployment’s live delivery, so it may mistake that run for a lost one and restart it — the two deployments then fight over the same Issue. Give each deployment disjoint source_repos; when one repository needs more concurrency, keep one deployment and raise max_concurrency instead.

4. Configure the model provider

The Runner never starts or manages the model process — it launches Pi, and Pi needs a provider it can actually use. There are two paths, and you need only one:

Path A: Pi’s own providers (no Orbi config)

Leave pi_providers unset and the Runner launches Pi exactly as you would run it by hand — Pi uses its own agent directory and the providers you configured there. Verify the provider is ready with Pi’s own check before dispatching work:

Path B: a pi_providers file (Orbi manages the selection)

Point pi_providers at a JSON file in Pi’s own models.json shape. At every run the Runner materializes a per-run agent dir (<worktree>/.orbi/pi-agent/, gitignored) whose models.json merges your user providers with the file’s providers (the file wins on an id collision) — your existing providers keep working, the file adds or overrides — and selects the configured provider/model for the run. The copy is written verbatim — $VAR / ${VAR} references in an apiKey stay references, and Pi 1.0 interpolates them from the environment the Runner passes to the Pi process at request time (Pi’s own docs/models.md), so the plaintext key is never written into the worktree (Issue #1554). Your file itself is never modified, and config load still fails fast when the selected provider’s reference does not resolve. An OpenAI-compatible remote endpoint (the key stays out of the file — it references an environment variable):
A local llama.cpp server (OpenAI-compatible routes at /v1, default 127.0.0.1:8080; --alias sets the model name the API reports, so the id below must match it):
Then select the provider and model in orbi.toml (the Runner passes them to Pi as --provider groq --model groq/compound, and --thinking <level> when pi_thinking is set):

Why groq/compound and not qwen/qwen3.8-27b

Both models sit on Groq’s Free Plan, and qwen/qwen3.8-27b even carries the higher request quota (1K RPD vs 250 RPD) — but the deciding quota for delivery-scale work is tokens, not requests. Per Groq account-page rate-limit data (provided by a user on 2026-09-12, not an Orbi measurement — re-check the official rate-limit table before relying on it), groq/compound is 70K TPM with no daily token cap, while qwen/qwen3.8-27b is 8K TPM / 200K TPD. At this repository’s delivery scale the difference is one of kind, not degree: the median across this repository’s own deliveries is ≈ 2.2M tokens per delivery, implement and review combined (a Python repository with CI and structured Issues — unverified on other repositories), so the 200K TPD cap cannot fund even one median delivery per day, and the 8K TPM alone means hours of rate-limit waiting. The token wall arrives long before the request wall, which is why the higher RPD never helps. The template, the provider guide and this guide therefore pin groq/compound — whose low TPM still makes short, bounded coding Issues a better fit than long-context repository analysis. These are account limits, not an Orbi guarantee; real Groq requests, usage metering, and 429 responses remain untested here.

Keeping the API key out of the repo and the docs

The file references the key as an environment variable ("$GROQ_API_KEY"); the actual value lives in the gitignored state dir, in an env file the systemd unit loads at service start:
The committed unit template carries EnvironmentFile=-{{ORBI_REPO_DIR}}/.orbi/env (Issue #172); the {{ORBI_REPO_DIR}} placeholder is substituted with your checkout path at install time (Issue #262), so the installed unit’s EnvironmentFile points at your .orbi/env automatically — no manual edit. A keyless local server needs no env file at all (the literal "local" placeholder above satisfies the shape check).

What the Runner validates (fail fast, before any slot or claim)

  • the file exists and is valid JSON with a non-empty providers object;
  • every provider that lists models has a non-empty baseUrl and an api (provider- or model-level);
  • the selected pi_provider and pi_model are defined in the file;
  • the selected provider’s apiKey env-var reference resolves to a non-empty value — a missing or empty variable fails the start with the variable name only (never a key value).

The provider boundary

The documented and tested path is an OpenAI-compatible endpoint (api: "openai-completions") — a local llama.cpp server or any compatible API. The file is passed to Pi verbatim, so other Pi catalog entries keep working through Pi’s own agent dir; Orbi does not test or guarantee per-provider integrations beyond that boundary.

5. Run the one-time setup

The setup entry does the whole initialization in one command: it verifies the prerequisites you installed (git, gh, python3, uv, the systemd user session — a missing one fails fast with the actionable install guidance), verifies or reinstalls the editable CLI install (Issue #152 — the running CLI must import from the deployment checkout), verifies gh auth status and the repo permissions, aligns the platform labels from the repo-managed labels.toml (the single source of truth for label name, color and description — a commit never creates labels, and a missing label makes the scan silently skip that state), installs the systemd user units and enables the timer, checks the checkout, and reports the optional model proxy:
It is idempotent (re-running it never creates duplicate labels, units or timers) and fail-fast (a wrong repo, insufficient permission, a dirty checkout or a missing systemd user bus stops it with the concrete reason). See One-time setup for the full output contract, success and failure examples. On a repository Orbi has never delivered, setup also offers orbi suggest. On a terminal — and only when the model provider is ok and the repository has no Issue carrying a delivery label (ai-ready, ai-in-progress, ai-pr-opened, ai-merged or the repository’s own dispatch_label) — it asks once: Scan OWNER/REPO and suggest three Issues to deliver? [Y/n] Y (or Enter) runs the interactive orbi suggest flow: it reads the repository, proposes up to three Issues, asks once per suggestion, and files or labels the ones you confirm, printing the URL of every Issue it filed or labeled at the end. A caller without a terminal never sees the prompt: setup prints Run 'orbi suggest --repo OWNER/REPO' to get three suggested Issues. and --json prints neither the question nor the hint. At any later time orbi suggest --json prints the Suggestions document (repo, base_sha, session_dir and the suggestions) to stdout and writes nothing.

6. Run one tick manually

The manual command is for first verification and troubleshooting only — normal operation is scheduled by the timer (step 8):
(PYTHONPATH=src puts the checkout’s src/ layout on the import path; -m runs the Runner package module — the installed orbi console script runs the same tick.) One tick does at most one thing: resume an opened PR (review/fix/merge) or claim one ai-ready Issue (p0-labeled Issues are picked first, then bug-labeled Issues, then plain features; with active_milestone set, only Issues of that Milestone are claimable — Issue #139), then it exits. With an empty ready queue it exits cleanly without claiming anything.

7. Smoke walkthrough (from zero)

The smallest end-to-end proof that your setup works. Run it in the clone directory from step 1; every command is relative to that directory.

Verify the full chain

The delivery does not end at the PR — the same run (same run id, branch, worktree and PR) continues through an independent review and the merge on later ticks (manually or via the timer). Each stage has a concrete, checkable success criterion:
You are done when the Issue has moved ai-ready → ai-in-progress → ai-pr-opened → ai-merged, the PR is merged with Fixes #<n> in its body (GitHub closes the Issue natively on merge), and the journal shows run_end ... result=pr_opened, review ... verdict=pass and merged pr=.... If any step fails, the Issue is marked ai-blocked alone with the scene (run id, branch, worktree, session, phase, last activity, the concrete error) and the journal shows run_failed ... reason=... — see Operations for recovery.

Troubleshooting

Real failure scenes, where to find the evidence, and the fix. Every one of them fails fast — nothing is retried silently. The editable deployment home is self-updated before every service start. Any local tracked source edit can therefore block the fast-forward; use a dedicated local branch or stash it before retrying. The preflight emits deploy_home_dirty with the changed files and fix command instead of leaving only raw Git stderr. For the label lifecycle, the journal fields and the recovery rules see Operations; for the review/merge contract see Workflow.

8. Verify the timers

The setup step installed the unit templates and enabled timer instances through max_concurrency: @1 through @N for N (up to MAX_RUNNER_INSTANCES = 5; each instance triggers its own service instance — see Operations); verify them:
The setup output carries one line per instance; enabled instances have an active state and next trigger time, while surplus instances report disabled and inactive. The cadence, the ExecStartPre fast-forward and the per-instance start semantics are defined in Operations (The timers). At capacity N, N instances may run concurrently; the capacity and the enabled timer count are both max_concurrency, while the flock slots remain the final safety boundary.
The committed unit templates are machine-independent: they carry the {{ORBI_REPO_DIR}} placeholder instead of a hardcoded checkout path (Issue #262). orbi install-units (and orbi setup) substitute it with the resolved path of the deployment home (deploy_home, defaulting to repo_dir) at install time, so a clone at ANY location deploys cleanly — no manual edit of the installed units. In the external single-repo mode (Issue #330) that means the unit’s WorkingDirectory, ORBI_CONFIG, EnvironmentFile, the ExecStartPre fast-forward, the base-sync.lock and the CLI self-heal ALL act on the orbi source checkout — never on the delivery checkout X. ExecStart is the installed orbi CLI and stays home-relative (%h/.local/bin/orbi). The repo templates stay the single source of truth for everything else — see Operations on drift detection.