Skip to content

Cookbook

Cookbook: HITL approval flow

Follow a human-in-the-loop approval from request to decision, including risk tiers, duty separation, plan hashes and break-glass.

Last updated:

The HITL gate ensures no high-risk agent action proceeds without human judgment. This cookbook walks the full approval lifecycle — from the moment a governed action fires a request through risk-tier resolution, human decisions, and the break-glass escape valve — so you can trace every moving part before you encounter it in production.

If you have not read the authorization model yet, start with Governance. The guide Govern & approve covers the same machinery from an operator perspective; this page is the step-by-step reference.

The request lifecycle

An approval request opens deny-closed and time-boxed. It starts pending, carries an expiry, and authorizes nothing until enough humans have decided it. The request cannot lower its own bar — a matching approval policy is authoritative for the threshold and timeouts.

The request is bound to a plan hash of the exact tool-call payload. This is the anti-TOCTOU defense: if the underlying action changes between request and decision, the hash will not match and the approval cannot bind. A stale approval never authorizes a different action.

Risk-tier resolution

Four tiers drive how much human control a governed action requires:

TierWhat it means
low / mediumAssigned only by explicit approval policy — nothing defaults here
highThe default for anything that reaches the approval queue. One human approval required
criticalMandatory two-person floor (NIST SP 800-53 AC-3(2) dual authorization)

The tier is re-derived from the live policy set at every decision, never read from a stored snapshot. A policy change takes effect immediately. A stale row can never hold the bar lower than the current classification — this is deny-closed by construction.

The built-in critical set covers the genuinely irreversible: production deploy and retire, data deletion and erasure, security-enforcement and kill-switch changes, key custody and rotation, and NHI offboarding.

An approval policy with an explicit risk_tier can raise or lower the default per action. But it can never lower a critical action below the dual-control floor.

Step by step

1. A governed action triggers a request

A governed action — deploy apply, agent fire, budget override, or any action matching an approval policy — reaches the approval engine. The engine opens a new request in pending state.

2. The request binds to a plan hash

The request records a hash of the exact tool-call payload. This plan-hash binding is the TOCTOU defense: the decision will only authorize the action the request was created for.

3. Risk tier resolves from current policy

The engine evaluates the live policy set (RBAC, native ABAC, and any external PDP) to derive the risk tier. The tier is never cached from an earlier evaluation.

4. A human reviewer decides

A human with sufficient role issues an approve or deny. The server enforces three invariants at this point, keyed on stable user identity:

  • Separation of duty. The requester cannot decide their own request. This keys on the stable user identity, not the credential string a single person could vary.
  • Duplicate-decider guard. A unique index backstops a duplicate-decider race — one human counts once toward the threshold.
  • Expiry binds. An expired request can never receive a binding decision, even before a sweep materializes the expiry. The effective status is re-derived at every decision.

5. Critical actions: dual-control with AAL3

For critical tier: the threshold is floored at two distinct human approvers. The floor is enforced both at create (the stored threshold can never start below two) and re-applied at decide (a request created before the action became critical still cannot cross with one human).

Each deciding human must have a fresh hardware-verified session — WebAuthn or PIV step-up (AAL3). A system token carries no human assurance and is refused.

6. Decision records to the ledger

Every decision appends an immutable row to the request’s decision trail and a ledger event recording the decision, the resulting status, and the risk tier under which it was made. The ledger is append-only, hash-chained, and Ed25519-signed — rewriting history is cryptographically detectable.

Expiry

Expiry is derived at read, not written once. An expired request can never receive a binding decision even if no background sweep has materialized the expiry yet. The effective status is always re-derived, so a slow sweep is a cosmetic delay, not a security gap.

Policy configuration

Approval policies let you override the default risk tier per action class. A policy with an explicit risk_tier can move an action from high to low, or raise a medium to critical. The one constraint: you can never lower critical below the dual-control floor. The two-person requirement for irreversible actions is structural, not configurable.

Break-glass

When a quorum is unreachable — the 03:00 incident where the second approver is asleep — break-glass provides an audited escape valve. It is loud by construction.

Activation requires all of:

  • The activator is an admin, always a real human — a system token is refused.
  • A fresh AAL3 step-up (WebAuthn or PIV).
  • A written justification recorded in the same transaction.
  • An actively recorded session as a precondition.

Time-boxing: the grant defaults to one hour, hard-capped at 24. An emergency that needs longer is an operating mode, not an emergency, and must go through normal dual-control.

Every use is recorded. Each action taken under break-glass appends an immutable row and a ledger event naming the grant, the action, and the subject. An action that proceeded under break-glass is permanently distinguishable from a properly approved one.

Forced post-review. A new grant cannot be activated while any prior grant is unreviewed. The review must come from a different human than the activator. You cannot stack emergencies to dodge scrutiny.

Break-glass relaxes a missing quorum; it never overrides an explicit rejection. A request someone deliberately denied stays denied.

What is recorded

Every decision — approve, deny, expire, or break-glass use — appends to two places:

  1. The request’s decision trail: an immutable sequence of decision rows on the request itself, each carrying the decider, the verdict, and the timestamp.
  2. The audit ledger: a hash-chained, Ed25519-signed event recording the decision, the resulting request status, and the risk tier under which it was made.

Both writes happen in the same transaction as the state change. You cannot make a governed decision the ledger silently forgets.

Search documentation