Concepts

An AI coding agent reading this: start at For agents, the roles, commands and rules on one page.

This page defines the vocabulary flywheel is built on — the words the quickstart uses and the rules every command enforces. It is the map; PROTOCOL.md is the territory, and the code is the authority. Where a term names a machine check, the check is real: run the command and it happens.

The core idea in one sentence: the repository is the session. All state — work orders, the event log, the readings, the verdicts — lives in files, never inside a vendor’s session. That is what makes agents swappable and the loop crash-safe: any head, human or model, reads the same files and continues the same work.

The work order (brief)

A work order, called a brief, is one text file that turns a goal into a bounded unit of work. It has a header block of rules and a body that states the goal. A minimal example:

owns: docs/hello.md (new)
needs: none
gate: test -f docs/hello.md
gate: grep -q "Hello" docs/hello.md

# TASK: hello — write the welcome page

The header is what the machine reads. owns: and needs: scope the unit; gate: lines are the contract the gauges re-run. The brief is hashed at dispatch — flywheel run records the SHA-256 of the exact prompt sent — so editing the brief after dispatch is detectable: rule T1 compares the dispatched hash with the current brief, and flywheel verify flags a mismatch. An amended event records a deliberate change of the brief later.

owns:

owns: is the unit’s boundary: the paths this unit may write. An entry is one of three forms, matched the same way at owns-check time:

(new) marks a file the unit is allowed to create. A path that changed outside owns: fails the check: flywheel validate compares every changed path against the unit’s owns: and reports the strays as outside, making the unit fail (exit 5) even when every gate passes. Pre-existing dirty files the unit did not touch are baselined — excused because they were already dirty at dispatch. An outside path whose bytes differ from the base only in line endings or whitespace still fails, but validate labels it with an owns: hint: line and the git checkout <base> -- <paths> that restores it, so a real stray edit stands out.

owns: exists so two workers never fight over one file: units run in parallel only when their owns: sets are disjoint, and the boundary is what makes “you edited a file you were not given” a machine failure instead of a conversation. flywheel run enforces it at dispatch: a unit whose owns: collides with an in-flight task’s is refused (exit 6, rule owns) unless you pass --allow-overlap, and then the dispatched event’s note records the overlap.

gate: and live-gate:

A gate: line is one shell command that must exit 0 on the built tree. It is the machine’s contract for the unit’s claims. The worker’s report is not enough — the lead re-runs every gate with flywheel validate, which records a supervisor reading per gate bound to the tree’s hash. A gate is not “trust me, it works”; it is “here is the command that proves it, re-run on the exact tree.” A brief with no gate: line is refused before it is ever dispatched.

A live-gate: is a gate that runs only in the lead’s verification pass, via flywheel validate <task> --live, never in the worker’s own dispatch or an ordinary validate. It is for a unit whose deliverable is a provider-facing contract — a real API, a real provider — where a mocked gate proves the mock and not the contract. The worker proves what it can with the mocked path; the lead proves the contract against the real thing, and a pass verdict is refused until that live reading exists.

The event log

Everything that happens to a unit is appended, one JSON line at a time, to .flywheel/events.jsonl. The log is append-only: nothing is edited, deleted, or rewritten. State is derived from the log — flywheel state (or the status block in flywheel.md) replays the events and computes where every unit stands, and .flywheel/state.json is a read-only projection of it. If the log and the state ever disagree, the log wins.

That one property does the heavy lifting: it makes the loop crash-safe (a dead session loses nothing, the next head replays the same events), auditable (every command that wrote the log is traceable), and honest (a hand-edited historical line does not silently change the story — rule T1 catches a brief that no longer matches its recorded hash).

The poka-yoke rules

The log is not just a diary; it is checked. flywheel verify (and the enforcing commands themselves) apply transition rules. flywheel verify implements eight in code: T1, T3, T4, T5, T8, R1 (no pass while a blocking review finding is open), W1 (no withdrawal of a live attempt) and P1 (no pass without a complete review panel). flywheel land enforces two more live: T7 (a first-article audit, when audit.first_article is set) and T9 (no landing while the task has untriaged signals). The rest of T1–T10 are still design-only. They are poka-yoke — mistake-proofing, devices that make the wrong action impossible rather than hoping nobody does it. The two a newcomer meets first:

The full rules, and which are still design-only, are in PROTOCOL.md and docs/design/autonomous-shipping.md.

Worktree units

flywheel run <task> --worktree runs the worker in the task’s own git worktree, .flywheel/worktrees/<task>, on branch fw/<task>, so parallel units never share a checkout. --base REF branches a new fw/<task> from REF instead of HEAD. The integration branch is integration.branch in .flywheel/config.json when set, else main (else master); it is where flywheel rebase moves a unit by default.

The review agent

flywheel review <task> --agent runs a review agent that reads the unit’s diff and records findings and a verdict; an open blocker or major finding refuses a pass (rule R1). --panel runs one persona per configured review.panel dimension and prints the verdict matrix (rule P1). --fix is the fix loop: it sends the open blocking findings back to the worker and reviews again.

withdrawn

A withdrawn event takes a plan back: flywheel log --task <id> --kind withdrawn --note "<why>". It is terminal like landed — never offered by flywheel next, holding no owns claims — and a later planned event revives the id. Rule W1 refuses it (exit 6) while an attempt is live.

Health

flywheel controller appends a health event at most once per --health-every (default 5m): how many units are running, stalled and rate-limited. flywheel status --health prints the latest, or reports it STALE (exit 1) when it is older than --stale-after (default 10m): the controller is not recording.

Who does what

The factory runs on separation of duties — each step is a different persona, so the checks do not collapse into one voice:

The one rule that matters above all: the session that inspects is never the session that built. That separation — T4, and the persona boundaries that mirror it — is what makes inspection an independent check instead of a rubber stamp.