Docs

Executing

The loop

The loop takes one task Issue with a frozen brief to a pull request ready for the Principal's merge, or to a pause. A Developer works, two fresh reviewers judge, and the controller decides each round.

Unattended
Two reviewers
Rounds
Resume by re-execution

About the loop

The loop takes one task Issue with a frozen brief to a pull request ready for the Principal's merge, or to a pause. It runs unattended.

One pure decision function holds the whole decision logic. The controller (also called the driver) turns facts from the forge and from each dispatch into observations, calls that function, and acts on the decision it returns. The controller never re-derives a stop condition on its own.

RoleIn the loop
DeveloperOne vendor session, resumed across rounds. Publishes only through the seven controller-run tools.
Code reviewerA fresh dispatch every round, never resumed. Has one tool, read_source, when the round has required sources.
Security reviewerSame as the code reviewer
ControllerPrepares inputs, spawns every agent, holds the forge login, posts round markers and verdicts, decides each round
PrincipalRules on pauses addressed to it

Start a run

Prepares the run (renders and freezes the brief), then runs the loop.

vinaya task run <tranche> <n> [--agent claude|codex] [--background]
vinaya task run --issue <n> [--agent …]

task run wraps the loop in a watcher that waits at a pause instead of exiting (see Resume by re-execution). Without --agent, the run uses dispatch.agent.

For a walk-through, follow Deploy backlog issues or Deploy medium work.

How it works

  1. 1

    Prepare the run

    The controller fast-forwards the checkout, freezes the brief, takes the task lock and checks that enforcement is active. Round 1 creates the task worktree and reserves the task branch.

  2. 2

    Prepare sources

    Each entry in the brief's ## Documentation becomes a manifest entry. Any source that cannot be prepared pauses the run as missing_source.

  3. 3

    Dispatch the Developer

    Round 1 gets a full context packet. Later rounds and resumes get a delta packet.

  4. 4

    Publish through the controller's tools

    The Developer commits, pushes and opens the pull request through seven tools, behind the hooks.

  5. 5

    Accept the Developer's turn result

    The Developer ends every turn with one typed result. The controller alone accepts it.

  6. 6

    Read the mechanical gate

    The controller reads CI, mergeability and the premises for the live head. A red head goes back to the Developer.

  7. 7

    Apply the confidence rule

    From round 2, missing or low confidence gets one more chance, then the run pauses.

  8. 8

    Review

    Fresh Code and Security reviewers judge the pull request on one review-input manifest.

  9. 9

    Assess

    The controller combines the verdicts and decides: publish, revise or pause.

  10. 10

    Publish

    The controller posts the verdicts and a publication comment. The pull request is ready for the Principal's merge.

Each step is described in detail below.

1. Prepare the run

  • The controller fast-forwards a clean default-branch checkout that is only behind the remote tip. It refuses a checkout on another branch, with uncommitted tracked changes, or with local commits the remote lacks, and names the command that clears it.
  • Preparation renders and freezes the brief. Its **Revision:** line records the tip it was rendered from.
  • A one-driver-per-task lock (driver.pid.json) refuses a second concurrent start on the same task.
  • A start-of-run sweep runs in the background and removes finished task folders and worktrees. If it fails, the run does not pause.
  • Before every unattended Developer dispatch, the enforcement-controls check requires the local gate (tracked pre-commit and pre-push hooks, routed and executable) and the adapter's hook files. Readiness runs the check-dispatch-readiness check and verify-dispatch --existing-work.
  • Round 1 creates the task worktree and pushes a commit-free reservation of the task branch. A remote branch whose head is an ancestor of the default branch counts as a fresh start. Any other existing branch enters crash recovery.

2. Prepare sources

Before every Developer dispatch, the controller turns each entry of the brief's ## Documentation into a prepared source. Each one records an identity, a URI, a digest, a delivery mode (injected, retrieval_tool or unavailable) and a freshness policy (pinned or volatile).

SourceHow it is delivered
Repository file or directoryPinned by git object at the brief's revision, delivered injected
Public pageFetched by the controller outside every sandbox. A snapshot is kept under its digest and delivered through fetch_documentation.
Anything elseunavailable, with a typed reason: login_required, other_host, not_a_document, fetch_refused, unreachable, missing_in_repository or unresolvable

Any unavailable source pauses the run as missing_source, addressed to the Planner. The manifest and receipts live in <runtimeDir>/tasks-execution/<task>/control/sources/, outside every worker's writable folders.

3. Dispatch the Developer

  • Round 1 gets a full context packet: the frozen brief and its digest, objectives and rulings, base and head, prepared sources, the capability grant and tool catalog, the terminal-result contract, budgets and stop conditions.
  • Later rounds and resumes get a delta packet, built from the hand-off stored in the loop_state record (unresolved findings by controller id, failing checks) and the prior session id. Conversation memory is never the only source.
  • A chosen model is used only for the Developer's dispatch. Reviewers use the vendor default.

4. Publish through the controller's tools

The controller hosts one MCP server in its own process for the turn, and the agent reaches it through task-tools dev-bridge. The Developer has seven tools and nothing else:

ToolWhat it does
publish_changesCommits under a Type(scope): Description header and pushes the task branch, behind the publication preconditions, the protected-path gate and the pre-push hook
open_pull_requestOpens the pull request (title and body), once
update_pull_request_bodyReplaces the body after validation
refresh_evidenceRebuilds the Evidence block for the current head
read_pull_requestReads state, checks, reviews, body and failed-check log tails
run_checksRuns vinaya check --all for the current head
fetch_documentationReads one public https page and records the delivery against the manifest
  • A change beyond the Surface is recorded, never refused. The controller rewrites the body's AEG:BEYOND-SURFACE block after every body write and every landed push. A protected administrative path passes only when the Surface's in: globs cover it.
  • A pre-push refusal returns the failing check and test lines to the Developer in the same turn.
  • After the turn, the controller publishes nothing itself. It detects unpublished work and asks the same session again, at most twice.
  • The after-turn confinement check re-hashes the control store, the source receipts, other roles' folders and the policy file.

5. Accept the Developer's turn result

Every Developer dispatch ends with one turn result, delivered as native structured output (Claude --json-schema, Codex --output-schema).

statusCarries
completedsummary, confidence (a whole percent), confidenceExplanation, addressedFindingIds, sourceUses, and optionally reportedChecks
blockedA typed blocker
needs_rulingA rulingRequest, limited to the permitted ruling decisions

Only the controller accepts a result. It checks that:

  • finding ids belong to this round's hand-off (R<round>-CR-<n>, R<round>-SEC-<n>), and round 1 names none;
  • a completed result cites every required source, each backed by a controller receipt.

An invalid result gets one bounded correction turn in the same session. A second failure pauses. reportedChecks is context, never evidence.

6. Read the mechanical gate

  • The controller reads the live head and CI from the forge. CI is never re-run locally. The review gate's own check-run is excluded. A failed workflow run makes the head red even if every check-run passed. Queued or unreadable runs read pending.
  • Mergeability (gh pr view --json mergeable) is read before reviewers. CONFLICTING sends the Developer back with the conflicting files, never to a reviewer.
  • A red head sends the Developer back in the same round number. If the head is still red and unchanged after a turn that published nothing, the controller asks the forge to rerun its failed jobs, once per round.
  • Premise re-assertion runs against the live body on each gate read.

7. Confidence rule (round 2 on)

The rule reads this round's newest accepted completed result, before reviewers run:

ConfidenceAction
Absent, first timeAsk the same session once more for its turn result; publish nothing
Absent, already askedPause: confidence
Below 50, extra turn unspentOne more Developer turn, with no reviewers. This extra turn is spent once, ever.
Below 50 againPause: confidence
50 or overDispatch reviewers

Round 1's stated confidence is recorded but the rule ignores it.

8. Fresh Code and Security reviewers on one manifest

  • The controller builds one review-input manifest (protocol version 2): head, base (the merge base with the default branch), brief hash, objectives version, ruling ordinal, policy digest and pauseBoundRuling. It is stored as a manifest record per round in the control store.
  • The controller posts the round marker comment itself: Head:, <!-- aeg:developer:round-<n> -->, the Pause-bound ruling: line and FINDING_IDS:.
  • Each round gets one read-only candidate copy, and each reviewer gets a fresh writable scratch copy per attempt. The pull request's facts are staged as files. Reviewers hold no gh and no forge credential.
  • Both reviewers and the evidence report run together. Each reviewer returns one native review result; nothing is written to disk. A reviewer with no accepted result is retried once, fresh. A second miss pauses as infrastructure.
  • Reviewer prompts carry facts only (objectives, rulings, head, CI, source revision) plus the role's own doctrine. They never carry Developer-written prose.
  • After both return, the controller resolves the manifest again. A mismatch discards both verdicts and pauses as objectives_changed, ruling_posted, brief_superseded or policy_changed.
  • A NOT MET objective whose evidence names only prose is reclassified MET (prose note).

9. Assessment

The controller combines the verdicts. Severities block under the reviewPolicy thresholds, with these adjustments:

  • A finding located in prose is capped at MINOR.
  • A finding on an unchanged file outside the Surface is deferred, in any round.
  • From round 2, a finding on an unchanged line is deferred, except a security finding at or above HIGH.

Deferred findings go to one vinaya/deferred backlog Issue per pull request, at publication.

ExitTriggerDecision
Green (ready)Both verdicts clean, every objective METPublish
ReviseBlocking findings, and none of the stops belowFindings go to the same Developer session; the round advances
EscalatedEither reviewer returns ESCALATEPause: escalation
StalledA finding marked resolved reappearsPause: reappearance
Repeating a findingSame role, same file, same description fingerprint, blocking, open in two consecutive reviewed roundsPause: repeat_finding
Repeating a failureTwo consecutive attempts end on the same normalized mechanical failure, with a finished Developer attempt betweenPause: repeat_failure
CappedA non-green round reaches reviewPolicy.maxRoundsPause: max_rounds
Out of timeActive working time exceeds reviewPolicy.maxTaskMinutesPause: time_budget
Low confidenceThe confidence rule abovePause: confidence

When several apply in one round, the most severe wins, in a fixed order: escalation, then a Principal-owned item, then repeat, then max rounds, then infrastructure. Every applicable reason is still recorded.

Before publishing, [live] Test Plan lines run on the approved head in a fresh checkout, outside any sandbox. A failure withdraws the approval and sends the Developer back as a new round. A line the host cannot run is recorded as unverified in the AEG:LIVE block, and it holds the merge gate like an unticked [principal] item.

Mergeability is read again before publication. A conflict discards the held verdicts and resumes the Developer without advancing the round.

10. Publication

The controller posts, in order: the held code verdict, the held security verdict, and one publication comment (<!-- aeg:loop:published head=<sha> confidence=… -->). Each post goes through the effect executor, so a rerun never posts anything twice. Nothing is posted before green.

A run can also end in a pause (see Pauses and rulings), or by cancel through --cancel or task_cancel on a paused run. Infrastructure failures (a gate error, a dispatch error, any error thrown on the controller's path, a corrupt control record) become a decided pause, infrastructure, never a crash.

Rounds

  • Rounds 1 to maxRounds run. The loop pauses after the last round's review if it is not green. A green round at the cap still publishes.
  • The round number advances only when a reviewed round concludes changes_requested. A red-gate retry and the confidence extra turn keep the same round number.
  • A resume after a ruling with a moved head restarts the counter at the ruling's newest ordinal, so the ruling acts as the round-cap override.

A finding open two rounds

A finding's identity is the reviewer role, the finding's file (line dropped) and a fingerprint of its description (lower-cased, with whitespace and punctuation collapsed). It is never the reviewer's positional id.

  • Only findings treated as blocking count.
  • "Consecutive" means consecutive reviewed rounds. A red gate or a low-confidence turn between them carries the previous keys forward.
  • A round that raises only new blocking findings continues.

Where state is stored

<runtimeDir>/tasks-execution/<task>/
  driver.pid.json     one-driver-per-task lock
  control/            control store: ownership, transitions, loop_state, effects,
                      resolutions, escalations, manifests, pause-state.json, sources/
  sessions/           vendor session and launch records per role+agent
  hooks/              documentation reminder hook files (ergonomics only)
  output/             raw agent output, driver.log
  rounds/<n>/         held verdicts, candidate and scratch copies, developer/ turn results
RecordHolds
loop_stateround, phase, pauseReason, budgets (mechanicalRetries, reviewRounds, infrastructureRetries), heldResult, deliveredFindings, repeatMemory, phaseMs, developerHandoff. Written on every transition.
manifestOne immutable review-input manifest per task and round
effectForge-write identity and status (started, verified, uncertain)
escalation, resolutionSee Pauses and rulings
pause-state.jsonThe held pause (a legacy side file, still read and written)
Forge markersRound markers, the publication comment and verdicts. The round journal is rebuilt from principal-authored comments.

Resume by re-execution

The loop resumes by running again, never by replaying a checkpoint or the Log.

  • The watcher. task run holds the driver lock for its whole life. At a pause on an open pull request it polls every 30 seconds. A merged or closed pull request ends it; a recorded cancel ends it; a Principal ruling newer than the pause runs the loop again in the --resume shape. infrastructure and stale_driver pauses resume without a ruling after a 60-second backoff, up to 5 times.
  • --resume <pr>. Reads Closes #N, the held pause and a fresh Principal ruling, then compares heads. If the head is unchanged, it re-enters the held round and dispatches the Developer with the ruling as context. If the head moved, the move is accepted and the loop goes straight to the gate and reviewers.
  • Budgets survive. Each entry seeds the round, budgets and repeat memory from loop_state, and floors the infrastructure count against pause-state.json. A corrupt record pauses as infrastructure and never resets a budget.
  • A stale controller. If the base moved over the controller's own code, the controller pulls and restarts itself. It pauses as stale_driver only if that fails.
  • A prior launch. The controller decides what to do with a prior Developer launch: live, finished, resume (same session), pause, fresh or none. An orphaned child process is reaped first.

Gates

StepGateRun by
Preparedispatch-readiness, verify-dispatch --existing-work, the enforcement-controls checkController, before every Developer turn
PublishPublication preconditions, protected-path gate, credential scan, then the pre-commit and pre-push hooks with the Ring 0 checksController and Ring 0
Pull requestcloses-n, surface-scope, test-plan, evidence-fresh, body-bare-digits; Ring 0 checked againRing 1
Gate readCI on the current head, mergeability, premise re-assertionController
ReviewersAgent-config scan (securityScan), after-turn confinement checkController
Assessmentreview-gate, bound to the same manifest comparisonRing 1
Before mergeprincipal-test-plan-wait, which holds unticked [principal] and [live] linesRing 1

See Gates for each check.

Configuration

KeyEffect
reviewPolicy.maxRoundsRound cap (default 3)
reviewPolicy.maxTaskMinutesActive time budget (default 180; 0 turns it off)
reviewPolicy.codeReviewThreshold, reviewPolicy.securityThresholdBlocking severities
dispatch.agent, dispatch.timeoutMs, dispatch.reviewerTimeoutMsRuntime and time ceilings
securityScan.commandThe agent-config scan
runtimeDirWhere the controller keeps its files
VINAYA_DEV_REVIEW_LOOP_WATCH_POLL_MS, VINAYA_DEV_REVIEW_LOOP_WATCH_INFRA_BACKOFF_MSWatcher poll (30 s) and infrastructure backoff (60 s)

Fixed, not settings: the confidence threshold of 50, at most 2 publish re-asks, at most 2 stalled gate turns, at most 5 infrastructure retries, at most 2 automatic rate-limit resumes, the two-round finding rule, and the set of agent-config paths the scan watches.

What gets logged

WhenEvents
Run start or resumedev_review_loop · loop_started, resumed (by: driver or principal)
Each roundround_started, gate_result_read (with confidence_value, confidence_unavailable, extra_turn_spent), verdicts_read (with findings, each with policy_treatment), findings_compared, round_ended
Turnsdispatch · dispatched, outcome_received, dispatch_failed; role_attempt · attempted; usage · observed
Stopsstop_condition_met, paused, journal_finalized, cancelled
Livenessdriver_heartbeat every five minutes; driver_exited on every exit (finished, paused, reexec, error, signal)
Forge writeseffect
Recovered hiccupsinfrastructure_retry

Every event the loop emits is validated against its schema before it is written. The input versions ride on reviewer dispatch events. See Events.

What's next