Concept 5 of 10

'Don't ship until tests pass' — declared once, enforced everywhere.

Your coding agent ships before CI is green, deploys on a red build, approves its own PR. You've been re-writing 'only after the tests pass' into every tool and hoping it sticks. Declare it once as a guard the agent can't talk its way past.

The ship guard: spell out what "ready" means, once

Take the move you don't want your agent making on a whim — shipping. Here the ship transition carries a guard: a check the runtime runs before the move is allowed to fire. To ship, three things have to be true at once — the actor holds the prod.deploy permission, the test suite actually passed, and coverage is over 80%. Miss any one and the move is refused. You declare it here and only here; you never re-write "don't deploy until CI's green" into each tool again.

ship-guard.yaml
checked:
  transitions:
    ship:                                # offered only in the 'checked' state
      target: shipped
      guards:
        - kind: allOf                    # every guard below must hold
          guards:
            - { kind: permission, permission: prod.deploy }
            - { kind: evidence, requires: [tests_passed] }
            - { kind: expr, expr: "$.context.coverage >= 0.8" }

That one guard is composed of the kinds flowgate gives you: permission and role (who is acting), expr (a plain condition over the workflow's shared state — its context), and evidence (proof an earlier step really ran). You stack them with allOf (all must hold), anyOf (at least one), and not. A move with a guard that fails is refused with GUARD_REJECTED — distinct from INVALID_TRANSITION, which is what you get when you reach for a move that isn't declared in this state at all.

Evidence: proof the runtime checks, that the agent can't fake

evidence is the piece worth slowing down for. Think of it like a required status check or a required PR approval — except it's the runtime that enforces it, not a setting your agent can route around. An evidence record is proof that earlier work really happened: the tests_passed record only exists because run_check actually ran your suite and it came back green. The agent can't assert it; it has to have been earned. The guard can't pass on a promise — it needs the record. And when one approval isn't enough, count: 2 requires two, not one.

publish.yaml
request_publish:
  guards:
    - kind: evidence
      requires:
        - { kind: human_approval, count: 2 }   # two approvals, not one

The move the agent should never make: approving its own PR

Some moves shouldn't be the agent's to make at all — merging to main, approving its own pull request. Mark the transition actor: "human" and the agent is never even offered the link forward; if it reaches for the move anyway, the runtime rejects it with ACTOR_MISMATCH. This isn't a line in the system prompt the model agrees to and then ignores two turns later — it's a hard gate, and the only path forward belongs to a person. (How airtight that is depends on how you run flowgate — the run modes spell out what each one enforces.)

wire.jsonl
→ flowgate.command  { "transition": "approve", ... }

← { "error": { "code": "ACTOR_MISMATCH",
               "message": "transition 'approve' requires actor: human" } }

Concept 5 of 10

Next concept →

Audit & replay

Every move the model made, on the record and replayable.

See all concepts