Skip to main content
An approval is a human’s yes to one exact action, bound to that action’s hash, single-use, and expiring. It is consumed in the same atomic write that reserves the effect key, so an action that differs from what the human saw matches nothing, and an action presenting an approval that was already spent is refused.

The four invariants

What the human sees, and what executes

The approval is left granted after a mismatch. It still authorizes the €2,000 action, and only that, until it expires.

Where the yes comes from

ctrlrun approve <request id> writes the grant. So does a webhook answer from Slack, and so does an adapter routing the request through LangGraph’s interrupt() or the OpenAI Agents SDK’s tool-approval interruption: every path ends in the same two store calls, grant_approval and deny_approval. There is never a second place to say yes, because a second approval path is one nobody is watching. Where a framework carries the arguments the human answered against, the adapter hands them back and CTRLRun rebuilds the hash and compares: that is prevention. Where it carries only the verdict, CTRLRun records who answered and cannot re-check what they answered about: that is attribution, and the adapter’s page says which it is, in that word.

The guarantee it supports

Approval binding, verified as G1 (mutated approval refused) and G2 (replayed approval refused) by ctrlrun verify against your own policy, wherever it has an approve rule.

What it does not do

An approval does not prove the human was right, or that the human was who they said; CTRLRun records the approver as given and authenticates nobody. It does not survive a policy change in one direction: if the policy now denies the action, the action is refused and the approval is left granted; if the policy now allows it outright, the action runs and the approval is invalidated so it cannot outlive the action it was for.

Next