# Flow --auto - the build loop

Source: https://flow-next.dev/autonomy/pilot/

How the unattended build driver works - the two shapes, what one run does hop after hop, the rails it keeps, the verdict grammar, backlog mode, and migrating from the retired pilot command and from Ralph.

[`/flow-next:flow --auto`](https://flow-next.dev/skills/flow/#flow-auto) is the unattended build driver. One invocation selects one ready spec and carries it to a pull request, hop after hop, and ends with one terminal `PILOT_VERDICT` line. It classifies every hop from the same routing references the attended [flow](https://flow-next.dev/skills/flow/) conductor reads, so the route `--explain` shows is the route the unattended run takes. By default it stops before merge. Add [`--until=merge`](https://flow-next.dev/skills/flow/#continue-through-merge) to continue through land for the selected item.

Build and merge authority are separate. The spec content, its dependencies, and the [`ready` flag](https://flow-next.dev/guides/writing-specs/#the-ready-flag) you set are the consent boundary. `--auto` accepts no intent, path, branch, or free text; the only positional argument is a spec id. [Goal mode](https://flow-next.dev/autonomy/goal-mode/) (`--goal`) is the exception: it takes a spec id or any source, and the goal you name is its consent. It executes the mechanical pipeline and never prompts interactively. In ready mode an ambiguity that needs a person ends the run with `NEEDS_HUMAN`; in [backlog mode](https://flow-next.dev/autonomy/pilot/#backlog-mode) the run parks the item behind an async question and reports `ASKED`.

**Chart is outside the build loop.** Optional [`/flow-next:chart`](https://flow-next.dev/skills/chart/) is pre-capture discovery and is never a stage of `flow --auto`. Attended chart decisions (`prototype`, `interview`, attended `task`) terminate `CHART_VERDICT=NEEDS_HUMAN` under unattended drivers; they never self-resolve. Get the effort briefed, captured, and ready before the run selects it.

Flags and the machine contract are on the [skill reference](https://flow-next.dev/skills/flow/#flow-auto). How to run it under a host loop is [Driving a loop](https://flow-next.dev/autonomy/driving-a-loop/). The `/flow-next:pilot` command was removed in 6.1.0, and 7.0 removed the hidden pilot and interview skill stubs that kept the old names working; [Migrating from pilot](https://flow-next.dev/autonomy/pilot/#migrating-from-pilot) maps its arguments.

## The two shapes

| Shape                                       | What one invocation does                                                                        | Fits                                                                                   |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `/flow-next:flow --auto [<spec-id>]`        | Hops until a terminal: the PR exists, deferred to land, asked, blocked, needs human, or no work | A host with stable long sessions; run it again for the next item                       |
| `/flow-next:flow --auto --tick [<spec-id>]` | Exactly one hop, then stop; what a pilot tick was                                               | A host `/loop` or `/goal` that owns repetition, or a host without stable long sessions |

Both shapes end with the same terminal line, so a driver that parsed pilot’s verdict parses these unchanged. A long-horizon run names every dispatched stage joined by `+` and carries the last hop’s verdict; a `--tick` run names one stage.

## What one run does

1. **Select.** The first `open` + `ready` spec whose `depends_on_epics` are all satisfied (`done`, or a chain parent that is open with every task done and its branch on origin, as `flowctl spec chain` reports) and that carries no other-actor task claims, or the spec named on the command line if it passes the same predicate. Selection runs once per run and fixes the item. A spec selected through its chain parent is built as a chain layer: its branch forks from the parent’s tip, its PR targets the parent’s branch, and the verdict reason begins `chained on <parent-id>;`. Two open parents, or a sibling already chained on the same parent, park the spec with a reason and no strike ([chains and stacks](https://flow-next.dev/skills/make-pr/#dependent-specs-chains-and-stacks)).
2. **Classify.** Read the routing references: `route-matrix.md` for the spec-state row, `plan-vs-no-plan.md` for a ready zero-task spec with no recorded route, and `gate-selection.md` for the review, QA, and completion-review gates. The build stage set is `{plan, plan-review, work, qa, make-pr}`, with `qa` only when gate-selection selected it; `--until=merge` adds the scoped `land` stage. The run echoes the row and the gate section the stage came from.
3. **Dispatch.** Invoke exactly one stage skill ([`plan`](https://flow-next.dev/skills/plan/), [`plan-review`](https://flow-next.dev/skills/plan-review/), [`work`](https://flow-next.dev/skills/work/), [`qa`](https://flow-next.dev/skills/qa/), [`make-pr`](https://flow-next.dev/skills/make-pr/)) with `mode:autonomous`. The run never re-implements their logic.
4. **Verify.** Re-read observed state: flowctl status fields and task transitions, the fresh `qa_verdict` receipt for QA, and a gh-confirmed OPEN PR URL for make-pr. Narration from the stage skill never counts.
5. **Record.** Write the receipts, echo the evidence block and the `stage:` line into the transcript, and update the strikes ledger.
6. **Re-classify.** An `ADVANCED` hop on a stage other than `make-pr` re-runs the dirty-tree guard and returns to step 2 for the same spec. With `--until=merge`, make-pr advances to land and the long run may continue through land progress and waits. Existing blockers still stop it. Under `--tick` the run stops after step 5.

Sub-skills run autonomously. The `mode:autonomous` token (plus `FLOW_AUTONOMOUS=1` for process-level drivers) suppresses questions: the run never asks, decides from evidence, and stops only for a call only a human can make or an irreversible action. Work branches deterministically from the spec’s `branch_name`, and make-pr hard-errors instead of prompting. Before the terminal line the run prints a `Decisions:` list, each default it chose, finding it declined and review it skipped on your behalf, with the evidence behind it, and the same list goes into the pull request body.

**Review loops until SHIP.** With nobody there to decide what is left, an unattended run fixes, re-reviews the fixes, and repeats until the reviewer returns SHIP, with flowctl’s round cap and stall check as the backstop (an `ESCALATE:` from either is a stop). Each declined hardening, scope-creep or pre-existing finding gets one `Declined #<n>: <reason>` line in the fix commit. When the reviewer keeps only findings like that, all below Major, the run may end the loop itself and records each disagreement, with both sides’ reasons, in the Decisions list and the pull request. A finding that shows a stated requirement broken is always fixed. The verdict stays the reviewer’s.

**The no-plan route is recorded spec state.** A ready zero-task spec with no recorded route is classified from `plan-vs-no-plan.md`; the run records the answer on the spec with `flowctl spec set-no-plan` or `clear-no-plan` before any task is created and echoes the deciding signal (`route: direct - <signal absent>` or `route: plan - <signal>`). There is no `--no-plan` flag; the run never infers consent from a flag. Existing planned tasks keep their route.

A later run may resume the same actor’s sole implicit execution owner only after positive evidence that the previous run has ended. An active run or uncertain ownership stays `NEEDS_HUMAN`; the direct route never authorizes stealing a claim. Since 5.5.0 that evidence check is the only way back in: a same-actor `flowctl start` on an `in_progress` task refuses with a typed error, and the run passes `--reclaim` only after the check, never on its own.

The `plan` and `plan-review` stages do not require the default branch. They probe the current branch for an open PR and plan in place when none exists, so a one-worktree-per-spec setup composes with the full loop.

## The rails it keeps

The rails:

* **The consent boundary.** Only `ready` specs are selected; the run never sets the flag.
* **The dirty-tree refusal.** A tree dirty outside `.flow/` at run start, or after any hop, ends the run with `NEEDS_HUMAN` and leaves state untouched. No cleanup, no claim reset, no strike.
* **The two-strike ledger.** A spec that fails to advance on two healthy hops is taken out of selection (`flowctl spec unready`); see [Don’t-thrash guard](https://flow-next.dev/autonomy/pilot/#dont-thrash-guard).
* **The all-done PR probe.** Before an all-done spec is routed, the run reads PR state once through `gh` and evaluates the branch’s PRs in order. An open PR defers the spec to land by default, or enters scoped landing with `--until=merge`. No PR routes on through QA to make-pr. A merged PR outranks any historical closed PR on the branch; the run compares the branch head with the head of the most recently merged PR, and additional commits beyond that merged head route to make-pr, while a branch head equal to it is `NEEDS_HUMAN` (the spec is still open with nothing new). A closed-without-merge PR is `NEEDS_HUMAN` only when the branch has neither an open nor a merged PR, because the run never silently reopens human-rejected work.
* **The merge-consent boundary.** The default terminus is `make-pr`. `--until=merge` authorizes land for the selected item’s pull request; land retains every merge gate. make-pr already closed the spec on the branch, so land writes nothing to the repository after the merge. A closed spec with an open pull request stays eligible for landing, a closed spec whose pull request is merged ends the run, and a closed spec with no pull request is `NEEDS_HUMAN`. Releases stay outside land.
* **The decision log.** In backlog mode, `flowctl pilot-log` appends one row per dispatched stage under `.flow/pilot-runs/`.
* **Ready PRs unless something is open.** The pull request opens ready for review. A human call that blocks nothing else (refreshing a frozen fixture, a requirement only CI can prove) goes into the pull request as an open item, and then the pull request opens as a draft; the run finishes the rest instead of stopping. A reviewer’s question that blocks nothing else is handled the same way.
* **Calls it can make when it holds the merge.** With `--until=merge` the run makes a human-type call itself when it is reversible, inside the spec and backed by evidence (updating a snapshot or golden file the change legitimately altered), and records it in the Decisions list. A call that is irreversible, a product choice the spec does not settle, or one that makes merging unsafe stops the run `NEEDS_HUMAN` before the merge. Land never marks such a run’s draft pull request ready; it stops `NEEDS_HUMAN` and names the open items.
* **Stops instead of guesses (6.1.0).** One read-only `flowctl pilot snapshot` call supplies the guards, config, candidates, and PR observations for the hop. A missing or unreadable snapshot, an unresolved scope, or a failed PR observation ends the run `NEEDS_HUMAN` with the error. A spec named by its tracker key resolves through the same lookup as every other command. When every remaining task is blocked or escalated, the run stops `NEEDS_HUMAN` with their reasons before dispatch and records no strike. Branch setup uses the resolved default base and stops on a git failure. A per-task review override stays explicit, and a review receipt whose verdict or counts contradict the review it records is refused.

## The verdict grammar

The `/goal` validator is transcript-blind. It reads conversation output and never runs tools. Every hop therefore echoes its verification evidence into the transcript, and every run ends with exactly one terminal line, the last line of output with nothing after it:

```text
PILOT_VERDICT=<ADVANCED|NO_WORK|DEFERRED_TO_LAND|BLOCKED|NEEDS_HUMAN> spec=<id> stage=<stage> reason="<one line>"
```

Backlog mode adds `ASKED`; [goal mode](https://flow-next.dev/autonomy/goal-mode/#verdicts) adds `GOAL_MET` and starts every reason with its goal.

`stage=` names every dispatched stage in order joined by `+`; the reason names the last hop’s outcome. A `--tick` run names one stage. A long-horizon run that carried a spec from work through QA to its PR ends like this:

```text
PILOT_VERDICT=ADVANCED spec=fn-12 stage=work+qa+make-pr reason="make-pr: open PR <url>"
```

| Verdict            | Meaning                                                                                                                                                                                   | Continues the loop?                                    |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `ADVANCED`         | The last hop advanced the spec; make-pr confirms an open PR, while land evidence distinguishes progress from completed landing                                                            | Yes, until the chosen destination                      |
| `NO_WORK`          | No open, ready spec with satisfied dependencies and no foreign claim; also the `--explain` terminal in ready mode                                                                         | No                                                     |
| `DEFERRED_TO_LAND` | Default: remaining candidates have open PRs for land. With a merge destination: a landing tick stopped on an external wait                                                                | No; inspect the reported land evidence before resuming |
| `BLOCKED`          | A healthy hop advanced nothing (strike 1/2 leaves the spec ready; strike 2/2 unreadies it and names `flowctl pilot strikes clear <id>`), or a backlog dep wait                            | No                                                     |
| `NEEDS_HUMAN`      | Crash-class: a stage crashed or asked for judgment, a dirty tree, a gh probe failure, a branch inconsistency, a closed-unmerged PR, a stale claim, or a review that exits `NOT_RETRYABLE` | No                                                     |
| `ASKED`            | Backlog mode only: the item is parked behind an open async question                                                                                                                       | No; the next invocation selects the next item          |
| `TRIAGED`          | Backlog mode under `--explain` only: the diagnostic classification line; never a live terminal                                                                                            | No                                                     |

Terminal names are preserved. A merge-destination run also echoes the original `LAND_VERDICT`, selected PR state, and merge commit. `ADVANCED` alone never proves a merge. Long-running landing waits at the driver’s cadence; those waits consume no pilot strikes.

## Don’t-thrash guard

A spec that fails to advance on two healthy hops is taken out of selection. The run clears its `ready` flag (`flowctl spec unready`) and the `BLOCKED` verdict carries the reason and the recovery command. Strikes live in a ledger under the git common dir, shared across worktrees and never committable; `flowctl pilot strikes list` shows what is struck and `flowctl pilot strikes clear <spec-id>` (or `clear --all`) resets it. Re-blessing the spec on a local repo clears its strikes on the next selection, which is an explicit human reset (`flowctl spec ready`). The finite stage set bounds a spec that advances; the two-strike rule bounds one that does not.

## Readiness as the control plane

`flow --auto` consumes exactly one gate: the [`ready` flag](https://flow-next.dev/guides/writing-specs/#the-ready-flag). Where that flag comes from depends on your setup, and it changes how you steer a running loop.

**Local repos (no tracker, or `tracker.readyState` unset).** `flowctl spec ready` / `unready` is authoritative. You bless work on the command line; the two-strike `unready` sticks until you re-bless the same way, and a re-blessed spec clears its strikes on the next selection.

**Tracker-connected repos (`tracker.readyState` configured).** The board is the control plane. The [readiness projection](https://flow-next.dev/integrations/tracker-operations/#readiness-projection) pulls one-way from the tracker: a Linear issue in the configured workflow state (or a GitHub / GitLab issue carrying the configured label, or a Jira issue in the configured workflow status) means `ready=true`; anything else means `ready=false`. Move an issue **into** that state to feed the loop work, **out** to starve it. Local `spec ready` writes are overwritten on the next pull, so bless on the board.

One interplay matters when driving against a board. The two-strike `spec unready` is a **local** write, advisory until the board reflects it, and the next tracker pull re-readies the spec. **That projection-set ready never clears the strike.** A board echo re-grants readiness with nobody acting, and a deliberate move back into the ready lane is indistinguishable from that echo in every durable artifact, so no board move can serve as the re-bless. When the run strikes a spec out (`BLOCKED … strike 2/2, spec unreadied`), move the issue **out** of the ready state to keep the board honest, fix whatever blocked it, and clear the strike with `flowctl pilot strikes clear <spec-id>`. The verdict line names the command. Clearing a strike never changes readiness. Strikes are driver state, the board owns readiness.

This keeps a clean division of labor. The board decides *what* the loop may touch; `flow --auto` decides *how far* each run advances it; verdicts report back in the transcript, and the [tracker-sync lifecycle events](https://flow-next.dev/integrations/tracker-sync/) mirror progress onto the issue so the board view stays current while the loop runs.

## Resume from disk

Every hop ends with the same receipts, evidence echo, and ledger write a pilot tick ended with. A run that dies mid-way (crash, kill, session limit) leaves exactly what a dead tick leaves: committed receipts, a ledger entry, a branch. The next invocation classifies from disk and picks up at the stage the state names; nothing is resumed from transcript. Check an idle dispatched agent through its commits, receipts, and status fields; a resume message restarts it, so a merely slow agent becomes two runs.

## `--explain` under `--auto`

`/flow-next:flow --auto --explain [<spec-id>]` (with `--dry-run` accepted as its one-release alias) stops after classification. It prints the selected spec, the classified stage with its routing row and gate section, the review backend, task counts, the consulted status fields, the resolved zero-task route as would-record with its signal, the PR probe result, skipped candidates, would-clear ledger entries, and `chain=<off|on>`. It writes nothing under `.flow/`, creates no ledger file, checks out no branch, and dispatches nothing. Its terminal line in ready mode:

```text
PILOT_VERDICT=NO_WORK spec=<id> stage=<stage> reason="dry-run: classification only, nothing dispatched"
```

In backlog mode an explain run is inspection-only as well. It skips the tracker-sync dispatches (`reconcile`, `list-open`, `list-comments`, `list-relations`), selects from the flow-side `ready --all` facts alone, triages the picked item, and stops before any routing with the diagnostic line:

```text
PILOT_VERDICT=TRIAGED spec=<id> stage=triage reason="dry-run: classified <class>, nothing dispatched or parked"
```

A `/loop` or `/goal` driver never runs `--explain`.

## Backlog mode

By default the consent boundary sits *before* the loop. `flow --auto` picks only from the **already-ready** queue and assumes specs are triaged, dep-clear, and unambiguous. Everything in front of that gate is manual prompting: enumerating the whole open backlog, triaging raw items, sequencing deps, deciding what’s next, and unblocking the things that need a person.

**Backlog mode** (`pilot.autonomy=backlog`, default off; the key keeps its `pilot.` spelling) makes `flow --auto` a standing **floor scheduler for the entire open backlog**:

```bash
flowctl config set pilot.autonomy backlog   # or per-run: /flow-next:flow --auto --backlog
```

Only the literal `backlog` activates it; any other value (`ready`, `null`, a bool `true`, a typo) leaves the run in ready mode byte-for-byte. Each run then:

1. **Enumerates the full open set:** flow specs (`flowctl ready --all`) **plus** normalized tracker issues from the `list-open` read, filtered to the exact configured `tracker.readyState`. A pull-before-scan (`reconcile`) runs first, so a board move is reflected on the next run.
2. **Orders tracker dependencies:** for each tracker candidate, the `list-relations` read supplies directed dependency rows and fails closed rather than sorting a truncated graph.
3. **Selects the top dep-ordered actionable item** across both sources, skipping anything already parked behind an open question.
4. **Triages it** with an *agentic* read of the spec, never a deterministic score: **workable** (ready signal + complete spec) flows into the normal hop loop; **dep-unsatisfied** sequences behind the blocker (`BLOCKED`); **ready-but-thin** is kicked back with an async question (`ASKED`).
5. **Drives that one item to its terminal, or parks it.** In long-horizon mode a workable item is carried hop after hop to its PR and the run stops; under `--tick` it advances one stage. Either way the run handles one item, and the next invocation selects the next item.

This is the one place the consent boundary moves, from *before* the loop to **inside the loop, on block**. When the run cannot safely proceed it **surfaces a precise async question** rather than stalling, writes it into the spec’s `## Open Questions` section, and projects the text through the tracker-sync `question` wrapper. It then parks the item (`ASKED`) and stops. A human answers in the spec or tracker; a later run re-triages and proceeds. It never prompts interactively.

The load-bearing boundaries hold unchanged:

* **Never authors a spec.** A thin or missing spec is surfaced as a *“run [`/flow-next:capture`](https://flow-next.dev/skills/capture/) or [`/flow-next:refine`](https://flow-next.dev/skills/refine/)”* gap. It may fill an obvious blank in an *existing* spec, never write one from a bare ticket.
* **Never sets the `ready` flag.** Promotion is the human’s board act; un-promoted backlog items are skipped **silently**, never worked and never nagged. The agent’s spec-read can only *withhold* by kicking a promoted-but-thin item back with a question, never *force* work.
* **No implied merge authority.** Backlog mode alone never grants landing permission. `--until=merge` adds land for the one selected item under its existing gates.
* **One item per run.** Selection sets exactly one subject; a selection that yields more is `NEEDS_HUMAN`.

**Full-auto by default, with an optional force-gate.** Workable, dep-clear, unambiguous items advance with no pre-gate. For extra caution you can name classes that must be **force-surfaced** as a question *before* action, even when otherwise workable, via the sibling config key `pilot.gateClasses` (default `[]`, which gates nothing):

```bash
flowctl config set pilot.gateClasses risky          # a single class
# or a JSON array for several: ["risky","prod-config"]
```

Whether a selected item belongs to a gate class is the agent’s read of the item, like triage and never a score.

Backlog mode is a leftward extension of the same driver: one loop target, one [verdict grammar](https://flow-next.dev/autonomy/pilot/#the-verdict-grammar) with `ASKED` added, one mental model. It manages the *existing* open backlog rather than inventing work the way [prospect](https://flow-next.dev/skills/prospect/) does. A per-run **decision log** (`flowctl pilot-log`, stored under `.flow/pilot-runs/`) records one row per dispatched stage plus host-reported token cost on the last row, which gives you the factory readout: how many items moved with no question, with one async answer, or parked, and cost per change. With the gate off, `flow --auto` behaves byte-for-byte as documented above.

## Two drivers, never nested

Attended `flow` refuses under every autonomy marker (`FLOW_AUTONOMOUS`, `AUTONOMOUS=1`, a `mode:autonomous` token) and stops before any read with this line:

```text
NEEDS_HUMAN: /flow-next:flow is attended - run /flow-next:flow --auto for unattended runs
```

`flow --auto` has no marker refusal, because it sets `FLOW_AUTONOMOUS` and `mode:autonomous` itself for the stages it dispatches. Attended flow and `flow --auto` are two drivers for the same pipeline and are never nested. Flow may invoke land only as its authorized, scoped landing stage. Recursive drivers remain forbidden. Default `DEFERRED_TO_LAND` behavior is unchanged.

## Live QA

`pipeline.qa=auto` now takes effect unattended. `flow --auto` reads the key through the same gate-selection reference attended flow reads: `on` runs [`/flow-next:qa`](https://flow-next.dev/skills/qa/) at all-tasks-done on every spec, `auto` runs it when the acceptance describes UI behaviour on a drivable surface and a target can be started, and `off` leaves QA to a user invocation. A skipped `auto` hop records one line in the evidence and the route advances to make-pr:

```text
stage: qa - skipped(config: pipeline.qa=auto: no drivable surface)
```

QA never hard-blocks the loop; `NEEDS_WORK` and `BLOCKED` outcomes advance to the pull request with their findings.

`pipeline.chainStages` was removed in 7.0. Under `--tick`, make-pr runs on the next tick; in long-horizon mode the hop loop already runs make-pr as the next hop. flowctl ignores a leftover key with a one-line note; delete it from `.flow/config.json`.

## Migrating from pilot

The `/flow-next:pilot` command was removed in 6.1.0. Replace it with `/flow-next:flow --auto --tick` in `/loop` and `/goal` recipes; [Driving a loop](https://flow-next.dev/autonomy/driving-a-loop/) has the replacements. Every pilot argument has a place:

| Pilot argument                                      | Flow argument                                         |
| --------------------------------------------------- | ----------------------------------------------------- |
| `--spec <id>` or `--spec=<id>`                      | the positional `<id>`                                 |
| `--backlog`, or pilot’s old `--auto` backlog switch | `--backlog`                                           |
| `--dry-run`                                         | `--explain` (`flow --auto` still accepts `--dry-run`) |
| `--review=<backend>`, `--depth=<level>`             | passed through unchanged                              |

Config keys (`pilot.autonomy`, `pilot.gateClasses`), the flowctl verbs (`pilot strikes`, `pilot-log`, `pilot snapshot`), the ledger path, `.flow/pilot-runs/`, and the `PILOT_VERDICT` name keep their spelling.

## Migrating from Ralph

Ralph, the repo-local shell harness, was removed in 7.0; `flow --auto` is the one unattended mode. If you still run Ralph, pin flow-next 6.7.x. Otherwise delete `scripts/ralph/` and any `ralph-guard` hook entries from your project settings, and run `/flow-next:flow --auto` (with `--until=merge` or `--tick`) where your Ralph loop ran.
