# Goal mode - toward an outcome

Source: https://flow-next.dev/autonomy/goal-mode/

flow --goal works toward an outcome larger than one spec - a port, a migration, a PRD - by measuring a goal spec's criteria, minting the next child spec, and building it through the flow --auto pipeline until the bar is met or a person is needed.

`/flow-next:flow --goal` is for work where you know the outcome and how to check it, but not the set of specs that gets there: porting an app to a new platform or migrating a legacy system to a new stack, moving a large codebase to a new framework version, working a PRD or vision document down into shipped changes, or reaching a release bar of budgets and sign-offs. You name the goal. Each run measures the goal’s criteria, mints the next child spec for the largest gap, and builds it through the ordinary [`flow --auto`](https://flow-next.dev/autonomy/pilot/) pipeline, then measures again, until every criterion is met or something needs you.

Without goal mode, this work runs under a hand-written loop prompt (“mint specs for gaps, keep going until done”), and on long runs the session drifts: workers get ad-hoc briefs instead of the work stage, minted specs skip review, a change merges unreviewed. Goal mode puts the loop in flow itself, so every child goes through the same routing, reviews and gates as a spec you wrote, and it keeps the loop’s memory in the goal spec on disk, so a run that lasts days resumes where it stopped.

## What it’s for

Goal mode fits when you can state the outcome and check it, but the specs that reach it only show up as you measure and build. Four shapes come up most.

### Migration or port

Moving a system to a new language, framework or platform: porting a desktop app to another operating system, moving a legacy estate to a new stack, rebuilding it clean-room from a spec. The old system is the specification that actually runs, so the bar is behaviour. Hand `--goal` the target spec: one your team wrote, or one a modernization tool produced by extracting business rules from the legacy code, with an acceptance test per rule. Anthropic’s [code-modernization plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/code-modernization) for Claude Code is one example: its rebuild path writes a target spec, business-rule cards and an acceptance test per rule, and leaves the logic for a builder like flow-next to implement. Criteria that hold up:

* Every business-rule acceptance test passes, and none was weakened or deleted to get there.
* Old and new give the same output on recorded inputs, plus the edge cases a recorded sample misses (year-end, error paths, boundaries). The comparison is the check.
* The new platform’s build and tests pass, and measured budgets such as start-up time hold.
* What only a person can judge, like platform conventions and feel, is an owner’s sign-off.
* Data migration, often the larger risk and outside the code, gets a criterion of its own or an explicit line in Boundaries.

Each child is one slice: a module moved while the old system keeps running, or a service or capability of the target architecture in a rebuild. It names the rules it covers, so you can follow a rule to its test and its pull request. Put “do not read or edit the legacy source” in Boundaries when the rebuild has to be clean-room. Rules your experts marked wrong or still under discussion become criteria only a person can judge: the run asks, parks that rule, and keeps building the rest. A comparison proves sameness, not correctness, so when the old behaviour turns out to be a bug, keeping or fixing it is a question for you. The goal spec under [Start a goal](https://flow-next.dev/autonomy/goal-mode/#start-a-goal) is a port.

### Version upgrade

Moving the same code to a newer version of its framework, runtime or language, often called an uplift. Most of the work is mechanical, and a dedicated upgrade tool (the code-modernization plugin has an uplift mode) works through it with the smallest diffs: a pilot module first, then batches in dependency order. A small upgrade is one spec. Goal mode fits when the upgrade breaks into many specs, for example an old test framework migrated first, shared libraries that have to build on both versions for a while, then the modules, and when work the tool leaves behind still has to land. The bar is the build and the full test suite green on the new version, with the same outputs as the old version on the same inputs. If most of the code would change anyway, it is a migration.

### A PRD or vision document

Hand `--goal` the document. Capture turns the outcomes it states into checkable criteria; a document with nothing checkable stops and asks you for the bar instead of guessing one. Decisions the document settles go into every child as context, and a child that would reopen one becomes a question for you. When each feature is safe to ship on its own, [opt out](https://flow-next.dev/autonomy/goal-mode/#integration-branch) of the integration branch so features reach the default branch one by one.

### A release bar

Budgets and sign-offs: build time, bundle size, tests green on every target platform, an owner’s approval per area. Each budget is a command and a threshold, and each sign-off is a criterion the owner answers in the goal spec.

**Not a fit:** one cohesive change is an ordinary spec for [flow](https://flow-next.dev/skills/flow/); an outcome you cannot check yet is [chart](https://flow-next.dev/skills/chart/) first; a direction with no end state is [prospect](https://flow-next.dev/skills/prospect/).

## Start a goal

Point `--goal` at what you have. A spec id is used as the goal; anything else is captured as the goal spec first.

```text
/flow-next:flow --goal fn-40                          # an existing goal spec
/flow-next:flow --goal docs/migration/target-spec.md  # a document: captured as the goal, then built toward
/flow-next:flow --goal "the Linux build reaches parity with the macOS app"
```

Children land on the goal’s [integration branch](https://flow-next.dev/autonomy/goal-mode/#integration-branch), and the goal reaches your default branch through one final pull request. Add `--until=merge` to land each child, and at the end the final pull request, without waiting for you (factory mode). Without it, each run ends when a pull request opens, you review and merge it, and the next run carries on from the merged result.

A goal spec is an ordinary spec:

* **Acceptance criteria are the bar.** Each one must be checkable: a command, a test, a benchmark, or a judgment a named person gives. A source with nothing checkable in it stops and asks you for the bar, writing nothing.
* **Boundaries hold what no child may change**, and can carry a budget (“at most 12 child specs”) or a stop date.
* **Goal & Context** states the outcome or links whatever describes it.

```markdown
# Linux port reaches parity with the macOS app


## Goal & Context
Port the app to Linux. Parity reference: the shipped macOS build. Target design: docs/linux-port.md.


## Acceptance Criteria
- **R1:** `make test-linux` passes on Ubuntu 24.04 and Fedora 41 in CI.
- **R2:** Cold start under 800 ms on the reference laptop (`scripts/bench-start --json`, field `cold_ms`).
- **R3:** The owner approves the tray menu, notifications and global shortcuts on GNOME and KDE.


## Boundaries
- The macOS build and its release pipeline stay unchanged.
- At most 12 child specs; stop and ask before more.
```

The goal spec stays **unready**. Naming it on `--goal` is your consent for the whole goal, and an unready goal is never picked up by a plain `flow --auto` and built as one oversized spec.

## What one run does

1. **Guard and resolve.** The same dirty-tree guard as `flow --auto`, then the goal: a spec id, an existing goal captured from the same source, or a new capture.
2. **Drive an open child.** If a child the goal minted is still open, the run drives it through the `flow --auto` hop loop: plan or no-plan, plan review, work, implementation review, QA when configured, the PR, and landing under `--until=merge`. One child is open at a time, so each measurement sees one merged change.
3. **Measure.** With no open child, the run merges the default branch into the integration branch, pushes the merge, and checks every criterion against the integration branch’s head, in a scratch worktree that never touches your checkout. A criterion counts as met only on an observed result, recorded with its commit. A criterion only a person can judge counts only on the answer you wrote in the spec.
4. **Decide.** Every criterion met leads to the final pull request, and `GOAL_MET` once it merges. A stated budget or stop date reached, two merged children in a row that moved no criterion, or every remaining gap waiting on you stops `NEEDS_HUMAN`. Otherwise the run mints.
5. **Mint the next child.** The run picks the gap that does most for the bar, settles an uncertain premise first with a throwaway probe, and captures a child spec sized for one pull request. The child carries the goal’s Boundaries and settled decisions, is added as a dependency of the goal, marked ready, and driven (step 2).

The run never edits the goal’s criteria, Boundaries or budget, never edits a spec it did not mint, and asks you nothing interactively.

## Integration branch

A port, a migration or an upgrade passes through states your default branch should never ship: half the modules moved, the old version gone and the new one not yet green. So by default the children land on an integration branch, `goal/<the goal spec's branch>`, and nothing reaches the default branch until the goal is met.

* **The run creates it** from the default branch the first time it runs and records it in the goal log.
* **Each child branches from it**, and its pull request targets it, so every child is still one reviewed change.
* **It stays current.** Before each measurement the run merges the default branch into it and pushes the merge. It never rebases or force-pushes. A conflict the run cannot resolve inside the goal’s Boundaries becomes an open question.
* **One final pull request.** When every criterion is met, the run reviews the whole integration branch against the default branch with your configured review backend, fixes what the review finds, and opens one pull request to the default branch with the goal log as its body. It opens as a draft when the review left a call for you. It merges only under `--until=merge` on that invocation, or when you merge it; then the run ends `GOAL_MET`.

**Opting out.** When each step is safe to ship on its own, for example a PRD built out feature by feature, say so in the goal spec, usually under Boundaries:

```markdown
- Land each child on main; no integration branch.
```

You can also say it in the message that starts the run. The first run records the choice in the goal log, and later runs follow the log. Opted out, children land on the default branch, the run measures there, and there is no final pull request.

**CI has to run on pull requests into the integration branch.** Many workflows run only on pull requests into the default branch, and then a child’s pull request shows no checks at all. The run will not treat that as green: when a child’s pull request into the integration branch reports no checks while pull requests into the default branch do, it stops `NEEDS_HUMAN` and names the trigger. For GitHub Actions, add the pattern once and it covers every goal:

```yaml
on:
  pull_request:
    branches: [main, "goal/**"]
```

If your checks are required before merging, protect `goal/**` the same way you protect the default branch (a branch ruleset that targets `goal/**` with the same required status checks), so land waits for them on every child.

## The goal log

Every run appends what it did to a `## Goal log` section at the end of the goal spec: where the goal came from, each measurement with its commit, each child minted and why, each child’s outcome, and each open question. It is the run’s only memory: a crashed, compacted or next-day run reads it from disk. The edits ride along on each child’s branch, so the log is in your pull requests too.

```markdown
## Goal log
- 2026-10-20 children land on goal/fn-40-linux-port, created from main at 9f8e7d6.
- 2026-10-20 measured on a1b2c3d: R1 fails (12 Linux test failures, all in tray/), R2 not run (no Linux build), R3 open.
- 2026-10-20 minted fn-41 for R1 (tray backend on libappindicator); probe: a scratch build links on Ubuntu 24.04. fn-41 merged into goal/fn-40-linux-port as #212.
- 2026-10-21 merged main (3 commits) into goal/fn-40-linux-port; measured on d4e5f6a: R1 met; R2 1,140 ms > 800 ms.
- 2026-10-21 open question for the owner: Wayland global shortcuts need the portal (works on GNOME 46+ and KDE 6) or an X11-only fallback. Recommendation: portal. Blocks R3.
- 2026-10-24 measured on b7c8d9e: R1, R2 met; R3 approved by the owner on 2026-10-23. Final review SHIP; final pull request #219 to main.
```

## Where you come in

* **Open questions.** A product choice, a premise no probe can settle, or a criterion only you can judge becomes an entry in the goal log, with the options, their measured cost and a recommendation. Answer by editing the goal spec; the next run quotes your answer with its date and treats it as settled. A question parks only the criterion it blocks; the run keeps working on the others.
* **Pull requests.** Without `--until=merge`, you review and merge each child into the integration branch as it comes, and the final pull request into the default branch at the end.
* **Accepting the goal.** `GOAL_MET` leaves the goal spec open for you to read the log and close it. With an integration branch, the whole log arrives with the final pull request. When children land on the default branch, the entries written after the last merge stay uncommitted in your checkout, since the run never commits to the default branch; commit them with the close.

## Verdicts

Every run ends with the ordinary verdict line; goal mode adds `GOAL_MET` and starts every reason with the goal.

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

`spec=` is the child the run worked on, or the goal when no child was dispatched. Re-invoke on `ADVANCED`; treat `DEFERRED_TO_LAND` as usual (land the child’s or the final pull request, or wait for its merge); stop on `GOAL_MET`, `NEEDS_HUMAN` and `BLOCKED`. `--tick` does one goal action per invocation (a capture, a measurement, a mint, one child hop, the final review with its pull request, or its landing), and `--explain` prints the next goal action without doing it.

## Driving it for days

A goal outlives one session. Re-invoke it from any driver, as in [Driving a loop](https://flow-next.dev/autonomy/driving-a-loop/):

```text
/goal keep running /flow-next:flow --goal fn-40 --until=merge until PILOT_VERDICT=GOAL_MET, or stop on NEEDS_HUMAN or BLOCKED
/loop 30m /flow-next:flow --goal fn-40 --until=merge --tick
```

Pass `--until=merge` on every invocation that should land: landing consent is never stored in the log. Run one goal run per goal at a time; two concurrent runs can mint for the same gap.

## What stays the same

* Children are routed exactly like specs you wrote, under your routing block, review backend, QA gate and strike rules. Nothing is skipped because an agent wrote the spec.
* Nothing new is stored: no new spec fields, no flowctl verbs, no config keys. The goal is a spec, the children are its dependencies, the log is a section of the goal spec, and the integration branch is an ordinary git branch.
* A run without `--goal` loads none of it.

## Related

* [Flow —auto - the build loop](https://flow-next.dev/autonomy/pilot/): the pipeline each child runs through.
* [Going Autonomous](https://flow-next.dev/autonomy/going-autonomous/): where goal mode sits on the dial and what it rests on.
* [Flow](https://flow-next.dev/skills/flow/#goal-mode): flags and the machine contract.
* [Writing specs](https://flow-next.dev/guides/writing-specs/): criteria a reviewer, or a goal run, can check.
