Configuration
All project configuration lives in one file: .flow/config.json. Read and write it through flowctl so writes stay validated and atomic:
flowctl config get <key> --json # one key (dotted path)flowctl config get --json # the whole treeflowctl config set <key> <value> # validated writeEvery key below comes from the published JSON Schema at plugins/flow-next/schema/flow-config.schema.json — the same schema /flow-next:setup stamps into the file’s $schema field, and the one the repo’s drift test holds flowctl to: a key flowctl accepts that the schema does not document fails the suite. This page is the human rendering of that contract.
Core pipeline
Section titled “Core pipeline”Always-relevant knobs for the default spec-to-PR flow.
| Key | Type | What it does |
|---|---|---|
review.backend | enum | Default review backend (rp, codex, copilot, cursor, host, none) or spec form backend[:model[:effort]], e.g. codex:gpt-5.4:high. cursor folds effort into the model name (no :effort rung); rp, host, and none are bare-only. copilot accepts no none/minimal effort. If unset, review commands require —review or FLOW_REVIEW_BACKEND. Optional: flow-next runs fully without a standing backend. It costs an out-of-host review pass per round plus a second CLI to install and authenticate; set it when agent-written diffs merge without a human reading them line by line, or invoke /flow-next:impl-review manually instead. See docs/running-lean.md. Default: null. |
review.maxIterations | integer | Cumulative review-round cap per scope (default 8, minimum 1 - the cap can never be disabled). The env var MAX_REVIEW_ITERATIONS takes precedence over this key. Raising it is a human act: ralph-guard blocks the config write, the config file, and the env assignment so an autonomous agent cannot extend its own review gate. Default: 8. |
| Key | Type | What it does |
|---|---|---|
planSync.enabled | boolean | Enable plan-sync after task completion. On by default and droppable: it costs a reconciliation pass after each completed task, and earns that on specs with dependent tasks. Turn it off and run /flow-next:sync manually when a task invalidates a downstream assumption. See docs/running-lean.md. Default: true. |
planSync.crossSpec | boolean | Cross-spec plan-sync: scan other open specs for stale references after each task (opt-in; increases sync time). Default: false. |
| Key | Type | What it does |
|---|---|---|
memory.enabled | boolean | Enable the memory system: skills capture and search categorized learnings under .flow/memory/. Default: true. |
| Key | Type | What it does |
|---|---|---|
scouts.github | boolean | Enable github-scout during planning (requires the gh CLI). Default: false. |
| Key | Type | What it does |
|---|---|---|
artifacts.html.enabled | boolean | Enable optional HTML artifacts: participating skills emit self-contained lenses at .flow/artifacts/<spec-id>/. OFF by default - markdown stays the sole source of truth. It costs an extra render step on capture, plan, and make-pr plus an artifact tree to commit or ignore; enable it when specs go to business reviewers, or ask for a render in conversation when you need one. See docs/running-lean.md. Default: false. |
| Key | Type | What it does |
|---|---|---|
makePr.derivedPaths | object | Optional derived-file classification rules for the make-pr export: bucket names (dualCopy, mirror, state) mapped to arrays of rules ({path|prefix, source}). A configured value fully replaces flow-next’s built-in default shapes; never required and not part of the seeded defaults. |
| Key | Type | What it does |
|---|---|---|
pipeline.qa | enum | Optional QA pipeline stage (fn-72). String-enum, NOT a bool: only the literal on activates; any other value, including bool true, is OFF. With it on, pilot inserts one live /flow-next:qa pass at the all-tasks-done juncture before make-pr. Optional: flow-next runs fully without it. It costs a live-app drive pass per spec plus a running deploy and a configured driver; enable it when nobody will exercise the app before merge, or invoke /flow-next:qa manually on the changes that deserve it. See docs/running-lean.md. Values: off, on. Default: "off". |
Autonomy — pilot & land
Section titled “Autonomy — pilot & land”The build loop and the ship loop. All off/conservative by default.
| Key | Type | What it does |
|---|---|---|
pilot.autonomy | enum | Pilot backlog mode (fn-68). Scalar string-enum (ready | backlog), NOT a bool. ready = select only already-ready specs. Only the literal backlog widens selection to the whole open backlog; any other value stays ready. Backlog mode never authors a spec, never sets ready, never merges. Values: ready, backlog. Default: "ready". |
pilot.gateClasses | enum | Backlog-mode force-gate: class names (e.g. risky, prod-config) that force surfacing before action - a matching item is parked with a question instead of advanced full-auto. Empty = full-auto for every workable item. Default: []. |
land.* — /flow-next:land babysit-loop settings (fn-60).
| Key | Type | What it does |
|---|---|---|
land.release | boolean | Run the post-merge release-follow step (the project’s own release docs; no-ops when none are discovered). false = stop at merge. Default: true. |
land.patienceMinutes | integer | Reviewer patience window in minutes, anchored to the LAST push (a land-authored CI-fix push restarts it). Default: 30. |
land.reviewSignal | string | Merge review-signal: silence (automated review present + zero unresolved threads + window elapsed), approve (formal reviewDecision == APPROVED), or a GitHub login (that reviewer’s latest review must be clean). Default: "silence". |
land.automatedReviewers | string | CSV allowlist of reviewer logins land counts as automated, supplementing the [bot]-suffix rule. Empty = suffix rule only. Default: "". |
land.reviewTrigger | string | One-shot comment land posts to summon a reviewer bot on a draft PR with zero automated reviews (e.g. @codex review - bots don’t auto-review drafts). Empty = never post. Default: "". |
land.ciFixBudget | integer | CI-fix attempts per PR before land durably labels it flow-next:needs-human and skips it on later ticks. Default: 3. |
land.cleanReviewCommentPattern | string | null | ERE for the silence-signal clean-review COMMENT path: a bot’s no-findings issue comment naming the current head SHA also satisfies the gate. Requires BOTH the clean phrase AND the Reviewed commit marker. null/missing falls back to the built-in default; set to an empty string to disable the comment scan (the only real off-switch). Default: "(Didn'?t find any( major)? issues|No( major)? issues found).*Reviewed commit". |
land.requestReviewers | string | null | Opt-in human-reviewer request (4.2.0): csv of GitHub logins and/or org/team slugs and/or the literal codeowners. When a human review is the only missing merge input (CI green, zero unresolved threads, and the review signal wants a human), land flips a draft PR to ready and requests the list minus the PR author — exactly once per PR per head SHA, recorded in the land ledger and atomically claimed so overlapping ticks cannot double-request. The codeowners token rides the ready flip (GitHub resolves the owners; no local CODEOWNERS parsing). Never gates a merge — land.reviewSignal does. Unset, null, and an empty string all mean OFF. Default: "". |
land.mergeVerdictCommand | string | null | Opt-in repo merge-verdict gate: a shell command land runs once per merge attempt, after every other gate is satisfied and only when the planned action is merge. Exit 0 = green; any non-zero exit - including a missing/unexecutable command, the 600s timeout, or signal death - blocks the merge (fail-closed, never skipped). Context arrives as environment only (FLOW_HEAD_SHA, FLOW_BASE_REF, FLOW_PR_NUMBER, FLOW_SPEC_ID); it runs on the base checkout, so it must key on FLOW_HEAD_SHA and refuse when it cannot see that head. Never executed under —dry-run. Unset, null, and an empty string all mean OFF. Default: "". |
Removed in 4.0.0 — routing is not config any more
Section titled “Removed in 4.0.0 — routing is not config any more”The work.delegate* keys (packaged codex delegation) and the models.* block (the role map and its verifiedAt / verifiedWith staleness stamps) are gone from the schema and from flowctl. Routing lives in the routing block in your CLAUDE.md / AGENTS.md instead, and implementation offload is the implementer tier plus a bridge recipe.
Leftover keys are inert, not dangerous. A .flow/config.json still carrying them keeps working: flowctl ignores them entirely and prints one non-blocking advisory naming what it found, on the config surfaces and the work entry points, to stderr so a --json read stays parseable. Delete them when convenient.
Chart (pre-capture discovery)
Section titled “Chart (pre-capture discovery)”Bounds for the optional decision-map stage.
| Key | Type | What it does |
|---|---|---|
chart.maxDecisions | integer | Charting-time decision ceiling (default 12). chart create with an initial-map refuses past this count without —force-size —reason. Later sharpening may grow past it. Default: 12. |
chart.claimStaleAfter | number | Stale-claim age threshold in hours (default 24). release-claim —break-stale —reason is allowed only after a claim is at least this old; always audited (actor, prior owner, age, reason). Default: 24. |
Tracker bridge
Section titled “Tracker bridge”Projection to Linear / GitHub / GitLab / Jira. Inactive unless enabled.
tracker.* — Tracker-sync bridge settings (Linear / GitHub / GitLab / Jira). See docs/tracker-sync.md.
| Key | Type | What it does |
|---|---|---|
tracker.version | integer | Tracker config schema version. Default: 1. (machine-written) |
tracker.enabled | boolean | Enable the tracker-sync bridge. The bridge is active iff raw tracker.enabled == true OR raw tracker.type is one of linear/github/gitlab/jira. Optional: flow-next runs fully without it. It costs a bidirectional round-trip per enabled lifecycle event plus a conflict policy and a second place state can be wrong; enable it when other people need to read or edit status where they already work, or run /flow-next:tracker-sync manually and leave the bridge off in between. Spec-only is a first-class mode. See docs/running-lean.md. Default: false. |
tracker.type | enum | Tracker backend: linear, github, gitlab, or jira. Values: linear, github, gitlab, jira (or null). Default: null. |
tracker.provenance | string | null | Free-form provenance written by the discovery ceremony on confirmation (who/when/signals). Default: null. (machine-written) |
tracker.perEvent.capture | enum | Sync op fired when a spec is captured: off | pull | push | reconcile | comment. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.interview | enum | Sync op fired after an interview updates a spec: off | pull | push | reconcile | comment. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.plan | enum | Sync op fired after planning: off | pull | push | reconcile | comment. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.work.firstClaim | enum | Sync op fired on a task’s first claim: off | pull | push | reconcile | comment. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.work.done | enum | Sync op fired when a task completes: off | pull | push | reconcile | comment. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.makePr | enum | Sync op fired when make-pr opens a PR: off | pull | push | reconcile | comment. The PR link + In Review push is unconditional whenever the bridge is active. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.resolvePr | enum | Sync op fired after resolve-pr: off | pull | push | reconcile | comment. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.completionReview | enum | Sync op fired after the spec completion review: off | pull | push | reconcile | comment. The ceremony seeds comment (verdict + R-ID coverage; never terminal Done). Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.perEvent.qa | enum | Post the /flow-next:qa ship verdict as a tracker comment: off | comment only. comment is the only sensible verb for a verdict; the QA skill treats any non-off value as comment. Not switched on by the ceremony’s default-on set - QA-specific opt-in. Values: off, comment. Default: "off". |
tracker.perEvent.land.merged | enum | Post-merge touchpoint for /flow-next:land. Active-by-default whenever the bridge is active (a real merge is the only event that legitimately projects terminal Done); this leaf only tunes the optional verdict comment, never the merge-gated status write. Values: off, pull, push, reconcile, comment. Default: "off". |
tracker.charts | enum | Optional chart lifecycle projection (fn-135). String-enum off|on, NOT a bool: only the literal on projects charts as parent issues with decision children through the tracker facade. Local chart operations always succeed when off or when the bridge is inactive. Values: off, on. Default: "off". |
tracker.perTracker.teamId | string | null | Linear team id. Default: null. |
tracker.perTracker.projectId | string | null | Linear project id. Default: null. |
tracker.perTracker.labelMap | object | Label linkage map (tracker-specific shape). Default: {}. |
tracker.perTracker.priorityMap | object | Priority linkage map (tracker-specific shape). Default: {}. |
tracker.perTracker.repo | string | null | GitHub repo as owner/name, written by the discovery ceremony (machine-written; not part of the seeded defaults). (machine-written) |
tracker.perTracker.project | string | null | GitLab group/subgroup/project path (URL-encoded once for the API, never double-encoded). Default: null. |
tracker.perTracker.host | string | null | Self-managed GitLab base URL. null resolves from glab config / CI_SERVER_URL; gitlab.com is never assumed. Default: null. |
tracker.perTracker.baseUrl | string | null | Jira site base URL (Cloud or DC/Server). The JIRA_BASE_URL env var overrides it at runtime. Default: null. |
tracker.perTracker.projectKey | string | null | Jira project key (the JQL / listOpenIssues scope). Default: null. |
tracker.perTracker.authScheme | enum | Jira auth shape decided once at the discovery ceremony: cloud-basic (Cloud HTTP-basic email:API_TOKEN) or bearer-pat (DC/Server bearer PAT). Credentials still read from env each run, never stored here. Values: cloud-basic, bearer-pat (or null). Default: null. |
tracker.perTracker.apiVersion | integer | null | Jira REST API version. null until the resolver pins 2; migration converges a legacy 3 to 2. Default: null. |
tracker.perTracker.owner | string | null | GitHub repository owner (discovery-fingerprint input; dynamic per.get read via _FINGERPRINT_KEYS). |
tracker.perTracker.issueType | string | integer | null | Jira issue type (name or id) for created issues; a configured value that does not resolve against the live project is an error. |
tracker.perTracker.blocksLinkType | string | null | GitLab link type used for blocks relations (e.g. blocks); probe and mutation use the same resolved name. |
tracker.perTracker.preferredTransport | string | null | Linear transport preference (mcp routes through the MCP continuation; anything else uses HTTP). |
tracker.perTracker.transport | string | null | Legacy alias for preferredTransport (read second). |
tracker.perTracker.sslVerify | boolean | Verify TLS certificates against Jira. false is an explicit opt-out for a self-hosted internal-CA / self-signed cert (JIRA_SSL_VERIFY env overrides). Default: true. |
tracker.perTracker.statusMap | object | Legacy normalized-status to Jira status map ({name}/{id}; id preferred - names are project-renamable). Live entries migrate into tracker.resolved.destination.statusIds; dead entries are dropped with a warning. Default: {}. |
tracker.staleAfterHours | integer | Staleness threshold (hours) consumed by sync list-stale. Default: 24. |
tracker.conflictTiebreak | enum | Status who-wins tiebreak: flow-wins | tracker-wins | always-ask. Strict enum: invalid CLI writes are rejected; malformed persisted values fail before status work. In Ralph mode always-ask resolves to queue, not prompt. Values: always-ask, flow-wins, tracker-wins. Default: "always-ask". |
tracker.readyState | string | null | Readiness projection: the tracker workflow state meaning ready for work (a Linear state name, Jira status name, or a GitHub/GitLab label). When set, pull-side sync projects it onto the local spec ready flag - one-way, tracker is authoritative. null = projection off. Default: null. |
tracker.specIds | enum | Id scheme for new specs when a tracker bridge is active: flow (native fn-N) or tracker (tracker-keyed KEY-N-slug / synthetic gh-N / gl-N). Strict enum on write; malformed on-disk values fail closed to flow. Not materialized at init so setup can detect never-asked via a raw null read. Values: flow, tracker. Default: "flow". |
tracker.transport.timeoutS | number | null | Per-request timeout in seconds (0-600). |
tracker.transport.maxRetries | integer | null | Retry attempts per call. |
tracker.transport.backoffCapS | number | null | Backoff cap in seconds. |
tracker.transport.concurrency | integer | null | Max concurrent tracker calls. |
tracker.resolved.destination.statusIds | object | Normalized status slots (todo, in_progress, done; optional provider slots) mapped to provider status ids. (machine-written) |
tracker.resolved.destination.stateIds | object | Normalized state slots mapped to provider state ids (Linear). (machine-written) |
tracker.resolved.capabilities._source | object | Machine-written capability provenance (which probe/endpoint established each flag); GitLab’s resolver persists it alongside the boolean capability keys. (machine-written) |
tracker.resolved.scopeResolvedAt.destination | string | ISO timestamp of the last successful destination resolution. (machine-written) |
tracker.resolved.scopeResolvedAt.destination.statusIds | string | ISO timestamp of the last successful destination.statusIds resolution. (machine-written) |
tracker.resolved.scopeResolvedAt.destination.stateIds | string | ISO timestamp of the last successful destination.stateIds resolution. (machine-written) |
tracker.resolved.scopeResolvedAt.capabilities | string | ISO timestamp of the last successful capabilities resolution. (machine-written) |
tracker.resolved.resolvedAt | string | null | Non-null only when all required destination fields, required normalized slots, and capability booleans are present. (machine-written) |
Environment overrides
Section titled “Environment overrides”A few knobs are runtime environment variables rather than config keys:
| Variable | Effect |
|---|---|
FLOW_ACTOR | Actor identity for claims/receipts. Wins over git email, git name, $USER. |
FLOW_REVIEW_BACKEND | Per-invocation review backend override (bare or backend:model:effort spec form). |
MAX_REVIEW_ITERATIONS | Session override for review.maxIterations (the config key is the durable form). |
FLOW_PR_CREATE_CMD | make-pr’s PR-create seam for App/bot-authored PRs. |
FLOW_AUTONOMOUS=1 | Question-suppression for autonomous drivers (never implies Ralph). |
JIRA_BASE_URL / tracker tokens | Tracker transport credentials and endpoint overrides. |
FLOW_NO_DEPRECATION=1 / FLOW_NO_AUTO_MIGRATE=1 | Silence deprecation notices / disable auto-migration of legacy layouts. |
Precedence where both exist: explicit CLI flag → environment → .flow/config.json → built-in default.
Team defaults
Section titled “Team defaults”Set policy where the team can inspect it — the config file is committed, so flowctl config set IS the policy record. The decisions worth making explicitly as a team: the review backend (cross-family beats same-family), review.maxIterations, whether planSync.crossSpec is worth the reconciliation time at your spec volume, the tracker perEvent map (which lifecycle moments project), and the autonomy posture (pilot.autonomy, land.reviewSignal, land.mergeVerdictCommand on repos with no branch protection). Everything else has a sane default.