One-line installation
New users only need to run the installer; it installs the CLI, creates the config, and starts the interactive setup:curl | bash, download and inspect first:
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 asorbi-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:
--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):
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):
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:
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 installedorbi 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:
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: anai-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_diris the orbi checkout itself — the deployment home and the delivery checkout are the same directory (deploy_homeabsent, defaulting torepo_dir). Steps 1–8 of this page describe exactly that mode. - External single-repo mode:
repo_diris the user repo X anddeploy_homeis the orbi checkout. The two checkouts play different roles:deploy_home(orbi checkout) — the CLI self-update source (the editableuv toolinstall, theExecStartPrefast-forward and the CLI self-heal), thesystemd/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 frozenorigin/<base_branch>, PRs open against X, and the slot locks live in<repo_dir>/.orbi/slots/.
<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:
<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 ownorbi.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:
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: eachsource_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)
Leavepi_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):
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):
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:
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
providersobject; - every provider that lists models has a non-empty
baseUrland anapi(provider- or model-level); - the selected
pi_providerandpi_modelare defined in the file; - the selected provider’s
apiKeyenv-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), verifiesgh 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:
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: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 throughmax_concurrency: @1 through @N for N (up to
MAX_RUNNER_INSTANCES = 5; each instance
triggers its own service instance — see Operations); verify
them:
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.