Concept 5 of 10
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.
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.
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 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.
request_publish:
guards:
- kind: evidence
requires:
- { kind: human_approval, count: 2 } # two approvals, not one
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.)
→ flowgate.command { "transition": "approve", ... }
← { "error": { "code": "ACTOR_MISMATCH",
"message": "transition 'approve' requires actor: human" } }