Skip to content

Flow-Next in regulated environments

Teams in regulated environments (GxP, medical devices, finance) have to show what was required, what was built, how it was tested and reviewed, and who approved it. A normal Flow-Next run already produces most of that: numbered acceptance criteria (R-IDs), tests named after them, review receipts with findings, QA receipts with captures, and pull requests. Three opt-in pieces make that record durable and mechanical, so it does not depend on an agent copying files or describing its own work.

PieceSet it withWhat it prevents
Committed development evidenceflowctl config set evidence.persist onReview and QA evidence being lost or overwritten between rounds
Spec approval by pull requestflowctl config set spec.approval prflow --auto implementing a spec nobody approved
Traceability matrixflowctl trace <spec-id>A traceability record that is only the agent’s own account of its work

Each one works without the others, and none of them changes anything until you set it. With the default config, every prompt, receipt, file and routing decision is the same as before.

Run these once in the repository, then commit .flow/config.json:

Terminal window
flowctl config set evidence.persist on # committed evidence per spec
flowctl config set spec.approval pr # a spec is ready only after its PR is approved
flowctl config set pipeline.qa on # live QA for every spec (or auto)
# optional: keep evidence somewhere else in the repo (default .flow/evidence)
flowctl config set evidence.dir docs/evidence

Put the criteria every change must meet in .flow/criteria.md, one bullet per criterion. Completion review judges each one against the whole implementation and records the result in its receipt:

- **G1:** Every acceptance criterion has a test whose name carries its R-ID.
- **G2:** Every user-facing change has live QA evidence or a recorded reason it has none.
- **G3:** No change edits the audit-log schema without a migration and a test for it.

Then set up identities and branch protection on your forge:

  • A separate agent identity. Run the agent under its own GitHub account or app, never under a person’s account, so its commits and pull requests are attributable and it cannot approve its own work.
  • Branch protection on the default branch. Require at least one approving review from a human (CODEOWNERS for .flow/specs/ if specs need a named approver), and dismiss stale approvals on new commits.

Flow-Next reads the approval the forge reports and records it. It does not decide who may approve; the forge enforces that.

With evidence.persist on, flowctl writes a copy of each record into .flow/evidence/<spec-id>/ at the moment it writes the record, and stages it so it travels in the next commit. The agent copies nothing.

.flow/evidence/fn-1-reorder-alert/
reviews/ one file per review round: plan, implementation and completion
qa/ each QA round's receipt and the captures behind it
tasks/ each completed task's recorded test evidence and summary
tests/ each task's test runner report (JUnit XML gives every test's result)
approval.json the spec approval (with spec.approval pr)
run.json flow-next version, host, each review's backend and model,
each task's implementing model ("unknown" when not known)

A later review round never overwrites an earlier one, so the folder shows the whole review history, including the rounds that asked for changes. Evidence from a round lands after the round finishes, so no reviewer is asked to judge evidence for a round that is still running. An evidence folder outside the repository or behind a symlink is refused, and a write that fails fails the stage that produced the record instead of reporting success.

The test runner’s own report is the strongest test evidence, so Flow-Next asks for it. When a task starts, flowctl start tells the agent where to have the runner write a JUnit XML report (pytest --junitxml, the vitest or jest junit reporter, bun test --reporter=junit, gotestsum for Go), and flowctl done commits it with the task. Put the R-ID a test covers in its name (test_r7_... or R7: ...); the matrix then shows each test and its own result instead of the agent’s summary of the run.

QA captures are images, so committing them grows the repository. That is the trade a team makes when it turns persistence on.

With spec.approval pr, a spec is ready for implementation only after its own pull request merges with at least one approving review. Capture, plan and refine end by opening the spec as a pull request from the branch spec/<spec-id> instead of marking it ready. Until it merges:

  • flowctl spec ready and flowctl start refuse the spec and tell the agent how to open the spec PR.
  • flow --auto does not select it and never starts implementation.

After the PR merges with an approval, the next flow --auto run (or flowctl start) records the approvers, the merge identity and their times with the spec, writes them to the evidence folder, and marks the spec ready. Nobody marks it ready by hand. A spec PR closed without merging, a merge without an approving review, or a forge Flow-Next cannot reach leaves the spec not ready, and the reason is reported. An invalid spec.approval value stops every gated command rather than quietly falling back to local.

Flow-Next’s QA is the development stage. Your QS is the validation stage. They do different jobs:

Development QA (Flow-Next)Validation (your QS)
Who runs itThe agent, during the buildYour testers and quality function
What it checksThe spec’s acceptance criteria, live in the running app and in R-ID-named testsThe validation plan, against the intended use and risk
What it leavesReceipts, captures, the traceability matrixSigned protocols and reports in your document control
What it decidesWhether the change is ready for human reviewWhether the system is released

Development evidence feeds the validation stage: the tester starts from a matrix that already links each requirement to its tests, QA captures and review findings, and spends their time on the risk the matrix cannot cover.

flowctl trace <spec-id> reads the spec, the task evidence, the evidence folder and the forge, and prints one row per acceptance criterion: the tests that cover it and each test’s result, its QA evidence, the review findings that name it, and the spec and implementation approvals. Pick Markdown (default), --format csv for a spreadsheet, or --json.

A gap check runs every time. Every criterion must have evidence, and every piece of evidence must name a criterion; a gap makes the command exit 1, so it can gate a pipeline. Without evidence.persist the command still runs and reports what is missing. When the forge cannot be reached, approvals show as unknown with a warning, never as a guess.

From a neutral demo application: an inventory app whose spec adds a reorder alert. The tests and QA columns are the command’s output for that demo; the approval columns show what it prints once the spec PR has merged with an approval and the implementation PR is still open, and the model names and version are placeholders.

# Traceability: fn-1-reorder-alert
| criterion | text | tests | test_result | qa | review_findings | spec_approval | implementation_approval |
|---|---|---|---|---|---|---|---|
| R1 | An item whose stock falls below its reorder level shows a reorder alert on the inventory page. | tests.test_reorder test_r1_alert_below_reorder_level: passed (fn-1-reorder-alert.1); tests.test_reorder test_r1_alert_shown_on_inventory_page: passed (fn-1-reorder-alert.1) | passed | live (QA SHIP) | | PR #12 merged; approved by qa-lead at 2026-10-01T09:12:00Z; merged by eng-lead at 2026-10-01T09:40:00Z | PR #14 open; no approving review |
| R2 | The alert clears when stock is raised above the reorder level. | tests.test_reorder test_r2_alert_clears_above_level: passed (fn-1-reorder-alert.1) | passed | live (QA SHIP) | | PR #12 merged; approved by qa-lead at 2026-10-01T09:12:00Z; merged by eng-lead at 2026-10-01T09:40:00Z | PR #14 open; no approving review |
| R3 | A reorder level of zero never raises an alert. | tests.test_reorder test_r3_zero_level_never_alerts: passed (fn-1-reorder-alert.1) | passed | live (QA SHIP) | | PR #12 merged; approved by qa-lead at 2026-10-01T09:12:00Z; merged by eng-lead at 2026-10-01T09:40:00Z | PR #14 open; no approving review |
## Gaps
None.

A criterion with no evidence shows up as a gap and a non-zero exit:

## Gaps
- R4: no evidence

The run record in the same evidence folder:

{
"reviews": [
{"key": "reviews/20261001T101500Z-r1-impl_review-fn-1-reorder-alert.1.json",
"type": "impl_review", "verdict": "NEEDS_WORK", "backend": "codex",
"model": "<reviewer model>", "host": "claude-code", "flow_next_version": "<version>"}
],
"stages": [
{"key": "fn-1-reorder-alert.1", "task": "fn-1-reorder-alert.1",
"implementing_model": "<implementing model>", "host": "claude-code",
"flow_next_version": "<version>"}
]
}
  • Configuration: evidence.persist, evidence.dir and spec.approval.
  • CLI reference: flowctl trace.
  • For teams: standing criteria in .flow/criteria.md and the spec as the team’s handover.
  • Live-app QA: what the development QA pass does and does not prove.