About pauses
A pause is a durable interrupt of a run that is not the end of it. It is never an error string: the pause, its escalation record and its comment are written before anything is reported. A pause continues by resume or ends by cancel.
Pauses fall into four classes, by who resolves them:
| Who resolves it | Cause | How it ends |
|---|---|---|
| The controller itself | Rate limit, CI pending, reviewer not staged, a transient outage | Retries with backoff within its bound and resumes; no ruling |
| The Operator | Something repairable in the environment: infrastructure, credentials, network, sandbox | The Operator repairs and resumes |
| The Planner | A required source cannot be prepared (missing_source) | The Planner corrects the source |
| The Principal | Escalation, scope exception, round cap, a finding open two rounds | The Principal rules; the ruling is a new Intent version |
| Role | In pauses |
|---|---|
| Controller | Raises every pause, writes its records, retries automatic-recovery pauses, consumes resolutions |
| Operator | Reads and presents the pause packet (task_); requests resume or cancel; never rules |
| Principal | Posts the ruling with vinaya pr rule, or writes the ruling marker on the Issue |
| Planner | Corrects sources (missing_source), or widens the Surface by superseding the brief (widen_surface) |
How it works
- 1
The run pauses
The controller writes the held state, an escalation record and a pause comment that says who must act and how to continue.
- 2
Someone acts
The controller retries by itself, the Operator repairs, the Planner corrects the brief, or the Principal posts a ruling.
- 3
The run continues or ends
A resume runs the loop again from the held round. A cancel stops the run. Each pause is resumed or cancelled, once.
Answer a pause with a ruling
When a pause is addressed to you, the Principal, the pause comment tells you what to decide, the ruling marker to use, and the ordinal your ruling must exceed.
Write your ruling in a file and post it:
vinaya pr rule <pr> --file <ruling.md>Then continue the run: the watching controller picks up the ruling by itself, or the Operator calls task_resume, or you run the task run or --resume command the pause comment names. To stop instead, cancel (see Cancel).
Pause reasons
Each reason is addressed to one authority (requestedAuthority).
| Reason | Addressed to | Raised when |
|---|---|---|
escalation | principal | A reviewer returns ESCALATE, or the Developer posts a stop comment before any push |
approved_ | principal | Version 2 ran its ten approved repair turns and CI never turned green |
ready_ | principal | Three consecutive ready pushes failed after approval; the detail names the failing test files |
max_rounds | principal | A non-green round reaches reviewPolicy. |
repeat_finding | principal | A blocking finding is open in two consecutive reviewed rounds |
reappearance | principal | A resolved finding reappears |
confidence | principal | Confidence is absent after one re-ask, or below 50 after the extra turn |
time_budget | principal | Active time exceeds reviewPolicy. |
no_progress | principal | A second attach finds the same unchanged head after findings were delivered again |
repeat_failure | operator | The same normalized mechanical failure twice, with a finished Developer attempt between |
infrastructure | operator | Any error thrown on the controller's path, a reviewer missing twice, a red or conflicting head that stays stalled, a corrupt control record, an unresolvable base |
no_push | operator | Unpushed work survives the one dedicated resume |
sandbox_refused | operator | The Linux sandbox probe refuses on this host |
missing_source | planner | A required source cannot be prepared |
objectives_ | self | Objectives changed while reviewers ran |
ruling_posted | self | A ruling landed while reviewers ran |
brief_superseded | self | A Planner superseded the brief mid-round |
policy_changed | self | Policy changed mid-round |
stale_driver | self | The controller failed to restart itself over its own changed code |
Waits that are not pauses
- A GitHub rate limit waits for the reset and retries the round.
- A host that cannot reach GitHub waits for the connection.
- An agent usage limit pauses with its reset time and resumes by itself.
- A sign-in refusal pauses without spending retry budget.
Automatic recovery
infrastructure and stale_driver pauses resume on the bare command, with no ruling, up to 5 times per task. Past that bound, a ruling is required.
sandbox_refused is never watched or retried. Once the host is repaired, it continues by an ordinary start, with no ruling.
The pause record
At a pause, the controller writes three things:
| Record | Where | Contents |
|---|---|---|
| Held state | control/ | Task, round, judged head, branch, pull request number (null before a pull request), reason, reason-specific detail, agent and model, and a snapshot of infrastructureRetries |
| Escalation record | The control store, keyed escalationId = <task>- | Run identity (runId, pid, host), round, head, branch, pull request, input versions (briefHash, objectivesVersion, rulingOrdinal, policyDigest), reason and detail, evidence (the last round's review context), attemptedRecovery and recipient, and agent |
| Pause comment | On the pull request, or on the task Issue before one exists | The reason, what the person must decide or repair, the ruling marker and the ordinal to exceed, and how to continue (task_resume, or the task run or --resume command) |
Writing the escalation record is best-effort. If it fails, the pause is raised again on the next controller start. The Operator reads all of this as one packet with task_ (see The Operator).
The ruling record
A ruling is a principal-authored comment with <!- on its first line, and an ordinal strictly newer than the one the pause was raised under. vinaya pr rule posts it on a pull request. For a pause with no pull request, the Principal writes the marker on the Issue; the pause comment names it.
| Property | Rule |
|---|---|
| Authentication | The author is checked against principals from the default branch. Issue and pull-request comments count on identical terms. |
| Binding | The resolution's authenticatedBy and authenticatedFrom (<pr>-<k>, or issue-) name the ruling's author and marker, never the caller |
| Choice | Resume or cancel is the caller's command, never derived from the ruling's prose |
| One-time use | The resolution is claimed exclusively and atomically. A second attempt on the same escalationId, with the same or a different decision, is refused as replayed. |
| Other refusals | Stale (no record, or one that cannot be parsed) and wrong target (the recorded pull request differs) |
| Transition | On success, the pause moves from paused to resumed or cancelled, under the same epoch |
| Self-recovery | An automatic-recovery resume is still consumed once, authenticated as driver-self |
| Intent version | The ruling ordinal is part of the review-input manifest, so a new ruling invalidates verdicts bound to the old ordinal. After a pause resolved by a ruling, the manifest's pauseBoundRuling binds the ruling identity, the pause identity and the SHA-256 of that ruling's text. |
Resume
| Path | How |
|---|---|
| The watching controller | Polls. A ruling newer than the ordinal captured at pause time runs the loop again in the --resume shape. |
vinaya dev- | Reads Closes #N and the held pause, and requires a fresh ruling (unless it is automatic recovery within the bound, or sandbox_refused). If the head is unchanged, it re-enters the held round with the ruling as context. If the head moved, the move is accepted, the round counter restarts at the ruling ordinal, and the run goes straight to the gate and reviewers. |
task_resume | Checks the pause, escalation, resolution and driver lock, and requires a ruling. Launches --resume in the background, guarded by a claim keyed by the escalation identity. The ruling is consumed inside the continuation. |
task_resume, pause before any pull request | Consumes the ruling itself, then relaunches through vinaya task run. That continuation does not carry the ruling text to the Developer. |
task_start | Continues a pause whose ruling is posted, by handing it to task_resume's authentication |
A pause raised before its pull request existed is bound to the open pull request on its own branch; the escalation keeps pr: null. An explicit --agent different from the paused run's continues on that agent.
Cancel
vinaya dev-review-loop --cancel <pr> --agent <vendor>The Operator's task_cancel does the same. A cancel:
- authenticates a ruling exactly as resume does, and refuses a wrong-target, stale or replayed resolution;
- checks the escalation's recorded
agent; - stops the in-flight role through the identity-checked shutdown path;
- marks every effect still
startedasuncertain.
A pause is either resumed or cancelled, never both, and never either one twice.
task_cancel outcomes are confirmed, pending (the driver is on another host) and uncertain (an effect was marked uncertain). A repeated cancel reports confirmed again.
Gates
Pauses run no registry check. Their guards are the authentication and one-time-use rules above.
The merge gate respects rulings: a ruling posted after a clean verdict voids that verdict until the reviewers judge again against the new ordinal (review-gate). See Gates.
Configuration
| Key | Effect |
|---|---|
reviewPolicy., reviewPolicy. | When max_rounds and time_budget fire |
principals | Whose rulings authenticate |
VINAYA_, VINAYA_ | Watcher poll (30 s) and automatic-recovery backoff (60 s) |
Fixed, not settings: at most 5 infrastructure retries, at most 2 automatic rate-limit resumes, and the two-round finding rule.
What gets logged
| When | Events |
|---|---|
| Pause raised | handoff · raised; dev_review_loop · stop_, paused, round_ended, journal_ |
| Pause comment posted | An effect sequence, with the same meta.lineage.run as the paused event |
| Resolved | handoff · resolved; dev_review_loop · resumed (by: 'driver' or 'principal') or cancelled (by: 'principal') |
| Automatic retry | dev_review_loop · infrastructure_ |
| Operator request | operation · completed |
Pauses the controller decides that are not round-assessment observations reuse the existing principal_stop condition and principal_item paused reason in their events; they add no new schema value. See Events.