Skip to main content
Orbi claims an eligible Issue, implements it in a worktree, opens a PR, independently reviews it, merges it, and records the result. One Orbi task is one runtime outcome, in one of two shapes:
  • Fix — when X, should Y, actually Z. Example: #1074 — when gh auth status exits 1 while the active account is valid, the Runner should resolve it; actually the exit code failed the delivery.
  • Change — when X, the user should be able to Y; today they cannot because Z. Example: #1048 — when a user starts the runner from the published image, they should get a working model provider; nothing read the ORBI_PI_* variables yet.
A task starts as a GitHub Issue and ends as a merged PR (or an ai-blocked scene that needs a human). This page covers the complete automatic chain and the project’s GitHub workflow vocabulary.

The complete chain

State machine (the seven delivery states; the review/fix loop stays on the same PR — only its head may advance):
A failure of an existing run/PR is classified (Issue #50): an AI-recoverable failure (a Pi execution failure, a verification failure, an unpushed local commit, a finding that could not be fixed) keeps the Issue in the automatic fix loop — ai-fix-needed with the failure comment on the Issue AND the PR carrying the full scene — and the next tick resumes the same run, branch, worktree and PR (no replacement PR, no re-claim). A missing worktree is not a failure at all: the resume rebuilds it from the remote delivery branch and continues (Issue #807 — the worktree is a local cache of the remote state, branch + PR live on GitHub). Only an external precondition the AI cannot safely judge or fix (an unrecoverable scene, a base-branch config change, an exhausted review round budget, a PR closed without merge, a delivery branch deleted from the remote — nothing left to rebuild the worktree from) fails fast: the Issue is marked ai-blocked with the explicit reason why automatic recovery is impossible, and the automatic loop never touches it again — a human decides the next step.

Steering an active delivery

You can correct direction without cancelling an active delivery. While the Issue is ai-in-progress, add a comment with the new decision or edit the Issue body. These two channels are equivalent: both are picked up by the Runner’s next steering poll (the interval is steering_poll_seconds, 60 seconds by default), and the active session restarts with the correction. The restarts are bounded by steering_max_rounds (3 by default); after the limit is reached, further corrections do not restart that delivery and the journal records steering_limit_reached. Let the current delivery finish, then put the revised scope in a follow-up delivery. Only comments from trusted repository roles—OWNER, MAINTAINER, MEMBER, or COLLABORATOR—can steer a run. A comment from a public commenter is ignored by design, because allowing arbitrary commenters to steer a delivery would expose a prompt-injection path. The initial trusted comment timeline is capped at issue_comments_limit (200 by default): older comments are dropped and the injected context states how many were omitted. Dead-loop guard and comment dedup (Issue #825): a resume may bind a NEW run id (one recoverable failure is enough), while the PR body is written once at creation, so the resume verification accepts ANY run marker the delivery line has used — the marker set is read from the Issue’s TRUSTED comments (the same trust filter as the resume scene; a copied marker in a public comment widens nothing, and the branch, base, head and exact-URL checks stay intact, keeping the #45/#89 anti-PR-swap posture). A failure comment of one (run_id, failure fingerprint) pair is posted ONCE (the hidden <!-- orbi:fail=... --> marker is the key); every identical repeat bumps that comment’s hidden :<count> suffix in place (the journal records the repeat). The same pair recurring to 3 CONSECUTIVE failures (a scene block — a newly opened PR, a completed review round — means the line advanced and resets the streak) is classified as a dead loop, not a transient error: it escalates to ai-blocked with the count and the unchanged-precondition verdict in the comment. Rules of the chain:
  • One task = one run = one feature branch = one worktree = one PR. The PR number never changes during the review/fix loop; only its head may advance.
  • Only the two opened-PR states are picked up automatically: ai-pr-opened (awaiting review) and ai-fix-needed (awaiting the next review session). ai-ready is claimed as new work; ai-blocked is never auto-recovered. A delivery that carries the backfilled ai-in-progress label is still picked up (Issue #178: a runner killed during review leaves it behind — the positive opened-PR qualifier already keeps implement-phase Issues out of the scan).
  • The review/fix loop is bounded (5 rounds). The completed-round count lives in the trusted scene (review_round, Issue #788): every round comment carries the updated scene block, so the next tick reads the advanced count from it (a human’s blocked -> ai-fix-needed recovery after the scene still starts a fresh budget, Issue #483). Exhausting rounds with findings marks the Issue ai-blocked (the bounded loop is a human decision); a recoverable failure (Issue #50) keeps it ai-fix-needed. Either way the PR, branch and worktree stay intact. The reviewer guards the budget itself (Issue #878): before the work its prompt reads the delivery’s round comments (the same run_id) and, when the same failure reason already appears in two or more consecutive rounds, emits the findings verdict naming the repetition instead of repeating the fix — a wall is hit by at most two rounds.
  • No in-tick waiting (Issue #788): the old in-process wait loop (wait_for_delivery, CI/mergeability polling with release_ci_wait_seconds / mergeable_wait_seconds) is gone. Every wait is the waiting primitive — write the state, return, release the slot, and the next tick re-reads (delivery_ci_pending, merge_gate_ci_pending, merge_gate_mergeable_unknown). The resumable scan skips a CI-pending candidate outright (Issue #1473), so a pending PR never costs a whole tick. The crash-recovery path IS the path.
  • Base advances are absorbed by a plain git merge of the latest origin/<base> on the task branch (conflicts resolved manually), followed by a full test rerun. No force push, no auto conflict resolution, no push of the protected branch.
  • Absorb contract (Issue #877, machine-checked by the Runner): a review round that starts with the head behind origin/<base> or facing a merge conflict must end in one of two states — the branch contains origin/<base> (git merge-base --is-ancestor true), or the verdict is findings stating that the round attempted the absorb and abandoned it, with the concrete reason. A pass whose head still lacks the base is named in the round comment as an absorb-contract violation (review_absorb_abandoned); the bounded 5-round semantics are unchanged.
  • Git transport (Issue #114, #580): git data operations go over the configured transport (git_transport: ssh by default — git over SSH —, or https with the gh credential helper), GitHub API operations stay on the gh token — the full two-channel boundary and the pre-start check: Operations; the credentials: Security.

Reviewing with a different model

The independent review does not have to run on the model that wrote the code: review_pi_provider, review_pi_model and review_pi_thinking select a separate provider/model/thinking level for the review session, so the reviewer does not inherit the author’s blind spots. Each key falls back independently to its implementation-side value (review_pi_provider → pi_provider, review_pi_model → pi_model, review_pi_thinking → pi_thinking), so overriding only one of them is valid. Both selections must exist in the configured pi_providers catalog and are validated separately at startup — an invalid review selection fails fast with review provider selection invalid. No specific pairing is recommended or benchmarked here; the capability and the fallback are the contract.

The merge record: external_commits and commits

When the Runner merges a delivery PR, the Orbi merged PR: Issue comment shows the user only what they act on — the PR link, the review result and the target branch — and folds the merged-as-is facts (Issue #833) into one collapsed Run details block:
The Run details rows keep the key=value form (equals sign, not colon) because they are machine-read anchors: the orbi-cloud mergeCommentFields reader and the repository’s comment-reader contracts match external_commits=K, review_rounds=N and run_id=… exactly. The Orbi: merged milestone line carries the same facts in the same key=value form.
  • commits — all commits of the PR branch relative to the base at merge time (the denominator of the ratio).
  • external_commits — commits NOT in the engine’s own push history (the head the PR opened with plus every review/fix head the Runner itself pushed; git authorship is never inspected). 0 = merged as-is: no human changed a line on the delivery branch before the merge. The count is read after the merge through the merge commit’s parent chain (M^1 the base tip, M^2 the PR head) minus the engine’s recorded push line: the last engine-pushed head, and — for an external takeover (Issue #608) — excluding the taken-over contributor head the line starts from, so the contributor’s commits stay external.
  • external_commits=unknown — the push history is unverifiable (a recreated worktree lost the run-state record, a fix push’s round ended before its record and the history is incomplete, or a recorded head or base is not an ancestor of the merged head). A squash or rebase merge (Issue #1480) has no M^2, so both metrics are unknown there as well. An uncountable history is never written as 0.
The engine’s pushed heads are recorded in the task worktree’s run state (pushed_head, Issue #219 file) at three points — the delivery push, a verdict-covered review/fix push, and the round-start adoption of a head a previous session pushed but whose round ended before the re-freeze record — so resumes of the same worktree keep the history; a fresh worktree starts without it and degrades to unknown. An external takeover additionally records the taken-over head once at claim time (pushed_base), and its round-start adoption never runs: the worktree legitimately starts on the contributor’s head. Recording is bypass-safe: a missing or corrupt state file logs pushed_head_unrecorded and the delivery continues.

Delivery labels

GitHub labels are the external state machine. They are not created by commits — initialize them per repository (see Getting started): A repository policy may block the merge even after Orbi’s review and checks pass. Orbi stops at ai-awaiting-merge; the Issue comment names the exact head and the one maintainer action (including the settings URL when applicable). After the maintainer changes the policy or supplies the required approval, a later tick resumes and Orbi merges the PR itself. A policy rejection without a known FAILED preflight action, or any other merge failure, remains ai-blocked.

Human acceptance gate (ai-human-review)

The main line of the product is unattended: the default pipeline runs with humans OUT of the loop, and the gate is off by default — with the switch off the delivery, review, merge and release behavior is exactly what it was before (Issue #763). The host config turns the gate on (human_review_gate = true in orbi.toml — an operator trust decision, not a repository policy). Every delivery then posts ONE acceptance checklist comment to the Issue when its PR opens, split in two columns:
  • Column 1 — what the run already verified (evidence, not todos): the PR head’s CI check status (Issue #1613: the engine never parses a local test log), the covered layer (the repository test suite), the delivered file count.
  • Column 2 — the machine-unverifiable minimum, derived per delivery from the changed paths and the available evidence (never a hardcoded human-items template): business intent (tests prove the implementation matches the tests, not that the tests match the intent), UI visuals and copy, external-environment verification, and missing-evidence items when the head’s checks have not passed or the diff cannot be read.
While column 2 is non-empty, it is a GATE: the review round short-circuits before any Pi session — the ticket returns to ai-ready (the opened-PR label stays as the resume anchor), the slot is released, and every later tick costs one label read. The wait does NOT consume the review-round budget and never touches ai-fix-needed. A person who confirms the checklist applies ai-human-review, and the next tick reviews and merges the delivery exactly as it does today (the verdict binding and --match-head-commit are untouched). An EMPTY column 2 is the target state: the checklist says 无需人工介入 (“no human intervention needed”) explicitly and the delivery passes without the label. As model capability grows, column-2 items migrate into column 1 — the checklist comment carries a machine-parsable <!-- orbi:human-review {...} --> block (schema 1) so the migration is toolable without changing the artifact shape. The checklist makes no correctness guarantee of any kind: column 1 is evidence of what was verified, column 2 is what a person still confirms.

Thin-ticket clarification gate (ai-needs-detail)

A confident delivery against a vague ticket is worse than no delivery: it burns a run, opens a PR nobody can accept and buries the real question. The gate is OFF by default and a repository turns it on with clarify_thin_tickets = true in .github/orbi.toml; an unopted repository behaves exactly as before. With the gate on, a fresh claim first asks ONE Pi session — --no-tools, no worktree, no branch, no delivery — to judge the ticket body on three checks: the observable result (what a user sees or does differently), the acceptance condition (how a finished delivery is told apart from an unfinished one) and the single outcome (one fix or one change, not several unrelated things). A passing verdict changes nothing and the delivery starts. A failing verdict stops the attempt before any work exists: ONE Issue comment lists every missing piece and the repair action, the repository’s claim label (ai-ready by default — dispatch_label may name another) is replaced by ai-needs-detail, and the tick ends — no worktree, no branch, no Pi session, no PR, and the slot is released for the next Issue. The ticket then waits for a human exactly like ai-blocked: the next chance to be delivered comes from editing the Issue and adding that claim label again, which re-runs the judgment from scratch. Every error path is a bypass — the gate never loses a delivery. A model error or timeout, an unusable answer, or a failed comment/label write logs the clarify_check_skipped journal line and lets the delivery proceed. The judgment and the stop are visible as the clarify_check_skipped and clarify_needs_detail rows in Operations.

Ticket dispatch: content, dev and ops

ai-ready is the execution switch for every task type; a second label selects the type (Issue #537). Three types exist — pick one per Issue at dispatch time: Ops semantics (Issue #537):
  • The trust boundary is (credentials, ticket) — never a command whitelist. The session receives the runner’s whole environment unfiltered (gh auth, CLOUDFLARE_API_TOKEN, AWS keys, … plus the deploy-home .orbi/env file); how wide the credentials are is the user’s decision — the runner never trims them.
  • The ticket body is the authorization. The session executes only the ops actions the ticket states (which PR to merge, which environment to deploy); an action beyond the ticket is reported in a comment and the session stops. Human approval gates (environment approval, Release approval) are never performed by the session — a prompt convention, not enforced code.
  • Delivery = evidence (the fact culture of Issue #526): every step posts the real command and its real output / API response verbatim on the ticket; a step not executed is labeled as not executed. A pure-ops run (no commit) ends with the Runner closing the Issue and removing ai-in-progress — no PR. Committed code takes the normal PR path with review and merge.
Research-only tickets (Issue #739): a ticket whose body demands investigation only — “research/design first, do NOT implement yet” — is an ops ticket at dispatch time: give it ai-ready + ai-ops-only. A dev ticket cannot carry that instruction: the dev delivery criterion is a commit on the task branch, so an agent that correctly follows it delivers no commit and the run fails with the agent delivered no commit — the Issue is burned ai-blocked. The ops playbook’s evidence discipline (real commands, verbatim output, unverified items labeled as such) IS the research deliverable; a no-commit ops run completes normally (the Issue is closed, ai-in-progress removed), and research that continues into code still takes the normal PR ceremony. Before Issue #537 the label ai-ops-only dispatched the content agent (the #530 rename never changed the behavior — the name promised ops, the mode was content-only); the content path dispatches on ai-content-only now, and ai-ops-only is the full-execution ops path.

Run marker and run_id

Every task attempt generates one run_id (8 hex chars, e.g. e07383c2) and reuses it for every step of the attempt; a retry of the same Issue generates a new one. The same id appears in:
  • every journal line of the attempt (prefix [e07383c2]);
  • the Issue/PR comments: visible field run_id=e07383c2 plus the hidden machine-readable marker <!-- orbi:run=e07383c2 -->;
  • milestone and scene comments also carry the hidden runner marker <!-- runner=1a2b3c4d -->: a fingerprint of the deployment that wrote them — the executing checkout’s HEAD, the package version for a non-editable install, or unknown when the probe fails (the live progress comment and PR comments do not carry it);
  • the Orbi opened PR scene comment carries the hidden versioned run scene <!-- orbi:scene:v1 {json} --> (src/orbi/scene.py): the machine-readable recovery record the resume scan parses — a corrupted scene and a missing scene are distinct failure branches (Issue #786);
  • the stable Issue-based feature branch (orbi/<owner>-<repo>-issue-<N>);
  • the worktree name (.worktrees/orbi-<owner>-<repo>-issue-<n>-e07383c2), plus the Pi session directory and run artifacts;
  • the PR body — the stable marker <!-- orbi:run=e07383c2 --> is part of the PR contract; the Runner rejects a PR without it.
Reconstruct a full timeline with one grep:

Failure record: orbi:failure:v1

Every failure the engine reports through its failure path is machine-readable: both the detailed Orbi: blocked / Orbi: fix needed comment and the short milestone posted beside it carry exactly one hidden versioned block next to the run marker:
outcome is blocked (terminal, a human decides) or fix_needed (the next tick resumes the same run). reason_code says why the delivery stopped; action_code says what the reader should do; retry_safe says whether a plain re-queue can succeed without a human repair. The sets are closed and owned by the engine (src/orbi/failure.py); an exception the classifier does not recognize maps to unclassified, the block is never omitted, and a comment written before this feature carries no block at all — readers ignore it (parse returns None). Actions: requeue relabels the Issue ai-ready (or presses retry) — nothing to fix; fix_ticket fixes the failure and re-runs the Issue; decide is a human decision; check_credentials verifies and repairs the credential, then retries; wait_quota retries after the quota window resets. The readable headline, **Reason:** and **Action:** lines stay beside the block for a human reader, and the milestone’s visible summary is the same bounded, host-path free reason — a full command stderr is never cut to its first word.

PR body contract: Fixes #N

The PR description must contain Fixes #<issue-number> (it may be on the first line). GitHub reads the body (not the PR title) and closes the Issue natively when the PR merges into the default branch. The Runner verifies the keyword during PR acceptance and fails fast when it is missing. This validation failure is recorded on the affected Issue and does not terminate the runner tick. A staged initiative must not use one Issue and one PR for several independent runtime outcomes. Use an Epic or parent Issue for coordination, split each phase into a child Issue, and give every child its own delivery PR with Fixes #<child-number>. This preserves native close behavior without adding a second PR-body syntax. If an existing PR says Part of #N, the maintainer must update it to the exact Fixes #N reference when that PR is the complete child outcome, or split the remaining phases into child Issues before retrying.

Epics, Release tasks and P0 priority

The project organizes multi-task work with plain GitHub primitives — no DAG, no priority numbers, no separate queues:
  • Epic: a coordination Issue that groups a set of related tasks (for example the v0.1 release checklist). It is marked with the ai-epic label. An Epic is not itself a directly executable task: the actual work is split into independent ai-ready Issues, each with one runtime outcome, one PR, one review and one merge. The Runner NEVER claims an Epic (Issue #93): the ready claim scan skips it with the structured epic_not_claimed issue=N repo=... journal line (no claim, no label change, no worktree, no run, no slot), and the restart-resume scan excludes it too. Epic completion is declared exclusively by GitHub’s native sub-issues relationship: every child Issue must be closed (or its PR is merged) and there must be no open native blockedBy dependency. References in the Epic body, including Children/Child Issues sections, are not declarations. An Epic with no native sub-issues, or malformed child or dependency state, fails closed. The Runner reconciles open Epics on every tick and leaves an idempotent audit comment. Any prose describing extra acceptance conditions is an Epic definition error: make that condition a final child Issue instead (for example, a browser acceptance ticket). The same tick then reconciles open Milestones: an exact-title published Release (draft excluded) plus zero open Issues closes the Milestone; all other cases remain open. The same tick finally sweeps open PRs (Issue #746): a stable-delivery PR (orbi/<repo>-issue-<N> head branch) whose source Issue a human closed is reported ONCE on the PR with an idempotent comment — every resumable scan reads state=open Issues only, so without this sweep the PR would dangle forever. The merge/close decision stays with the human: the Runner never auto-merges and never auto-closes across a human’s close.
  • Release task: an ai-ready Issue additionally marked with the ai-release label (Issue #98). It is NOT a development task. The three ordinary scans skip it with release_not_claimed; only the release fallback scan uses allow_release=True. Before claiming it, _pick_from_scan applies the Milestone completeness gate: while its Milestone has any other open Issue (open_issues > 1), the Runner records release_milestone_incomplete, leaves the release ai-ready, and retries on a later tick. This is a recoverable wait, never ai-blocked. A Milestone query that cannot be evaluated also skips the claim, with release_milestone_check_failed. This claim layer runs before process_issue and before any release-state-machine wait, so a release queued behind open tickets has no wait_started value and accumulates zero release_waited_seconds. Once claimed, process_issue routes it to the deterministic release state machine instead of run_pi — no Pi session, no PR. The state machine is idempotent and resumable (a restart resumes the same run id/worktree) and runs these steps in order:
    1. Strictly parse the ## Release declaration from the Issue body: version, base_branch, and scope (a list of #N items) or scope_from_milestone, plus optional version_file (pyproject.toml by default, package.json, or none). The declaration carries NO test contract (Issue #569): test acceptance is the GitHub Actions CI result on the release commit (the Issue #268 CI-wait gate). Missing, duplicated or unknown fields fail fast.
    2. Freeze the base — the release commit is exactly origin/<base_branch> (fetched under the base-sync lock). Before any gate wait, the declared (or defaulted) version_file is proven to exist at the root of that frozen tree (Issue #740): a mismatch fails fast at claim time, naming the supported ecosystem files that DO exist there and the none option, never as a late FileNotFoundError after the gates and the scope verification.
    3. Enforce the pre-release gates. Only after the claim layer above has admitted the release can the gate-layer release_waiting_deliveries wait for open Issues carrying ai-in-progress, ai-pr-opened, or ai-fix-needed (the release Issue itself is excluded). With a Milestone, only Issues in that Milestone hold the gate (Issue #671); without one, the repository-wide scan is unchanged. A bare ai-ready Issue does not hold this gate. The bounded wait uses RELEASE_DELIVERIES_WAIT_SECONDS; exceeding it produces a delivery wait timeout and moves the release to ai-blocked. Then require CI green on the release commit (no check runs passes with explicit evidence; a pending check is an intermediate state — the gate waits for the final conclusion, one release_waiting_ci journal line and one progress-comment update per poll, up to release_ci_wait_seconds (default 1800 s); a wait timeout fails with its own reason, never reported as a CI failure). Open PRs are deliberately NOT a gate (Issue #608, maintainer ruling): an open PR is queue state, never a release premise — whether open PRs exist, how many, or who authored them says nothing about the release; a stranded PR is the delivery loop’s takeover/reconciliation job. A gate that cannot be checked because of a real gh failure is a failed gate.
    4. Verify the scope item by item: gh pr view must report MERGED, otherwise gh issue view must report CLOSED with stateReason COMPLETED — a NOT_PLANNED closure (duplicate / won’t fix) is annotated in the Scope evidence as excluded and never enters the Changelog, and a Changelog PR link is written only when its PR is contained in the tagged commit range (Issue #1492). The Changelog itself is derived from the tagged commit range (prev_tag..release_commit), never from the Milestone Issue list: the Milestone scope decides only whether a release may run, not what its notes say. For milestone-derived scope, open_milestone_evidence does not wait: still-open milestone Issues are excluded and recorded as NOT released (still open in milestone <name>): <item>. Checkbox parsing is forbidden — every scope item is checked against the live API.
    5. Test acceptance is the CI-wait gate on the release commit: after the version bump the gates re-run against that exact commit (step 3’s release_ci_wait_seconds wait), and a red or timed-out CI takes the existing recoverable failure path (ai-blocked). No local test execution (Issue #569) — the release flow is bump → push → CI-wait → tag. If Actions is red or times out, the existing failure path blocks the release; the version bump may remain on main as an acceptable state. The bump is idempotent, so repairing CI and retrying the release safely prepares the requested version again.
    6. Tag: when the remote tag is absent, create an annotated tag at the release commit and push it with a plain push (never --force). When it exists it must point EXACTLY at the release commit — a mismatch fails fast. An existing tag is never moved, overwritten or force-pushed.
    7. Publish the GitHub Release (idempotent) whose notes carry the full verification evidence: version, tag, release commit, per-item scope evidence, gate evidence, test evidence and the run marker. The docs-site Release pages are NOT written by the state machine (Issue #1483): the repository’s own .github/workflows/release-docs.yml (Issue #1482) renders docs/release-<version>.mdx and docs/zh/release-<version>.mdx from the published Release body, inserts the new version at the head of the Releases/发布 navigation groups in docs/docs.json (both languages, latest first) and moves the (latest) title marker to it. Since Issue #1096 both groups nest one collapsed Earlier releases subgroup — the automated sync puts the new release at the head and moves every entry beyond the three newest into that subgroup, so the visible group keeps the three newest releases. Idempotent: an existing page with identical content is neither regenerated nor overwritten, and a run with nothing to change commits nothing; an existing page with different content fails fast — the workflow run fails, never a silent skip.
    8. Apply ai-merged and close the release Issue (terminal delivery transition); the merged event clears ai-ready and ai-in-progress, so the released Issue ends ai-merged ALONE (Issue #1526).
    9. Close the Milestone whose title is EXACTLY the released version (Issue #214), then write the success comment (release URL, tag, commit, evidence, Milestone evidence). Exact title match only (no guessing, no fuzzy match, never a different Milestone): close it when it has 0 open Issues; already-closed is an idempotent success (no reopen); a missing Milestone or remaining open Issues fail fast with the version, the Milestone number/url and the open issue list (never a silent skip). The release’s own ticket is never counted as unfinished work (Issue #808): it is being closed by this release, so a stale-open listing of exactly that ticket — the issue index lags seconds behind the close (Issue #754) — does not block or delay the close, while any other open Issue still refuses it. Any failure: ai-blocked ALONE (a release is a human decision point — no automatic retry), the failure comment carries the run marker and the concrete reason, and the exception propagates so the tick fails fast.
    The ai-release label is a type marker, NOT a delivery state: it is not part of the delivery-state machine, not an exclusion of the ready scan, and the Runner never adds or removes it — only the human does (at dispatch time, together with ai-ready).
  • P0 priority: the plain p0 GitHub label marks an urgent Issue (a production outage). It is NOT a delivery state — it only orders the ready pickup, never changes the Issue granularity, any delivery state, or the terminal-state semantics, and the Runner never adds or removes it. The ready pickup order is fixed: ai-ready+p0 → ai-ready+bug → plain ai-ready (three gh issue list scans sharing the exact same exclusions and blockedBy semantics). P0 obeys every existing exclusion rule and the single-slot constraint: a blocked P0 is skipped (falling back to the bug/plain scans) and an in-flight P0 is resumed by the restart scan. A failed P0 run enters ai-blocked ALONE (the claim label is removed; the ai-ready residue is excluded by every ready scan), so no tick re-claims it — no infinite retry. There is no priority number or weighted queue.
Task dependencies between Issues use GitHub’s native blockedBy relation (gh issue edit N --add-blocked-by M); the Runner reads the field and skips Issues with open blockers. Depends on #N lines in an Issue body are not parsed.

Repository-level delivery policy (.github/orbi.toml)

A repository can version its delivery policy as code. At claim the Runner reads .github/orbi.toml from the repository’s default branch tip through the GitHub contents API — never the task branch, never the local worktree — and applies its whitelisted keys per key over the host orbi.toml. A repository without that file behaves exactly as before (zero change). The location is fixed; only the file NAME may be overridden per source repo with the optional [[repositories]] entry field config_path (default .github/orbi.toml). Only delivery-policy keys are allowed:
The override is per key: when the repository file declares active_milestone, it wins over the host config and that is the value the claim scan uses. orbi milestone set <title> lands the new value in the same place — the repository policy when it declares the key (a contents PUT on the default branch), otherwise the host orbi.toml line — and prints wrote: naming the target (Issue #1306). A host-only identity/security key (see Security), invalid TOML or a wrong type fails the claim fast with the offending key names and the Issue becomes ai-blocked — one repository’s bad file never affects another pool. Any other unrecognized key is ignored instead: one repo_config_keys_ignored journal line names the key and the engine version, the run comment carries repo_config_ignored, and the claim proceeds with the known keys (Issue #1329). The file is read by whatever engine version the delivery host runs, which is often older than the code that wrote the file, so a new policy key must be safe for an older engine to ignore: it defaults to the pre-feature behavior, and an engine that does not know it delivers as before. The host-only set also owns host scheduling: max_concurrency can never be delegated to a repository file — the concurrency slot pool belongs to the deployment host. dispatch_label must not be a delivery lifecycle label (ai-in-progress, ai-merged, …): those stay host constants. The block lands on the Issue the claim selected; a repository whose dispatch_label is not ai-ready has no ai-ready candidate to block when the file itself is malformed, so the fail-fast reason is the journal’s repo_config_invalid line (no label change, no comment). The run comment records repo_config: <sha>; when the sha differs from the previous run it also carries the repo_config_changed annotation and, when the previous content is readable, the effective policy diff as repo_config_diff, so a change is always visible on the run. A read that fails for a transport reason (rate limit, network) is logged as repo_config_read_failed and the host config applies — the degradation is visible as the missing repo_config field on the run comment.

Waiting for a Milestone decision: the one reply that answers it

When the whole chain goes quiet, the Runner says WHY instead of waiting silently. Two idle states are surfaced through the SAME decision notice — one Issue carrying the fingerprint orbi-milestone-advance old=<active> candidates=<titles> — and each of them is answered by the same in-ticket command, a trusted /milestone <version> comment posted on that notice: Both notices are idempotent (one open notice per wait, never one per tick), are closed automatically with a receipt once the wait ends, and never act by themselves: the Runner never creates a release ticket and never advances the Milestone without the reply. The reply runs the deterministic three-step /milestone command (milestone → release ticket → active_milestone) described in Operations; no model call is involved.