Public Workflow CLI#

Flow Agents exposes its supported consumer workflow surface through the primary package binary. Consumer repositories do not need a package.json, a local dependency, or a repository-owned writer script.

Run a workflow from the package binary#

Use an exact package version from an isolated npm prefix. This prevents a repository-local dependency with the same version from intercepting the command. Generated workflow actions and doctor remediation include this isolation automatically. The example pins 9.0.0, the current latest release; pin the release you have checked (npm view @kontourai/flow-agents version). A section that needs a newer release says so:

flow_agents() (
  root=$(mktemp -d) || exit 1
  trap 'rm -rf "$root"' EXIT HUP INT TERM
  npm exec --yes --prefix "$root" \
    --package=@kontourai/flow-agents@9.0.0 -- flow-agents "$@"
)

flow_agents workflow start \
  --flow builder.build \
  --work-item provider:work-item-123 \
  --assignment-provider example-provider \
  --effective-state-json .kontourai/flow-agents/provider-assignment.json

flow_agents workflow status --json

flow_agents workflow evidence \
  --session-dir .kontourai/flow-agents/example \
  --expectation implementation-plan \
  --status pass \
  --summary "Implementation plan recorded." \
  --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/example/example--plan-work.md","summary":"Reviewed implementation plan with Definition Of Done and task-to-criterion mapping."}' \
  --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/example/acceptance.json","summary":"Stable acceptance criteria and required evidence."}' \
  --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/example/handoff.json","summary":"Execution handoff and next action."}'

flow_agents workflow critique \
  --session-dir .kontourai/flow-agents/example \
  --verdict pass \
  --summary "Report-only review found no blocking findings." \
  --artifact-ref ".kontourai/flow-agents/example/example--deliver.md" \
  --lane-json '{"id":"code-review","status":"pass","summary":"The delivered implementation was reviewed.","evidence_refs":[{"kind":"artifact","file":".kontourai/flow-agents/example/example--deliver.md","summary":"Reviewed delivery report and changed scope."}]}'

builder.build accepts the stable, human-readable Work Item reference emitted by the selected provider adapter. Flow Agents persists that exact reference as the run subject; it does not infer provider identity from a GitHub-shaped string. Pass the resolved --assignment-provider; non-local providers also pass their standard assignment status result through --effective-state-json. Flow Agents verifies that the current actor is the confirmed holder, retains that provider result as selected-work evidence, and creates a local runtime lease mirror for atomic session mutation. A provider-backed Builder session also takes its branch only from the validated AssignmentStatus.assignment.record.branch. That branch is copied unchanged into the immutable provider snapshot, local assignment mirror, delivery artifact, state, and current-session projections; the public command deliberately has no caller branch override. Re-running start for the same Work Item resumes only when every existing projection still agrees with the current provider-authorized branch. A missing, malformed, or conflicting provider branch fails closed before session mutation. Do not repair such a contradiction by editing runtime artifacts: retain the conflicting session for evidence, release or supersede the provider assignment through its normal provider workflow, and begin a fresh provider-backed Work Item/session. This is the safe recovery boundary until a transactionally recoverable branch-reconciliation protocol exists. A direct local request can resume an existing bound session, but the public CLI does not invent a provider or create an unresolvable local binding.

Omitting --flow starts the flow a kit declares as its default_flow (builder.build for the Builder kit). When no kit declares one, or more than one kit does, start refuses and asks for --flow; see Default flow.

Approved cross-repository rollouts#

An approved portfolio or retrofit initiative does not authorize a retroactive Builder run. Before editing each target repository, create or select that target's provider-backed Work Item, link it to the portfolio approval, and complete its normal pull-work selection and ownership preflight. Then bind the target Work Item through the public command:

flow_agents workflow start \
  --flow builder.build \
  --work-item kontourai/target-repository#123 \
  --assignment-provider github \
  --effective-state-json .kontourai/flow-agents/target-assignment-status.json

The provider status must confirm the calling actor holds that exact Work Item. start retains the provider result as selected-work evidence and begins at the canonical Builder entry step. A portfolio approval, a branch name, or a finished change is not a substitute for that binding. If the work has already been changed without a bound run, do not manufacture a prior session or use a private writer. Review and verify the finished change through builder.review-existing instead (see Review and verify an existing change), deliver and merge it through builder.deliver-existing (see Review, verify, and merge an existing change), or record the gap and create a follow-up Work Item.

The agent executes the reported public next_action through planning, implementation, review, verification, and release readiness, produces the required artifacts, and records their evidence. Starting the run does not launch a worker. Flow Agents no longer ships a CI check that reconciles committed delivery bundles (the Trust Reconcile check and trust-verify action were retired on 2026-10-11), so an ordinary run skips provisional delivery. The verb remains for a repository that runs its own pull-request check over the session's delivery bundle; in that case, publish a provisional delivery after the pull request is open and before recording ci-merge-readiness:

flow_agents workflow publish-provisional-delivery-request \
  --session-dir .kontourai/flow-agents/kontourai-target-repository-123 \
  > provisional-delivery-request.json

# Sign provisional-delivery-request.json.authorization exactly as serialized with a
# configured lifecycle-operator Ed25519 key. Add only the signature block and store
# the resulting authorization outside the project checkout.
flow_agents workflow publish-provisional-delivery \
  --session-dir .kontourai/flow-agents/kontourai-target-repository-123 \
  --authorization-file /absolute/outside-project/provisional-delivery.authorization.json \
  --json

This command is available only to the active bound builder.build run at merge-ready-ci. It first seals the already-reviewed and verified source checkpoint with status: "provisional" and phase: "ci-readiness". The request binds the exact project/session/Work Item, live assignment generation, canonical definition version and digest, Flow head and gate visit, authenticated publish-change provider record and immutable head, source snapshot, checkpoint, bundle, attestation, and mutually exclusive companion digests. Publication transports those exact bytes; the root-owned coordinator rechecks every binding, appends a signed-authorization event under the Flow run lock, and installs a purpose-specific signed completion before the package records the delivery. It then writes only that session's delivery/<slug>/ transport. Commit and push those companion files before provider checks and before recording ci-merge-readiness, so that check can read the PR revision's bundle. It is not a release decision, does not add a ci-merge-readiness claim, and cannot complete or declare the run.

After those provider checks, record the explicit readiness decision and complete learning. Only when the canonical builder.build run has advanced through learn to completed done, publish the terminal CI-visible delivery evidence through the primary public CLI, commit that superseding delivery output, and push the resulting head:

flow_agents workflow publish-delivery \
  --session-dir .kontourai/flow-agents/kontourai-target-repository-123 \
  --json

This command derives the repository and session from the bound artifact; it does not accept a caller-selected output repository or private-writer escape hatch. It refuses a missing bundle, a partial Builder run, an unreconciled bundle shape, a stale or non-holder actor, and a positively identified checkpoint/checkout mismatch. Public test verification stamps the canonical Git workspace snapshot alongside its successful command observations. Before sealing, publishing requires both that stamped verification snapshot and the canonical live review graph to match the exact current workspace. The one supported continuation is a checkpoint-bound provisional delivery: terminal publication permits it only when the verification base is an ancestor of the current checkout, every committed and uncommitted change since that base is confined to the same delivery/<slug>/ transport, and the provisional bundle and checkpoint still match their session-local digests. A missing, altered, stale, unrelated, or concurrent-session delivery fails closed; re-run review and verification instead of borrowing its evidence. Sessions whose older test evidence lacks this snapshot also fail closed. The publisher holds the session subject lock and rechecks the identical snapshot after sealing, immediately before copying, and after the copy. A post-copy source change restores the exact prior delivery/<session>/ destination (if any) and leaves every sibling delivery untouched before failing. It then seals against the derived target checkout's current HEAD, replaces stale companions with one newly emitted signed or in-toto companion, and verifies that companion binds both the checkpoint digest and current trust bundle before copying the set into delivery/<session>/.

At terminal publication, the Git delta must contain exactly the four transport files: trust.bundle, trust.checkpoint.json, trust.checkpoint.attestation.json, and the signed or in-toto companion selected by the attestation descriptor. Ordinary ancestry, tree equivalence, inherited bundles, extra source changes, altered or missing companions, and unrelated session bundles are refused.

Terminal publication is unavailable while learn is active, even when a release decision is positive; provisional publication is the only CI-transport path before learning closes.

builder.shape uses a caller-supplied, safe slug. Derive it from a selected title using lowercase ASCII words separated by single hyphens. Never inject raw request text into a shell command or use it as a slug:

flow_agents workflow start --flow builder.shape \
  --task-slug onboarding-alerts \
  --summary "Shape onboarding alerts into independently actionable slices."
flow_agents workflow status --session-dir .kontourai/flow-agents/onboarding-alerts --json

For tests-evidence, supply one criterion object for every accepted criterion and one or more substantive commands that were actually run. Repeat --command when criteria require different checks. Every command needs a matching top-level command reference, and every passing criterion must cite at least one of those exact commands. A passing observation must also report a positive executed-test or assertion count; a successful zero-test run is rejected. Predictable payload, current-review, result-protocol and publication-command incompatibilities are checked before execution and revalidated afterward. Bounded diagnostics retain the final stdout result independently of late stderr; output hashes cover all bytes. A watchdog termination is non-confirming even if the terminated child exits zero. The full command deadline also bounds inherited output streams after the command exits: streams that remain open beyond its grace produce a refusal, rather than holding the evidence transaction indefinitely. A supported framework must terminate its package-script chain; trailing summary printers and post-script lifecycle hooks cannot supply its proof. Node supports its built-in default, spec and TAP reporters. Project-local regular preloads remain inside the reviewed source boundary; arbitrary outside preloads or reporters do not. Repository test code and the host runner remain the local trust boundary; these counts are execution evidence, not cryptographic proof of assertion quality.

For a committed manifest command that directly delegates to a local Node coordinator, Flow Agents also supports committed v3 verification receipts. Admission uses the producer's current public explain <lane> request and its exact canonical receipt path and commit sidecar. Both freshly executed and reused summaries must match the current source, request, environment, toolchain, provenance, artifact digests, cleanup and positive counts. Missing, stale, failed or incompatible receipts remain non-confirming. Do not use placeholders such as true or a version command as behavior proof:

flow_agents workflow evidence \
  --session-dir .kontourai/flow-agents/example \
  --expectation tests-evidence \
  --status pass \
  --command "npm test" \
  --summary "The project test command passed for the implemented criterion." \
  --evidence-ref-json '{"kind":"command","excerpt":"npm test","summary":"Exact substantive project test command recorded for this verification result."}' \
  --criterion-json '{"id":"<criterion-id>","status":"pass","evidence_refs":[{"kind":"command","excerpt":"npm test","summary":"Exact substantive project test command run for this criterion."}]}' \
  --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/example/example--plan-work.md","summary":"Accepted criterion and verification mapping."}'

--criterion-json supplies the complete, verified evidence for the accepted criteria during this public verification transaction. It is intentionally distinct from acceptance.json: that file remains the planning contract and can retain an incomplete planner reference such as {"kind":"source","summary":"Locate the implementation source before verification."}. The successful workflow evidence --expectation tests-evidence command validates the complete criterion objects and records them in authoritative trust.bundle; it does not rewrite the planning artifact. Do not edit acceptance.json to repair a planned placeholder or invoke the package-private workflow:sidecar record-evidence writer for that purpose. Generic/private evidence writes continue to validate their final refs and fail closed on incomplete source refs.

Revision-bound command observations#

The public evidence command does not accept caller-supplied Git provenance. Repository-owned capture records observed_at_commit and worktree_clean after each command result settles, then preserves them in trust.bundle metadata and the consumer-facing workflow-evidence v2 projection. For an item to contribute to a passing gate, its captured commit must resolve through trusted Git and be an ancestor of current HEAD, its cleanliness must be true, and its exact captured Git-worktree snapshot must equal the current snapshot. Ancestry is not byte proof; the snapshot comparison is required as well.

Trusted local reads use the system Git provenance policy, including its bounded Linux namespace support and protected system-path checks. Namespace-visible UID0 is not a universal host-root identity, and an overflow UID alone is not trusted provenance. Signed lifecycle helpers and verification keys retain their stricter ownership and authentication rules.

Dirty, missing, non-Git, malformed, unavailable, unresolved/shallow, or non-ancestor provenance is NOT_VERIFIED/not_verified, never an implicit pass. Schema v1 is rejected and must be re-recorded; v2 command entries missing these fields are non-confirming. A delivery checkpoint or attestation commit_sha is aggregate transport provenance, not a substitute for an individual command's observed_at_commit.

An embedding host whose current runtime identity cannot reproduce the active assignment actor must establish an expiring recovery-capable session binding, then authorize each ordinary evidence mutation separately. Run evidence-request with the exact evidence arguments, sign the emitted signing_payload with a registered lifecycle-authority key, add that signature to the emitted authorization, and pass the protected signed file to the otherwise identical evidence command:

flow_agents workflow evidence-request \
  --session-dir .kontourai/flow-agents/example \
  --expectation implementation-scope \
  --status pass \
  --summary "Implementation remained inside the accepted scope." \
  > evidence-request.json

flow_agents workflow evidence \
  --session-dir .kontourai/flow-agents/example \
  --expectation implementation-scope \
  --status pass \
  --summary "Implementation remained inside the accepted scope." \
  --authorization-file /absolute/outside-project/evidence-authorization.json

The authorization binds the exact assignment generation, assignment actor key and struct, the independently named host routing key and routing-binding generation, Flow head and manifest, Trust Bundle, evidence arguments, canonical project/run/subject, nonce, and expiry. The external coordinator verifies and durably redeems it before the normal evidence transaction runs. A pointer alone is routing metadata, never bearer authority; replay, binding retirement/replacement, assignment takeover, or changed Flow, trust, or evidence arguments fail closed before another mutation.

When a current root-signed lifecycle completion already binds a Trust Bundle and its external resolution ledger, ordinary workflow evidence correctly refuses to invalidate that completion. If a legitimate later public critique or gate claim has already made that receipt stale, do not rewrite or delete it, append a resolution event, or use history repair. First request and sign the completion-only refresh (the authorization file must remain outside the project):

flow_agents workflow recover-exact-current-completion-request \
  --session-dir .kontourai/flow-agents/example > completion-recovery-request.json

# Sign completion-recovery-request.json.authorization with the configured operator key.
flow_agents workflow recover-exact-current-completion \
  --session-dir .kontourai/flow-agents/example \
  --authorization-file /absolute/outside-project/completion-recovery-authorization.json

This narrow action is available only at the one open builder.build verify gate. It binds an authenticated stale same-run root completion, the raw current bundle and ledger bytes, complete cross-reviewer resolution coverage, critique and edge projections, subject, raw Flow definition and canonical ordered gate-policy digests, Flow head/manifest, fixed transition, nonce, and expiry. It writes no claim, ledger event, bundle, or stale receipt; the coordinator first durably stages and root-signs an exact five-artifact Flow-only publication plan. It activates Flow's native recovery fence before the first postimage and keeps that exact generation active through root completion/nonce persistence and receipt installation. Prepared retry uses the matching recovery lock, accepts exact all-new state, rolls exact all-old or mixed old/new state forward from the staged postimages, and rejects unknown or mismatched state without restoring foreign bytes. Finalization verifies the exact receipt, postimages, and fence generation, opens the fence through Flow, and then removes the plan and stages. The plan only hashes the bundle, ledger, and stale receipt; it never snapshots or restores them.

The canonical attachment ID and stored filename include the signed authorization digest in addition to the unchanged request-envelope digest. Reusing the same authorization-file path for a later, distinct signed recovery therefore creates a distinct attachment while the durable completion still binds the request digest. Replaying the same signed authorization returns the immutable completion without another attachment. Use it only after all final independent critique/resolution work is complete, and then use reseal for the final verification evidence.

Use the two-stage reseal only for the final builder.build verify evidence after critique resolution:

flow_agents workflow reseal-verification-evidence-request \
  --session-dir .kontourai/flow-agents/example \
  --expectation tests-evidence \
  --status pass \
  --command "npm test" \
  --summary "Final verification passed after the resolved review." \
  --evidence-ref-json '{"kind":"command","excerpt":"npm test","summary":"Final substantive verification command."}' \
  --criterion-json '{"id":"<criterion-id>","status":"pass","evidence_refs":[{"kind":"command","excerpt":"npm test","summary":"Final verification command for this criterion."}]}' \
  > reseal-request.json

# Sign reseal-request.json.authorization using the configured lifecycle operator key,
# write the signature block into a protected file outside the project, then:
flow_agents workflow reseal-verification-evidence \
  --session-dir .kontourai/flow-agents/example \
  --authorization-file /absolute/outside-project/reseal-authorization.json

The request executes the existing evidence writer once into a retained transaction candidate; it does not attach or publish that candidate. Its unsigned authorization binds the current raw bundle, candidate bytes and transaction id, unchanged ledger digest/length/tail, exact current completion raw/core identity, canonical Flow step/gate, head and manifest, critique projection, run subject, nonce, and expiry. It also binds the exact target verify expectation and the predecessor/current claim id, status, raw-JSON digest, ordered index, and replace delta. The signed action invokes only the fixed lifecycle helper. The coordinator derives the protected current verify-gate requirements, requires the target exactly once, and rejects predecessor or replacement gate_claim metadata that does not match the current expectation's step, claim type, and subject type. It acquires Flow's native run-mutation lock before capturing a closed transaction plan over exactly six artifact identities: session bundle, Flow manifest, Flow state, request-keyed stored attachment, JSON report, and Markdown report. The root-signed plan contains no artifact paths; it binds the request, authorization key and nonce, pinned reducer identity, result core, and exact pre/post presence, mode, size, and digest for each fixed identity. Old and new images are durably staged in fixed siblings and reread before publication. It permits replacement of only that one ordered target claim; every other claim, including other verify-gate claims, must remain byte-identical and in the same order. It requires a byte-identical critique projection, attaches the candidate to the builder.build verify gate, commits the exact candidate bytes without appending a sixth ledger event, and installs a new root-signed exact-current completion. A provider-neutral Flow recovery fence becomes active before the first postimage and remains active through durable root completion and exact receipt installation; all canonical readers and ordinary Flow mutations fail closed while active. Flow's native writer assigns a unique generation and durably publishes it; the dedicated finalizer requires that exact generation before reopening. Consumers bind the generation, file bytes, and run-directory identity across their complete supported reads and reject symlinked fixed Flow ancestry. Before root records a nonce or any plan/stage exists, the helper preflights the installed mutation lock, recovery lock, active writer, and generation-bound finalizer APIs. Recovery accepts only an exact all-old or all-new generation. Mixed, malformed, or unknown generations are quarantined with the fence left active for offline recovery. An active legacy recursive reseal journal is never auto-restored. Publication also preflights the fixed, protected verification-reseal-atomic-replace.cjs host capability. That administrator-supplied capability must implement kontourai.atomic-expected-preimage-replace.v1: atomically compare the named leaf's exact preimage and install or delete only the authorized postimage. It receives a pinned parent descriptor and basename, while the coordinator independently rechecks the parent and postimage. Root validates its fixed protected artifact; only the caller-identity worker executes its code. The reference coordinator ships no pathname or descriptor-only fallback and refuses on every platform before coordinator state, nonce, plan, stage, fence, or artifact mutation when the capability is absent or invalid. Portable request creation and external signing remain available. After reopening, the signed plan remains the cleanup recovery marker until every fixed stage is removed; replay validates the exact receipt/postimages and safely finishes interrupted cleanup. Stale, altered, replay-mismatched, or wrong-gate requests fail closed; durable nonce/completion and transaction recovery make an exact completed retry a replay.

The public lifecycle verbs are pause, resume, release, cancel, archive, resolve-critique, repair-critique-resolution-history, and reseal-verification-evidence. Pause, resume, and release require the current assignment actor and an explicit reason. critique records the clean-critique claim but does not independently attach it to Flow or advance a gate. Cancel and archive require a signed user/operator authorization file. Flow owns the canonical run transition; Flow Agents validates assignment and request binding and projects the resulting run. Critique derives reviewer identity from the calling runtime actor. An active implementation assignment is required, and its actor cannot review its own work; the delegated reviewer invokes the command directly under a distinct identity. The public interface does not accept a caller-selected reviewer label. Every critique includes an explicit pass, fail, or not_verified verdict and at least one substantive --lane-json. Passing critiques additionally require local reviewed --artifact-ref values and every lane to pass; reviewed files and the workspace snapshot are hashed into the stored review target so later implementation changes invalidate stale clean critiques. The writer captures the workflow-owned execution report as immutable historical context and always includes its controls. Verification notes may grow only in its single final ## Verification Evidence appendix without another source review. Source files, unowned reports, the execution prefix, accepted plan, criterion identity and live scope claims remain review subjects. Ambiguous markers and malformed role metadata fail closed.

Current local runtime actor IDs provide coordination-level separation, not a cryptographic identity guarantee. A policy that requires externally attested reviewer identity must keep that assurance NOT_VERIFIED until the runtime supplies a trusted delegation credential.

Review and verify an existing change#

builder.review-existing reviews and verifies a change that already exists at one exact Git commit, such as an open pull request, without claiming that the run implemented it. Use builder.build for new work.

builder.build builder.review-existing
Starts from A selected Work Item with no implementation yet A selected Work Item whose change already exists at one commit
Steps pull-work, design-probe, plan, execute, verify, merge-ready, pr-open, merge-ready-ci, learn pull-work, existing-change, verify
Implementation claim implementation-scope at execute None. pre-existing-implementation records the change as it existed.
Failing check at verify Routes back to execute, plan, design-probe, or verify Routes back to verify only: at most three route-backs; a fourth failing visit blocks. A code fix is a new head, so it needs a new run.
Source head Follows the branch Pinned at start with --source-head and re-checked at every gate

Before starting:

  1. Check out the exact commit under review in a Git worktree with no uncommitted or untracked files. Workflow output must be ignored. If the reviewed head does not already ignore .kontourai/ and delivery/, add both to the repository's local exclude file, which does not change the head under review:

    exclude="$(git rev-parse --git-path info/exclude)"
    mkdir -p "$(dirname "$exclude")"
    printf '.kontourai/\ndelivery/\n' >> "$exclude"
    

    git rev-parse --git-path finds the file in a linked worktree too, where .git is a file rather than a directory. The exclude file is shared by all worktrees of the repository. Commit an ignore rule (for example the .kontourai/.gitignore that init writes) only if the repository wants it anyway; a commit makes a new head, which is then the head you review.

  2. Select the Work Item and write .kontourai/flow-agents/<slug>/<slug>--pull-work.md naming its reference, as for builder.build.

  3. Use a provider reference (owner/repo#numeric-id or provider:id). A local: reference only resumes an existing local session created by builder.shape, so it cannot start this flow.

  4. In a linked Git worktree, the default artifact root is the main checkout's .kontourai/flow-agents (one shared store per repository), so the pin would be compared with the main checkout's HEAD and refused. Pass the worktree's own store to start: --artifact-root "$(git rev-parse --show-toplevel)/.kontourai/flow-agents", and write the pull-work report under that root. Every later verb for the session (status, evidence, critique, publish-delivery) must then name the session with --session-dir under the worktree, as the examples below do (a worktree-relative path works). Without it, a verb looks the session up through the main checkout's store, does not find it, and refuses.

  5. Before pinning the head or requesting review, select verification commands from the repository's reconciliation manifest. Inspect each selected command's actual runner and test arguments: its name or a passing unrelated check does not prove coverage of the changed behavior. Cover every changed test and accepted criterion, using multiple commands when necessary. For repositories using the baseline registry, list the declared IDs, exact commands and required lanes with:

    bash evals/ci/run-baseline.sh --manifest-json | jq '.[] | {id, command, lanes}'
    

    In Flow Agents itself, static-eval-suite (bash evals/run.sh static) reaches evals/static/test_unit_helpers.sh, which executes every src/cli/*.test.mjs file with the unit-state preload. This establishes CI coverage, but the shell suite does not expose a supported tests-evidence result protocol; declaration alone is insufficient for that claim. The focused telemetry-local-pin-unit-tests entry runs node --test --import ./src/cli/unit-test-state.mjs src/cli/telemetry-local-pin.test.mjs in required Source and Static CI. Prepare its build and bundles first with npm run build:bundles --silent. A suite entry admits only its exact command; it does not admit an arbitrary focused subset.

    If no declared command covers the change, stop setup before pinning: register a suitable command in the owning manifest and wire it into required CI, then commit and review the resulting head. For the baseline registry, update CHECKS, its LANE_* array and the CI workflow step together; run bash evals/static/test_ci_check_registration.sh to check their agreement. A valid tier 1/2 manifest ignores TRUST_RECONCILE_COMMANDS; it cannot extend that manifest. Changing registration after pinning needs a new run at the new head.

Then start with the head pinned. With a provider assignment:

flow_agents workflow start \
  --flow builder.review-existing \
  --work-item kontourai/target-repository#123 \
  --assignment-provider github \
  --effective-state-json .kontourai/flow-agents/target-assignment-status.json \
  --source-head "$(git rev-parse HEAD)"

Without a provider assignment, the local assignment mirror works with a provider-style reference:

mkdir -p .kontourai/flow-agents/provider-review-1
printf 'Selected Work Item: provider:review-1\n' > .kontourai/flow-agents/provider-review-1/provider-review-1--pull-work.md
flow_agents workflow start \
  --flow builder.review-existing \
  --work-item provider:review-1 \
  --assignment-provider local-file \
  --source-head "$(git rev-parse HEAD)"

The run persists the head in its canonical state. start refuses before writing any session artifact when the head is missing, is not a full lowercase 40-hex SHA, is not the checked-out HEAD, or the Git worktree has uncommitted or untracked changes. The refusal names up to five dirty paths and suggests the local exclude file (git rev-parse --git-path info/exclude) for workflow output. Every other flow refuses --source-head. The rule is enforced by the shared runtime that starts every run, so the exported startBuilderFlowSession API refuses the same inputs as workflow start.

At existing-change, write <slug>--existing-change.md with the change's provider record, base, exact head, and changed files, then record it:

flow_agents workflow evidence \
  --session-dir .kontourai/flow-agents/kontourai-target-repository-123 \
  --expectation pre-existing-implementation \
  --status pass \
  --summary "The change existed before this run at the pinned head." \
  --evidence-ref-json '{"kind":"provider","url":"https://github.com/kontourai/target-repository/pull/124","summary":"Provider record of the existing change."}' \
  --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/kontourai-target-repository-123/kontourai-target-repository-123--existing-change.md","summary":"Base, exact head, and changed files."}'

verify has the same expectations as builder.build: a clean critique from a reviewer whose runtime identity differs from the assignment holder, acceptance criteria, command-backed tests-evidence such as --command "npm test" with matching --criterion-json, and applicable policy compliance. This flow has no <slug>--plan-work.md and no execution report (the <slug>--deliver.md that every session gets is only the session template), so artifact references cite <slug>--existing-change.md instead. Passing the verify gate completes the run.

A failing verify claim does not end the run. verify-gate declares only the missing_evidence and default route-backs, both to verify, so a failure opens a new verify visit. Passing evidence re-recorded at the same pinned head in that visit, a fresh critique and fresh tests-evidence, can then complete the run; that is how a flaky or mis-invoked check recovers. A real code fix changes the head, so it needs a new run. The gate allows at most three route-backs; a fourth failing visit blocks.

The run fails closed when:

  • the checked-out HEAD is no longer the pinned head when any gate is evaluated, even if every claim was re-recorded at the new commit;
  • a claim carrying a Git snapshot was observed at another commit or in a dirty worktree (a failing claim stays recordable and routes the run back to verify);
  • start is re-run with a different --source-head, for an active or a completed run;
  • a non-failing claim carries no Git snapshot, because the writer could not capture the worktree;
  • the critique comes from the assignment holder, or tests-evidence names a vacuous command;
  • the same Work Item already has a builder.build run, or the reverse.

Any excursion off the pinned head needs a new run, even if the checkout returns to the pin. For example, a critique recorded while HEAD was another commit leaves the review history bound to that commit, and the run stays refused after the pin is restored.

A new head needs a new run under a new provider Work Item. The run is keyed by the Work Item, and archiving a run keeps its canonical record, so starting the same Work Item again resumes the old run and refuses the new head. This is deliberate friction in this release; there are no per-head runs within one Work Item.

CI reconciliation of command evidence#

workflow publish-delivery accepts a completed run and seals its checkpoint at the pinned head. It first checks the bundle against the repository's reconcile manifest, and refuses with "command is not in the reconcile manifest" unless every command recorded on a passing claim (for example tests-evidence, or policy-compliance recorded with --command) is in it. It resolves the manifest in this order:

  1. a trust-reconcile-manifest entry in package.json (an inline array, or a script that prints the manifest JSON);
  2. evals/ci/run-baseline.sh --manifest-json, when the repository has that script;
  3. otherwise a manifest built from the TRUST_RECONCILE_COMMANDS environment variable, or, when that is unset, from a package.json script named trust-reconcile-verify (which contributes only the command npm run trust-reconcile-verify).

An ambient TRUST_RECONCILE_MANIFEST is ignored by this check. TRUST_RECONCILE_COMMANDS is split on commas and newlines and each part is trimmed, so a command that contains a comma cannot be listed there. A claim matches an entry only when the two commands are identical after runs of whitespace are collapsed to one space. Adding a package.json script changes the head under review, so for a review the environment variable is usually the way:

TRUST_RECONCILE_COMMANDS="npm test" flow_agents workflow publish-delivery \
  --session-dir .kontourai/flow-agents/kontourai-target-repository-123 --json

Use the exact --command text the tests-evidence claim recorded, one entry per command, separated by newlines or commas. If the reviewed head already defines trust-reconcile-verify, record tests-evidence with --command "npm run trust-reconcile-verify" and no variable is needed. If tier 1 or 2 yields a valid manifest, the variable is not consulted, and the recorded commands must appear in that manifest instead; a declared tier 1 or 2 source that fails or prints invalid JSON falls through to the next tier. A tier 1 script entry and the tier 2 script run repository code from the reviewed head (an inline tier 1 array runs nothing), the same trust boundary as running its tests.

Its delivery/<slug>/ output is untracked, so the next pinned start in the same checkout refuses the dirty tree and names those files; append delivery/ to "$(git rev-parse --git-path info/exclude)" (create its directory first if missing; the file is shared by all worktrees of the repository and does not change the reviewed head) or remove them first. merge-change refuses these runs by name ("builder.review-existing runs do not publish a change") before reading provider settings or calling the provider, because the flow binds no step to the publish-change operation. Merge through the provider's own controls, or deliver the change through builder.deliver-existing (next section), which merge-change supports.

Not verified by this route:

  • The runtime requires the reviewer to differ from the assignment holder, and the skill asks the assignment holder to be the change's owner. That the reviewer is not the change's author on the provider, for example the GitHub pull request author, is NOT_VERIFIED.
  • Nothing checks that --source-head is the change's actual head on the provider, for example the pull request's current head commit. The provider URL, base, and head in <slug>--existing-change.md are written by the caller. Both are NOT_VERIFIED.
  • acceptance.json is not anchored to a planning step, the same as in builder.build-lean.

Review, verify, and merge an existing change#

builder.deliver-existing does everything builder.review-existing does and then delivers the change: it adopts the change's open pull request, records CI readiness and learning at the same pinned head, and lets merge-change merge it. It still makes no implementation claim and has no execute step, and it never opens a pull request.

builder.deliver-existing is not in 9.0.0, which has only builder.review-existing; it needs the first release after 9.0.0. Pin that release in flow_agents(), and check that workflow start --flow builder.deliver-existing is accepted before you rely on this section.

builder.build builder.review-existing builder.deliver-existing
Steps pull-work, design-probe, plan, execute, verify, merge-ready, pr-open, merge-ready-ci, learn pull-work, existing-change, verify pull-work, existing-change, verify, pr-open, merge-ready-ci, learn
Implementation claim implementation-scope at execute None None
pr-open publish-change opens or recovers the pull request No step publish-change adopts the existing pull request
Provisional delivery Allowed at merge-ready-ci No step Refused
Source head Follows the branch Pinned at start Pinned at start
merge-change Supported Refused Supported

Start, record pre-existing-implementation, and verify exactly as in Review and verify an existing change, with --flow builder.deliver-existing. The pull-work, existing-change, and verify gates are the same gates. Three things differ:

  • Check out the pull request's head branch: a local branch with the pull request's head branch name, at the pull request's head commit, which is the head you pin.
  • The pre-existing-implementation claim must name the pull request with exactly one provider evidence ref, its URL. pr-open adopts a pull request only when its URL is that one.
  • A configured ChangeProvider must already resolve before you start (see Authenticated publish-change): either context/settings/change-provider-settings.json is committed at the pinned head, or the machine-global $HOME/.config/flow-agents/change-provider-settings.json configures the repository. Adding the project file during the run leaves an untracked file, which the pinned run refuses as a dirty worktree. Its capabilities must include change.create and change.observe, which every configured provider must declare, and change.merge for merge-change. Adoption itself only observes.

At verify, record a critique from a reviewer whose runtime identity differs from the assignment holder's, and command-backed tests-evidence bound to the acceptance criterion. FLOW_AGENTS_ACTOR names the reviewer's identity where identities are assigned explicitly; it must be a different reviewer, not a relabeled holder:

S=.kontourai/flow-agents/kontourai-target-repository-123
R="$S/kontourai-target-repository-123--existing-change.md"
FLOW_AGENTS_ACTOR=existing-change-reviewer flow_agents workflow critique \
  --session-dir "$S" --id existing-review --verdict pass \
  --summary "Independent review of the existing change at the pinned head." \
  --artifact-ref "$R" \
  --lane-json '{"id":"code-review","status":"pass","summary":"Reviewed the existing change at the pinned head.","evidence_refs":[{"kind":"artifact","file":"'"$R"'","summary":"Existing change record."}]}'

criterion=$(node -p 'require(process.argv[1]).criteria[0].id' "$PWD/$S/acceptance.json")
flow_agents workflow evidence --session-dir "$S" \
  --expectation tests-evidence --status pass \
  --summary "The existing change passes its tests at the pinned head." \
  --command "npm test" \
  --evidence-ref-json '{"kind":"command","excerpt":"npm test","summary":"Project tests at the pinned head."}' \
  --criterion-json '{"id":"'"$criterion"'","status":"pass","evidence_refs":[{"kind":"command","excerpt":"npm test","summary":"Project tests at the pinned head."}]}'

--command must name a real project test: a non-vacuous package script or a known test runner on a project path.

At pr-open, adopt the pull request:

flow_agents publish-change execute \
  --session-dir .kontourai/flow-agents/kontourai-target-repository-123 \
  --head-ref feature/existing-change --base-ref main

For a run pinned to a source head the operation adopts rather than opens, and the mode comes from the run's definition, not from a flag. It takes no --title, --body, or --draft (the command refuses them), and it does not compare or set the pull request's title or body. Closed or merged pull requests on the same branch, and fork pull requests from a branch with the same name, are ignored. It refuses, without advancing the gate, when:

  • the provider shows no open same-repository pull request for that head and base ref, or more than one, or more than 100 open matches (it does not adopt from a truncated listing);
  • the pull request is closed or merged by the time it is observed, or the provider record shows its head branch in another repository (a fork);
  • the pull request's head is not the pinned head, or --head-ref does not name the pinned head;
  • the checked-out HEAD is not the pinned head, or the worktree is dirty, either before the provider call or when the result is committed under the session lock afterwards;
  • the pull request's URL is not the one the pre-existing-implementation claim names.

The adoption claim carries the clean Git snapshot it was observed in, like every other claim this run accepts.

At merge-ready-ci and learn, produce ci-merge-readiness, decision-evidence, and learning-evidence as for builder.build (with release-readiness and learning-review). Record CI readiness for the pinned head, which is the pull request's head at this point. release.json and learning.json follow schemas/workflow-release.schema.json and schemas/workflow-learning.schema.json in the package; flow-agents-validate-artifacts --skip-markdown-validation "$S/release.json" "$S/learning.json" checks them. A minimal passing record:

now=$(date -u +%Y-%m-%dT%H:%M:%SZ)
cat > "$S/release.json" <<JSON
{
  "schema_version": "1.0", "task_slug": "kontourai-target-repository-123", "decision": "merge",
  "updated_at": "$now", "scope": "The existing change at the pinned head.",
  "evidence_ref": "kontourai-target-repository-123--existing-change.md",
  "gates": [{ "name": "merge", "status": "pass", "summary": "Required checks and review pass at the pinned head." }],
  "rollback_plan": { "status": "ready", "summary": "Revert the merge commit.", "owner": "change owner" },
  "observability_plan": { "status": "not_required", "summary": "No runtime signal changes." },
  "post_deploy_checks": [],
  "docs": { "status": "not_needed", "summary": "No documentation change." }
}
JSON
flow_agents workflow evidence --session-dir "$S" \
  --expectation ci-merge-readiness --status pass \
  --summary "CI and review support merging the pinned head." \
  --evidence-ref-json '{"kind":"artifact","file":"'"$S"'/release.json","summary":"Release-readiness decision at the pinned head."}'

cat > "$S/learning.json" <<JSON
{
  "schema_version": "1.0", "task_slug": "kontourai-target-repository-123", "status": "learned",
  "updated_at": "$now",
  "records": [{
    "id": "existing-change-delivery", "recorded_at": "$now",
    "source_refs": ["kontourai-target-repository-123--existing-change.md", "release.json"],
    "outcome": "success",
    "facts": ["The existing change was reviewed, verified, and adopted at its pinned head."],
    "interpretation": "No workflow correction is needed.",
    "routing": [{ "target": "none", "action": "No follow-up required.", "status": "completed" }],
    "correction": { "needed": false, "evidence": "Every gate passed at the pinned head." }
  }]
}
JSON
for expectation in decision-evidence learning-evidence; do
  flow_agents workflow evidence --session-dir "$S" \
    --expectation "$expectation" --status pass \
    --summary "$expectation for the delivered existing change." \
    --evidence-ref-json '{"kind":"artifact","file":"'"$S"'/learning.json","summary":"Delivery decision and learning."}'
done

The release-readiness and learning-review skills say what a real decision and learning record must contain; a fail or not_verified verdict uses the same commands. workflow publish-provisional-delivery-request and workflow publish-provisional-delivery refuse these runs, because a provisional delivery commit would move the pull request head off the pinned head. A merge-ready-ci or learn claim made after HEAD moves is refused, and there is no implementation_defect route: a failing claim routes back to verify. A completed run ends at learn.

Then deliver and merge:

  1. Run workflow publish-delivery as above. It seals the checkpoint at the pinned head.

  2. Commit exactly delivery/<slug>/ as one commit on the pull request branch, and push it. This needs push rights to the pull request's branch, which must be in the same repository, and it starts one more CI round, on the new head. If delivery/ is in the local exclude file (step 1 of the previous section), git add refuses the ignored path, so force it for this one directory:

    git add -f delivery/kontourai-target-repository-123
    git commit -m "Add delivery evidence for kontourai-target-repository-123"
    git push
    
  3. Run merge-change request and merge-change execute as in Authenticated merge-change. The terminal head they merge is the pinned head plus that one delivery-only commit.

merge-change holds the run to the pinned head: the adopted pull request must have been observed at the pinned head, the delivery checkpoint must be sealed at the pinned head, the terminal head must be the checkpoint commit plus exactly this session's delivery files, and the provider's pull request head must be that terminal head when it merges. A moved or retargeted pull request, failing, pending, or missing required checks, a review that is not APPROVED, a merge state that is not CLEAN, or a different gh actor is refused before the merge call.

Disclosures:

  • The pull request branch gains one commit, the delivery files, which is what is merged.
  • Nothing refuses a second session that adopts the same pull request. Its delivery commit moves the pull request head, so the first session's merge refuses because the head is no longer its terminal head.
  • That the reviewer is not the pull request's author is NOT_VERIFIED. The merge relies on the provider's approval policy (see Target-branch policy precondition; GitHub forbids approving your own pull request). Whether an approval given before the delivery commit still counts for the terminal head is decided by the repository's dismiss-stale-reviews setting.
  • That the pinned head is the pull request's head is checked when pr-open adopts it, not at start. The base and changed files in <slug>--existing-change.md are written by the caller and are NOT_VERIFIED.
  • acceptance.json is not anchored to a planning step, as in builder.review-existing.

Resolving repaired critique history#

workflow resolve-critique closes a historical failing or not-verified review without deleting it or borrowing the earlier reviewer's identity. It is only available during builder.build verification. It requires an Ed25519-signed user/operator authorization handled by an externally provisioned lifecycle-authority helper. Administrators provision protocol v1 at the immutable path /usr/local/libexec/kontourai/flow-agents-lifecycle-authority-v1; callers cannot select another executable. The helper must be outside the project, package, and worktree, OS-owned, executable, and non-writable by the runtime user, group, or world through every path component. Platforms without a supported ownership adapter fail closed. The helper—not package JavaScript—owns signature and registry verification, locking, nonce replay protection, preimage CAS, atomic bundle/history writes, canonical evidence attachment, and any Flow synchronization for the authorized transition. The authorization binds the exact canonical project root, run, subject, pre-mutation bundle digest, critique IDs and hashes, expected resolving reviewer, nonce, request time, and expiry. Ambient runtime identity and actor overrides do not authorize this operation.

Requests and responses use strict single-line JSON envelopes. A response is accepted only when its protocol version, action, canonical request SHA-256 digest, status, and exact action-specific result match the request. Empty, multi-line, malformed, or extended responses fail closed. The helper must independently reject unknown fields/actions, canonicalize and constrain every received path, derive the run/project/artifact roots rather than trusting claimed relationships, and perform mutation only after its own locked precondition and compare-and-swap checks. Package-side graph validation never invokes a live verification subprocess or treats a helper response as authorization. It verifies the coordinator's pinned Ed25519 completion receipt and requires its result digest to bind the exact resolved graph before Builder consumes the transition.

For a signed critique resolution, the coordinator accepts up to 16 MiB only for the canonical Flow evidence manifest. The protected descriptor, O_NOFOLLOW, regular-file, group/world-mode, and mandatory JSON checks remain in force. Definitions, state, trust bundles, authorizations, journals, keys, and responses retain their smaller existing limits; this is not a general input-size increase.

Completion verification always reads the fixed public key at /etc/kontourai/flow-agents-lifecycle-authority-v1/completion-verification-key.pem; no command, authorization, or environment setting can choose another key. On Darwin only, the standard protected /etc alias resolving exactly to /private/etc is accepted. Every resolved component and the final O_NOFOLLOW Ed25519 key descriptor are validated as protected. Different alias targets, deeper or writable symlinks, non-root-owned components, all non-Darwin symlinks, and all helper-path symlinks are rejected. The helper itself remains pinned and symlink-free.

Administrators upgrade the direct source packaging/lifecycle-authority/coordinator.mjs through the privileged installer. They verify the installed coordinator's bytes and SHA-256 separately from the signed runtime digest, which continues to identify runtime-v1.mjs; the installer preserves its prior coordinator/runtime/pin/ reducer set for rollback. This operational path is administrator-owned and cannot be selected or overridden by package callers.

The helper and its provider-neutral key registry are deployed and configured by runtime administrators outside source control. Flow Agents ships neither helper configuration nor keys. Missing, untrusted, repository-local, or writable helpers fail closed. The privileged coordinator only verifies authority, serializes and records the operation, then gives a one-use signed capability to a worker running as the invoking non-root user; repository artifacts are read and written only by that unprivileged worker.

Use immutable metadata.critique_record_id values from the two trust-bundle critique records. The resolving critique must be verified, current against the workspace, later in the writer-issued predecessor hash chain, and cover every failed or not-verified lane plus every open finding from the earlier critique. When both reviews target Git worktrees, the resolving commit must descend from the earlier reviewed commit. The explicit per-lane and per-finding edges are the policy-defined relationship between the two reviews.

flow_agents workflow resolve-critique \
  --session-dir .kontourai/flow-agents/example \
  --prior-record-id '<earlier-critique-record-id>' \
  --resolving-record-id '<later-passing-critique-record-id>' \
  --authorization-file critique-resolution.authorization.json

The earlier record remains in trust.bundle with its original reviewer, findings, timestamps, superseded_by reference, and critique_resolution audit record. Repeating the identical valid request is a no-op. Missing, ambiguous, circular, stale, equal-snapshot, wrong-subject, or unauthorized requests fail without changing the bundle.

The signed authorization is consumed once under the subject lock. Resolution also appends a separately hashed event binding the authorization digest and exact edge. The local event chain is tamper-evident; deployments requiring non-repudiation should additionally retain the signed authorization and anchor the resulting state in their provider-neutral durable audit store.

Repairing an irrecoverable legacy authority-history gap#

workflow repair-critique-resolution-history-request and workflow repair-critique-resolution-history are deliberately distinct from ordinary resolution. They are available only for an already-superseded, cross-reviewer edge whose original resolve-critique event is absent from the external lifecycle-authority.resolution-events.json ledger. They do not reconstruct that original event, signature, timestamp, reviewer, or Trust Bundle claim. Instead, a newly signed repair authorization records the missing original event ID and authorization digest with the fixed disclosure reason coordinator-external-ledger-overwrite-v1.

The request binds the exact protected Trust Bundle bytes, external-ledger digest/length/tail, current signed completion digest, preserved edge digest, both critique records and hashes, reviewer, snapshots/heads, project/run/ subject, nonce, request time, and expiry. Changing any of those values, finding the original event, or finding a prior repair rejects without mutation. A successful repair appends exactly one repair-critique-resolution-history event, writes a new root-signed completion and Flow attachment, and leaves trust.bundle byte-identical. The strict graph accepts exactly one authority proof per cross-reviewer edge: either its original event or one repair for a missing original. Zero, both, duplicates, unmatched events, altered edge data, or an invalid event chain fail validation.

The historical bridge has three jointly required anchors: the root-signed historical completion; the canonical Flow manifest entry selected by that completion request SHA-256 plus its protected stored Trust Bundle snapshot; and the request-keyed root-only durable operation, completion, and applied nonce records. The coordinator accepts only one ledger prefix that reproduces the historical completion core with that stored snapshot. A missing or tampered durable record, attachment, snapshot, critique/edge projection, or an ambiguous prefix fails closed.

An old completion proves continuity for this repair request; it is never a current completion. Builder synchronization, artifact validation, ordinary resolution, and final gates continue to require an exact-current receipt and must reject it. This remains true when a later critique is appended without a new completion. Only a successful bridge produces the one new exact-current completion and Flow attachment those strict consumers can use.

The root-owned helper verifies both durable-state and historical anchors before it grants a one-use worker capability, then verifies them again immediately before mutation. The worker performs raw Trust Bundle and ledger compare-and- swap checks during preparation and again before publication. The request lock serializes operator actions: an exact completed request replays without overwriting a newer receipt; prepared recovery repeats the checks; and any session/Flow fault rolls back both artifact trees without a partial event or attachment. The serialized operator flow is read-only request generation, external Ed25519 signing, one helper invocation, then retention of the root-signed completion; neither a repository file nor package caller is a durable authority root.

# Read-only: emit the precise payload that an external operator must sign.
flow_agents workflow repair-critique-resolution-history-request \
  --session-dir .kontourai/flow-agents/example \
  --prior-record-id '<earlier-critique-record-id>' \
  --resolving-record-id '<later-passing-critique-record-id>' \
  > critique-history-repair.request.json

# After the operator adds the Ed25519 signature outside the worktree:
flow_agents workflow repair-critique-resolution-history \
  --session-dir .kontourai/flow-agents/example \
  --prior-record-id '<earlier-critique-record-id>' \
  --resolving-record-id '<later-passing-critique-record-id>' \
  --authorization-file critique-history-repair.authorization.json
flow_agents workflow pause --reason "Waiting for a decision"
flow_agents workflow resume --reason "Decision received"
flow_agents workflow release --reason "Handing work back"
flow_agents workflow cancel --authorization-file cancel.json
flow_agents workflow archive --authorization-file archive.json

workflow status is read-only. It reads the actor-scoped current-session pointer, the projected state, and the canonical Flow run without rewriting either store. Its next_action is freshly derived from the canonical run, so a stale sidecar projection cannot misdirect recovery.

For an actionable run, next_action separates the work from evidence submission and observation:

Field Responsibility
executor agent: the calling agent owns orchestration.
execution_action Execute the version/hash-bound skill sources, perform the declared operations through their authorized executors, and produce the declared artifact targets in the canonical stop-condition sequence.
evidence_submission Use the canonical public mutation bindings and parameters to submit genuine evidence for the unresolved expectations. Its schemas supplies the evidence-reference format and examples.
sync_command Run the version-pinned public status command to inspect canonical state after submission.
command, command_role Compatibility alias of sync_command, explicitly labeled synchronization.

Mutation parameter value_schema_ref values retain their canonical gate-action-envelope scope. In particular, #/public_interfaces/schemas/evidence_ref_json names the schema that a status response projects at next_action.evidence_submission.schemas.evidence_ref_json; it is not a JSON pointer into the status response. Driver clients resolve the reference against the supplied canonical envelope, while status clients use that explicit projected schema location.

workflow evidence records and synchronizes evidence for Flow evaluation. Status does not execute skills, produce artifacts, submit evidence, or advance a missing-evidence gate. Producing a file alone also does not satisfy its gate: submit its evidence through the declared mutation, then inspect status and execute the resulting next action. The action fields are projections of the canonical gate-action envelope, not a second step map or authority source.

An agent follows a continue action until the selected run completes or reaches a concrete blocker. Returning a delegated turn's result returns orchestration to its caller. Starting or inspecting a run does not establish a running external task or a notification mechanism; wait only when an actual task and its completion mechanism have been established. An external capability block retains its declared operation authority and cannot be replaced with invented provider evidence.

Bounded Continuation Driver#

workflow drive lets the active implementation assignment run multiple Flow steps without a human continuation prompt. Flow remains authoritative: the runtime adapter receives the current projected action, but its completed result means only that one model turn ended. The driver returns done only after the canonical Flow run is terminal.

The adapter command is an explicit JSON argv file whose executable must be an absolute path. Flow Agents invokes it directly without a shell, writes one versioned continuation-turn request to stdin, and requires exactly one JSON result on stdout:

{ "argv": ["/absolute/path/to/runtime-adapter", "--profile", "builder"] }
{ "status": "completed", "summary": "Turn ended; synchronize canonical evidence." }

A completed result may also include a generic JSON evidence object of at most 65,536 bytes. Flow does not interpret its domain semantics. Trust-sensitive callers can ask the long-lived driver to attest the exact request/result sequence it observed by providing a one-time Ed25519 private key:

flow_agents workflow drive \
  --session-dir .kontourai/flow-agents/example \
  --adapter-command-file .kontourai/flow-agents/runtime-adapter.json \
  --evidence-signing-key-file /absolute/protected/one-time-ed25519-private.pem \
  --evidence-checkpoint-dir /absolute/protected/empty-checkpoint-directory \
  --max-turns 6 \
  --json

The key file must be an absolute canonical regular file. The driver reads it no-follow and unlinks it before any adapter starts, retains the key only in driver memory, and adds an evidence_attestation to the final JSON outcome. That attestation carries the public key, a base64 payload containing the canonical outcome and ordered adapter requests/results, and an Ed25519 signature over the exact payload bytes. Consumers must compare the public key with the key they pinned before launch, verify the signature, and then validate any evidence-specific schema. Without this optional flag, the outcome is not externally authenticated.

An evidence checkpoint directory is optional and requires both the signing-key and JSON flags. It must be an absolute canonical, non-symlink, empty directory provisioned by the caller outside the model's writable workspace. After each accepted adapter result is canonically synchronized and measured, the driver publishes one bounded signed checkpoint there. Each checkpoint binds the exact accepted-turn journal record, the canonical gate projection at that point, its sequence, and the digest of its predecessor. Its machine-readable evidence_scope: accepted_prefix and drive_completion: not_attested fields prevent an accepted adapter result from being interpreted as proof that the drive returned. Publication uses an unpredictable same-directory staging file, fsync, and an atomic no-replace link to checkpoint-NNNNNN.json. Incomplete staging files are ignored. Consumers must pin the launch public key and verify the directory with the package's verifyContinuationEvidenceCheckpoints export. Hosts can derive the required pinned expectedAdapterCommandIdentity from the exact command file before launch with the package-root continuationAdapterCommandIdentity(commandFile) export. It applies the same executable, regular-file, and integrity binding used by workflow drive; consumers should not reconstruct that identity algorithm.

A verified checkpoint proves only the ordered accepted prefix and canonical state measured at its publication time. It does not prove that the whole drive completed. A later timeout or process termination may prevent a final aggregate attestation while leaving earlier checkpoints valid. Malformed final files, gaps, reordering, key mismatch, cross-run substitution, and signature or chain failures are rejected rather than projected as partial success.

Every JSON outcome also carries canonical_gate_projection, a compact observation copied from the final synchronized Flow state while the continuation lock is still held. It identifies the run and definition, current status and step, evaluated gate statuses, matched evidence ids, trust diagnostic codes, and accepted exceptions. This projection is final-output telemetry only: it is not included in continuation snapshots, adapter requests, or model context. When --evidence-signing-key-file is used, the projection is inside the signed outcome bytes; otherwise consumers may display it but must not treat it as authenticated proof.

An adapter may instead park on a process or deadline. A pending barrier is persisted and does not consume another turn when workflow drive is invoked again:

{ "status": "wait", "barrier": { "kind": "pid", "pid": 12345 }, "summary": "Waiting for the verification process." }
flow_agents workflow drive \
  --session-dir .kontourai/flow-agents/example \
  --adapter-command-file .kontourai/flow-agents/runtime-adapter.json \
  --context-policy fresh \
  --max-turns 6 \
  --turn-timeout-ms 900000 \
  --barrier-wait-ms 300000 \
  --json

--context-policy warm remains the default and requests one new adapter context for the mission, then resumes it. --context-policy fresh requests a new context for every bounded Flow action. Both policies pass the same canonical gate-action handoff and preserve the same Flow gates, evidence rules, mission budget, and adapter authority. The selected policy is persisted with the mission and cannot be changed on resume. Adapters must honor the request's context_strategy.thread value and must not infer context routing from model names. This is a context-management capability, not permission to drop gates, invent evidence, or replay a prior transcript into a nominally fresh context.

Driver state and its append-only event stream live under .kontourai/flow-agents/<slug>/continuation-driver/. The mission turn count survives reinvocation; subsequent invocations must use the same --max-turns value. The request contains the canonical run id, definition id, current step, projected next_action, iteration, and budget. Builder turns additionally carry one bounded top-level gate_action_envelope with immutable skill identities, declared artifacts/evidence, requirement satisfaction and unresolved ids, typed public workflow.evidence/workflow.critique argv or product-operation bindings, one-turn stop semantics, product-declared implementation policy, and prior canonical progress/stagnation. Parameter values are appended as separate argv entries; adapters must not perform string substitution into a shell command. The full envelope is request-only and is not duplicated in projected next_action or durable state.json; the execution, submission and observation fields above project its relevant bindings. It does not mutate or replace the runtime system prompt. Adapter errors are recorded as failed turns and fail open to canonical resynchronization and the next bounded turn; they cannot bypass the persisted mission budget. The Builder Flow projection supplies the canonical continue/wait/done/failed disposition, so the generic driver does not duplicate Flow lifecycle semantics. Human-decision and paused Flow states park, while remediable blocked and accepted-exception states continue; failed Flow runs stop as failed. Assignment ownership is revalidated before every adapter turn. A session-scoped process lock rejects concurrent drivers and safely removes unique lock files left by exited owners. The adapter argv plus the content digests of its executable and absolute regular-file arguments are bound to the mission on first invocation and rechecked before every turn. Adapter process groups are terminated after every non-wait result and on timeout, output overflow or transport error. The driver retains custody through the 250 ms termination grace period even if the leader exits first; escalation checks original group member identities before sending SIGKILL. A valid wait result preserves the background process for its declared barrier. POSIX hosts must provide /bin/ps with PID, process-group and start-time fields; this capability is checked before admitting an adapter worker. In addition, state rollback or deletion is rejected when its append-only event history proves turns already started. These local coordination records detect accidental or in-process rollback; they are not a cryptographic boundary against a process that can rewrite the entire artifact directory.

Authenticated publish-change#

publish-change is a provider-neutral product operation with a GitHub gh-CLI adapter as the first implementation. It is not a generic operation runner and it is not a generic evidence interface. A configured status projects the executable argv beginning with:

flow-agents publish-change execute --session-dir .kontourai/flow-agents/<slug>

The status projection supplies this operation only when its effective ChangeProvider is configured and compatible. It then describes the bounded --title, --body, --head-ref, --base-ref, and optional --draft inputs. The command accepts those named flags only; it does not accept a result JSON, a caller-selected expectation or operation id, or a private-writer switch. It derives the repository, immutable head SHA, current assignment actor, canonical run, and current gate-visit identity from the active session rather than trusting caller copies.

Configuration is explicit. Project settings live at <repo-path>/context/settings/change-provider-settings.json; optional machine-global settings live at $HOME/.config/flow-agents/change-provider-settings.json. Resolve and inspect the effective settings without running a mutation:

flow-agents effective-change-provider-settings --repo-path . --json

Settings are deep-merged in this order, where each later value wins:

  1. global defaults
  2. matching global project entry
  3. project defaults
  4. matching project entry

The currently supported configured provider declares role ChangeProvider, kind github, capabilities change.create and change.observe (plus change.merge to use merge-change), and executor gh-cli; credentials remain in the authenticated gh environment, never in these settings. An absent configuration remains external_capability_required; an invalid or incompatible configuration remains unavailable with its compatibility reason. Neither state exposes an executable completion claim, and both must remain an explicit external capability/verification gap rather than a successful publish.

For a configured provider, the GitHub adapter resolves gh only from fixed trusted absolute locations (never caller-controlled PATH), authenticates it, queries pull requests by configured repository, base ref, head ref, and immutable head SHA, and verifies the title, body, and draft intent before returning a result. It creates only when no exact match exists. If creation has an ambiguous failure (for example, a timeout after GitHub accepted it), it queries again and recovers exactly one matching published pull request. An exact open record supports the normal path; an exact merged record supports truthful reconciliation when provider work completed before the local workflow caught up. Multiple candidates, wrong repository/base/head/SHA/intent, a closed-but-unmerged record, malformed output, or an unauthenticated provider fail rather than selecting or creating another record.

The durable result is bounded to 65,536 bytes and is written only by the Flow-owned completion transaction as publish-change.result.json. It records the bound run/definition/step/gate visit, provider kind/configuration/adapter, repository, provider record id and number, HTTPS URL, normalized published state (open or merged), base ref, head ref and SHA, the bound assignment_actor, the authenticated GitHub provider_actor, and observation time. Title, body, and draft are authenticated request intent rather than free-form result fields. Before persistence, Flow reacquires the subject lock and revalidates assignment ownership, active gate visit, exact request binding, and effective provider configuration; it then re-observes the provider record, attaches only pull-request-opened, requires that bound evaluation to advance exactly one canonical step, and projects the resulting state.

For a run whose definition pins its source head (builder.deliver-existing), the same operation adopts instead: it takes only --session-dir, --head-ref, and --base-ref, exercises only change.observe (the status projection advertises that capability and those three flags), never runs a create, and does not compare or set the title, body, or draft flag. It accepts exactly one open, same-repository pull request at the pinned head, the one the run's pre-existing-implementation claim names; see Review, verify, and merge an existing change.

A caller-authored publish-change.result.json, generic adapter JSON/evidence, or a package internal/private writer cannot complete this gate. Provider/authentication failures leave the gate unresolved; retry the public operation after correcting the provider condition, relying on the exact-match recovery protocol rather than a blind second create. Provider stdout/stderr and authentication material are deliberately excluded from result artifacts, trust bundles, diagnostics, logs, and test snapshots. Local deterministic coverage does not itself prove a live provider operation; privileged live recovery remains a separate verification activity.

Authenticated merge-change#

merge-change execute --session-dir <session> --strategy <squash|rebase|merge-commit|merge-queue> --authorization-file <signed-file> is the supported mutation path for a configured ChangeProvider that explicitly declares change.merge. Readiness records a decision; it never authorizes or performs a merge. First create a read-only signing request, sign its exact payload with a registered lifecycle-authority Ed25519 key, and add that signature to the request file:

flow_agents merge-change request \
  --session-dir .kontourai/flow-agents/example \
  --strategy squash \
  --out /absolute/outside-project/merge-change.authorization.json

flow_agents merge-change execute \
  --session-dir .kontourai/flow-agents/example \
  --strategy squash \
  --authorization-file /absolute/outside-project/merge-change.authorization.json

The protected lifecycle coordinator validates and consumes this signed, single-use authorization under the session subject lock immediately before provider mutation. It binds the exact run, definition identity/digest and Flow state, repository/PR, terminal head, strategy, assignment actor, publication-time expected provider actor, issued action digest, nonce, request time, and expiry. Repeating the same operation after a crash can return only its exact durable replay; it cannot authorize a different head, strategy, provider, PR, actor, or request. A read-only merge-change validate-terminal-delivery --session-dir <session> --head-ref <ref> diagnoses the local terminal-delivery binding without calling a provider.

The evidence-refresh gate is the single gate in the run's effective definition that sets requires_current_verification: true; its kit may choose the gate identifier. Zero or several declaring gates refuse the signing request. The existing bounded refresh routes to verify and post-amendment verify-gate provenance remain required by both the unprivileged CLI and the protected coordinator.

Target-branch policy precondition#

Automated merge requires the target branch to enforce a no-bypass pull-request approval policy (required_pull_request_reviews with required_approving_review_count >= 1, plus enforce_admins). merge-change request observes this precondition against the configured provider before it mints an unsigned authorization and refuses when the target branch does not satisfy it, naming the actual condition (approval policy absent, zero required approvals, or enforce_admins disabled) rather than a type error. When the provider cannot be reached the request reports merge_policy_precondition.status: "unverified" with the reason instead of passing silently; merge-change execute re-checks the same policy against the provider immediately before mutation. Ruleset-only configurations that do not expose an equivalent no-bypass policy remain deliberately unsupported rather than guessed.

The practical consequence for a rollout: a repository whose only write-access account authors every pull request cannot satisfy this precondition, because GitHub forbids approving your own pull request and enforce_admins leaves no bypass. Such a repository needs a second approving reviewer with write access (or an app/bot with write access that can submit approving reviews) before merge-change can merge; until then its merges remain a disclosed provider operation performed outside merge-change. Enabling required reviews on a target branch is a repository policy decision with consequences for bot-authored pull requests (release automation, dependency bots) and is deliberately not something the kit changes.

The enforced order is publish change, provisional delivery if CI needs it, exact-head checks, readiness, learning, terminal delivery on the same source branch, commit/push its delivery-only companions, refreshed required checks on that exact terminal head, then merge. The operation requires a completed canonical Builder run that semantically contains the merge-ready-ci evidence-refresh control (missing_evidence and default route to verify, bounded by a block-on-exceeded policy) and passing refreshed verification evidence, the current matching assignment actor, a clean source worktree, byte-identical session/working-tree/committed terminal companions, a checkpoint-bound in-toto or DSSE companion, and an ancestor-bound delivery-only delta. It rejects source drift, a changed head, provider-configuration drift, provider-actor drift, zero required checks, and non-passing required checks before mutation. Squash, rebase, merge-commit, and merge-queue each use exact-head provider behavior; no tree-equivalence or post-squash delivery-only provenance relaxation is accepted.

Immediately before spawning an adapter turn, the driver writes a transient, schema-versioned active-turn.json beside its mission state and passes a raw 32-byte turn secret plus the path-safe, signed run id in FLOW_AGENTS_CONTINUATION_TURN_SECRET and FLOW_AGENTS_CONTINUATION_RUN_ID to that child. Only the secret's SHA-256 is persisted in the signed record. The driver's schema-1-compatible mission state separately stores the ephemeral public-key digest before the adapter starts, so replacing and correctly re-signing the entire active-turn record with another key does not replace the signer anchor. The driver clears that digest with the active-turn step on every completion, error, cleanup, terminal, waiting, and budget path. It replaces every inherited continuation capability variable but preserves ordinary FLOW_AGENTS_ACTOR. The shared actor resolver remains ordinary: it never accepts continuation data as an identity override. When an assignment-gated public workflow command finds that ordinary resolution does not match, it may use only a live signed active-turn record bound to the exact session, turn secret, run id, assignment file and actor struct, mission, adapter identity, expiry, and driver lock; it returns the signed assignment identity only for that active-turn evidence gate. Pause, resume, release, cancel, and archive remain control-plane operations: they require ordinary actor identity or their existing explicit external authority and never accept this turn capability. The private key remains only in the driver process. The Stop hook resolves the signed run id under the canonical artifact root and fully validates that exact session before consulting ordinary current pointers. Once the base signed turn remains valid, that exact session stays selected even if canonical Flow has become paused, blocked, completed, or another canonical disposition; Stop never falls back to a conflicting actor or global pointer. It securely validates canonical state against the run's definition and treats only canonical active as authority to make the ordinary unfinished canonical-gate warning advisory so the adapter can return control to the driver. It never advances a Flow gate, releases assignment or liveness for a continuation-owned nonterminal run, or relaxes evidence, integrity, false-completion, malformed-state, or configuration blocks. The record is removed when the child completes, errors, or times out; parent identity changes leave it to expire instead of unlinking through a replacement.

This is cooperative same-user protection, not cryptographic filesystem isolation from a hostile same-UID process. A same-UID process that can rewrite both mission state and authority records or control the driver process remains outside this boundary. Within the cooperative boundary, the mission digest anchors the driver's ephemeral signer, while descriptor reads, final-path inode rechecks, and realpath/device/inode parent rechecks detect practical replacement races. Active-turn and lock records are capped at 16 KiB; canonical assignment and continuation mission records are capped at a conservative 1 MiB so valid provider metadata is not rejected. PID liveness is best-effort only: PID reuse and same-UID process control remain outside this local coordination boundary.

The event stream records turn_completed, gate_not_advanced, turn_failed, and best-effort authority_cleanup_failed. Cleanup failures are audited but do not replace the adapter or canonical outcome. Failed turns carry failure_kind of timeout or adapter_error; a completed adapter turn whose canonical run remains active at the same current step records gate_not_advanced. These events describe driver execution only and do not change canonical Flow state. Adapter-returned evidence is not interpreted as gate evidence; only the public workflow evidence path can attach evidence for Flow evaluation. Same-step canonical evidence or declared-artifact hash changes are recorded as progress, while repeated no-progress turns are classified as possible stagnation and then stagnant without fabricating a gate outcome. Progress uses run-wide canonical evidence/artifact manifests and resumes from the durable last_progress baseline after interruption or reinvocation. Kit metadata, skill source, and observed artifact reads are bounded descriptor-stable regular-file reads that reject symlinks and identity changes. Request-facing declared and required artifacts exclude control state.json, even where legacy kit ownership metadata retains it. Kit validation and envelope construction both cap an action at 16 skills and a flow at 128 distinct observable file artifacts, excluding virtual trust-bundle refs and control artifacts consistently. Synchronization measures an interrupted, waiting, terminal, or callback-failed turn before clearing its recovery marker. Evidence-free canonical gate evaluation is limited to accepted exceptions and gates whose effective expectations are all optional; ordinary missing-required gates remain unevaluated. Signed drives reserve aggregate attestation capacity before adapter execution when another bounded signed result cannot fit. When a protected checkpoint directory is supplied, each accepted turn is also signed and published atomically after canonical measurement. These records prove an accepted prefix only; canonical terminality still comes from the final synchronized outcome. The complete serialized adapter result is capped at 74,000 bytes, and preflight reserves that exact maximum plus the actual request and JSON structure. Accepted request/result pairs and measured progress are journaled durably before the active marker is cleared. Restart idempotently completes missing audit writes, and signed attestation fails closed when persisted accepted events lack journal coverage.

Compatibility Doctor#

Run doctor through the exact isolated package helper defined above:

flow_agents workflow doctor --json

The report distinguishes the executing CLI from a repository-local dependency and reports the workflow/writer contract, installed hook and writer package, active Kits, the content and declared Flow definitions of each active Kit (and of any other packaged Kit copied into the project; kit reports the first and kits all of them), the current run's declared Flow source and version, workflow-state schema, and trust-bundle schema. The current-run source lookup checks declaration ownership and path agreement; it does not verify composed-flow admission, producer bindings, or runtime readiness. Incompatible or missing installed components produce a nonzero exit and an exact, version-pinned init command that preserves the recorded runtime and active Kits.

Without a current run, doctor reports the kit-declared default selected by the same rule as workflow start without --flow. If that selection refuses (for example, no default, competing defaults, or an unreadable kit tree), doctor emits a warning and reports a null definition; startup refuses before creating a session. A current run retains its recorded definition and does not consult startup defaults for this diagnostic.

The package may use lower-level writer modules internally. They are not a supported consumer or skill surface. Consumer guidance and Builder skills use flow-agents workflow exclusively.

Sealed workload execution#

workflow execute-sealed-workload-request emits the readable unsigned authorization and an exact compact canonical-JSON signing_payload for a separately registered Ed25519 signer. The signer signs the signing_payload bytes verbatim and attaches that signature to the authorization. The command derives the subject from the bound one-work-item session (an optional --subject must match it), binds the canonical project/run/session identity and exact workload digest, and requires every finite budget flag explicitly. It never grants coordinator maxima by default. The public execute command rereads the exact signed authorization, computes request and authorization digests, and prints only a completion verified against that exact safe result.

The root coordinator stages the signed closure under a root-owned per-operation leaf of a non-listable execution root, clears supplementary groups in its fixed native privilege-drop launcher, and removes and awaits the process group on every terminal outcome or forwarded cancellation before leaf cleanup. The signed controller/closure, local signing principal, and invoking OS account are trusted local TCB. Staging provides exact-byte integrity and replay control; it is not a secrecy boundary against another process already running as the invoking UID. Deliberate setsid escape by a hostile signed controller is likewise outside this proportional local threat model. The public command never returns raw provider material: retained artifacts are closed structured values only—booleans, finite numbers, hashes, and a fixed status vocabulary. Tracking: #1146.

Source: docs/public-workflow-cli.md