Writing specs
A Flow-Next spec is a durable implementation contract. It should remove ambiguity without turning into pseudocode.
The spec should say what must be true when the work is done, why that matters, and what is out of bounds. The agent should still be free to discover the implementation path inside those boundaries.
Include
Section titled “Include”- Problem and target user
- Business outcome
- Acceptance criteria with stable R-IDs
- Constraints and non-goals
- Edge cases
- API or data contract expectations
- Decision context and trade-offs
- Genuinely-open unknowns, parked under
## Parked unknownsinstead of written up as if they were decided
- File-by-file implementation orders
- File paths and line numbers as spec content - they rot on the first refactor; state the contract (types, signatures, behaviors) instead.
**Files:**/**Touches:**stay a task’s job - Premature abstractions
- Pseudocode
- “Maybe later” features
- Renumbering R-IDs after review
- Restating a standing G-ID from
.flow/criteria.mdas an R-ID - completion review already judges every G-ID against the spec; reference it in prose and write an R only for what this spec adds (Standing Criteria)
Spec authoring loop
Section titled “Spec authoring loop”flowchart LR Draft["Draft intent"] --> Business["Business interview"] Business --> Technical["Technical interview"] Technical --> Ready["Spec ready?"] Ready -->|gaps| Technical Ready -->|ready| Plan["/flow-next:plan"] Plan --> Tasks["Spec + tasks"] Tasks --> PlanReview["/flow-next:plan-review (optional)"] PlanReview -->|gaps| Technical PlanReview -->|ship| Work["/flow-next:work"]
Solo developers can collapse the business and technical passes into one conversation. Teams should keep them distinct so product ownership and engineering ownership are visible in the artifact.
/flow-next:plan produces both the spec (with R-IDs) and the task breakdown sized for one work-loop iteration. /flow-next:plan-review is an optional gate between planning and implementation - see Plan Review for when it earns its cost.
The ready flag
Section titled “The ready flag”The “Spec ready?” diamond above is a real, recorded state since Flow-Next 1.12.0 - not just a judgment you hold in your head. A spec carries a ready boolean (default false) marking it complete enough to hand to an agent:
flowctl spec ready fn-1 # bless the spec (idempotent)flowctl spec unready fn-1 # back to draftflowctl specs # ready specs carry a [ready] badgeReadiness is human-owned, never agent-inferred - the spec is where your judgment concentrates, so blessing it is your call. It is orthogonal to status: a ready spec stays open through planning and work. For tracker-connected repos, readiness projects one-way from the tracker instead (tracker.readyState - see Tracker Sync), so the board is the single place the team blesses work.
The gate is opt-in and invisible until adopted. Once it is in use (any spec marked ready, or tracker.readyState configured), /flow-next:capture and /flow-next:interview offer to mark a finished spec ready, and /flow-next:plan soft-warns before planning a spec that is still draft - warn, never block. The flag is the entry gate for autonomous loop execution: /flow-next:pilot selects ready specs only, so a half-baked draft is never executed unattended - and on tracker-connected repos that makes the board the loop’s control plane (see Readiness as the control plane). It stands alone as backlog hygiene too - flowctl specs shows at a glance which specs are blessed vs still-draft.
ready has a sibling boolean since the no_plan field shipped: flowctl spec set-no-plan fn-1 records the human judgment “this spec is too small to plan” on the spec itself (refused once tasks exist; clear-no-plan undoes it). A spec that is both ready and marked no_plan goes through pilot straight to the no-plan route — one implicit task, no plan or plan-review stage, every other gate unchanged. Same contract as ready: human-owned, lazy on disk, never agent-inferred, and flow-local (never tracker-projected).
Capture first, then sharpen
Section titled “Capture first, then sharpen”Two skills write specs, and using them in the wrong order is the most common way to get a weak one.
/flow-next:capture is the intake valve. It does not want a requirements document, it wants whatever carries the intent: a conversation you just had, a briefing packet, a well-described ticket, research the agent just ran, a prototype, or a chart briefing package. It settles “one spec or several?” at intake with a spec-count proposal, and it tags every acceptance criterion [user], [paraphrase], [inferred], or [strategy:<track>].
/flow-next:interview is a sharpening tool rather than intake. It shines after the spec exists, pointed at what is actually soft:
- Burn down the guesses. Aim it at the tagged lines: “do a business interview with me on this spec regarding anything that has been inferred”. Every question then lands on something the agent guessed rather than something you already stated.
- Pressure-test one requirement. “In-depth technical interview regarding R9” concentrates the whole question machinery on the single criterion that worries you.
- Let it find what you did not think about. A scoped technical pass surfaces edge cases the original material never mentioned, and it works precisely because the settled parts are already tagged as settled.
Capture marks the guesses; interview burns them down. “Interview me on this ticket” skips the step that makes the interview precise.
Push back on the options. Teams treat each question’s proposed options as a multiple-choice test and pick the recommended one, which inverts the tool. The recommendation is a starting position with a stated confidence. When none of the options fit, that is the interview working: answer in your own words, send it to investigate the codebase first, redirect the line of questioning, or park the decision. An interview where you picked the recommended option every time laundered the AI’s guesses into requirements with your name on them.
Skips are not answers. Only an explicit answer, or an explicit “you decide” delegation, resolves a question. Everything skipped parks under ## Open Questions with an owner hint and the agent’s unconfirmed leaning, and a consent checkpoint runs before write-back.
Scope split for teams
Section titled “Scope split for teams”Business pass:
/flow-next:interview fn-1 --scope=businessTechnical pass:
/flow-next:interview fn-1 --scope=technical --strategy --docsBoth passes write to the same spec file. They are states of one evolving artifact, not separate documents.
Product-owner pass
Section titled “Product-owner pass”The business pass should answer:
- Who is affected?
- What behavior must exist?
- What customer or operational outcome defines success?
- What is explicitly not in scope?
- What can the agent infer, and what must remain human-owned?
Good product acceptance criteria are observable:
**R1:** A maintainer can run a single command to generate a PR body from an implemented spec.**R2:** The PR body lists every acceptance criterion and whether it has evidence.**R3:** Deferred review findings appear in the PR body instead of being hidden in agent context.Technical pass
Section titled “Technical pass”The technical pass should add:
- System boundaries and integration points.
- Data model or file format expectations.
- Security, permissions, and migration constraints.
- Test strategy and evidence expectations.
- Known risky files or modules.
Do not write a step-by-step implementation unless the sequence is itself a requirement. Let /flow-next:plan map the spec to the codebase after the spec is stable.
Additional role passes
Section titled “Additional role passes”Business and technical are the two built-in scopes, not the ceiling. Field teams add passes for the roles their organization actually gates on - platform/cloud enablement (deployment topology, IAM and secrets, cost and quota constraints, observability requirements) and quality (test-environment needs, regression surface, release-gate criteria) are the common two.
Two mechanics, composable:
- Standing sections via the scaffold. Copy the bundled template to a repo-root
SPEC.mdand add the sections those roles always fill (## Platform & operations,## Quality gates, …) - every new spec then arrives with the role’s checklist built in, and the guidance prose inside a section is free to rewrite. See Customizing the spec scaffold; the four parsed headings are the only ones that must keep their names. - A focused interview pass per role. Run
/flow-next:interview fn-Nand steer it in conversation - “interview from the platform-engineering angle: deployment, IAM, cost, observability” - or simply discuss the concerns in chat and let the interview’s write-back consent fold them in. R-IDs are append-only and source-tagged, so a platform pass addsR14-R16without disturbing the product criteria, and the coverage table later shows exactly which task satisfies the ops requirement.
The same rule binds every pass: each role states what must be true, not how to build it. An ops criterion like “R14: the service exposes a health endpoint the deploy gate can probe” ages well; “use Terraform module X” belongs in the technical constraints only if it genuinely is a constraint.
Good acceptance criteria
Section titled “Good acceptance criteria”Enumerate error cases inside the criterion. Each behavioral R-ID states its error / invalid-input / boundary handling as sub-clauses in the same bullet - or explicitly records “no error surface beyond X”. A one-line “none” declaration is a complete enumeration; silence is not, and a reviewer can’t tell considered-and-none from forgot. The work stage inherits enumerated cases as required tests, which is exactly where untested error paths otherwise entrench: a persisted regression suite reports “all green” for the cases nobody enumerated. Error cases stay prose sub-clauses - no sub-R-IDs, no extra coverage-table columns. Standing G-IDs keep their own error handling; don’t restate it per spec.
**R1:** Users can create a spec from conversation context.**R2:** Each generated acceptance criterion is source-tagged as [user], [paraphrase], or [inferred].**R3:** The agent shows a read-back before writing files.Briefing evidence from chart
Section titled “Briefing evidence from chart”When a spec is captured from a chart briefing package, keep three provenance lanes separate:
- Chart decision provenance - structural: D-ID, decision type, answer, evidence/assets, supersession, briefing membership (B-ID / cluster key). Cite these as links and references in Goal, Decision context, or Notes - e.g. “Storage approach settled in
fn-140.D2(probe); see briefing B1.” - Acceptance-criterion author provenance - trailing tags
[user] | [paraphrase] | [inferred] | [strategy:<track>]on criteria that capture or interview newly write. These answer who grounded this criterion, not how strong the chart evidence was. A criterion derived from a resolved unattended D-ID is not automatically[user]. - Verified-versus-inferred technical fact grammar - not used on chart facts or decisions. Do not invent trailing
[verified]/[inferred]labels for D-ID evidence.
D-ID evidence never receives acceptance-criterion source tags. Capture and interview tag only the criteria they author; they never retag an earlier pass.
Spec-as-PR
Section titled “Spec-as-PR”For high-impact changes, open a PR containing only .flow/specs/<id>.md and review the spec before code lands. That moves the high-leverage discussion upstream.
flowchart LR Branch["feature branch"] --> Spec["Spec-only PR"] Spec --> Product["Product review"] Spec --> Tech["Technical review"] Product --> Frozen["Frozen R-IDs"] Tech --> Frozen Frozen --> Implementation["Implementation PR"]
Reach for it when a spec crosses team boundaries, user-facing behavior, permissions, data migrations, billing, enterprise controls, or production risk. Reviewing a spec-only PR is much cheaper than reviewing a finished implementation that encoded the wrong product decision. Once that PR is approved, implementation has a frozen contract.
Constraint ledger
Section titled “Constraint ledger”For team specs, keep constraints visible instead of burying them in prose:
| Constraint | Example |
|---|---|
| Product boundary | ”Do not add billing plan management.” |
| Technical boundary | ”No database migration in this spec.” |
| Compatibility | ”Existing config files continue to load.” |
| Operational | ”Failure must leave .flow/ state valid.” |
| Security | ”No secrets are written to receipts.” |
The ledger keeps the agent from optimizing away a constraint a human assumed was obvious.
Decision records
Section titled “Decision records”Record a load-bearing decision when it is hard to reverse, surprising without context, and represents a real trade-off. R-IDs are frozen after review: new criteria append, removed criteria leave gaps.
The pre-planning checklist
Section titled “The pre-planning checklist”Run the checklist:
- Every requirement has an R-ID.
- Non-goals are explicit.
- Ambiguous words like “fast”, “simple”, “secure”, or “nice” are translated into observable criteria.
- Product assumptions are marked as product assumptions, not engineering facts.
- Technical constraints are constraints, not preferences.
- The spec can be reviewed by someone who never saw the original chat.
Project-specific spec shapes (extra sections, house style for criteria, the headings flowctl parses) are in Customizing the spec scaffold.