Escalation & human-in-the-loop
ESCALATE parks a request for a person to decide — it is a hold, not a denial, and no budget is reserved while it waits. The one integration mistake this guide exists to prevent: treating an escalation as a failure instead of pausing for its outcome.
What actually happens on ESCALATE
The verdict carries an escalationId and no authorizationId — nothing has been permitted yet, so nothing is held against the mandate's cap. Approval requires M of N distinct approvers (configurable per agent, defaulting to 1), optionally restricted to a named role and authority level. An unresolved escalation expires: ESCALATION_EXPIRED.
Poll it, don't guess at it
- GET /policy/escalations/:id/status — PUBLIC, keyed by the escalation id itself, which is the capability; no user token needed to check your own held action
- Returns status, reasonCode, authorizationId, expiresAt, approvals, required
- GET /policy/escalations — the review queue an owner or approver works from
- On approval the gate RE-RUNS the cap check and mints a fresh authorizationId — approval does not bypass the budget a slow approver's delay may have let something else consume
In the guard and the Python client
Both reference implementations turn this into one blocking call: the guard's escalationStatus() polls for you; the Python client's wait_for_escalation() does the same and returns only once resolved. Act only on an approved status carrying an authorizationId — a "pending" is not a yes, and it is not safe to proceed on the strength of having asked.
See it in code
const decision = await guard.authorize({
action: "flight-purchase", amount: 150, currency: "USD",
merchant: "skyward-air", context: { riskLevel: "high" },
});
if (decision.decision === "escalate") {
// Blocks until an approver resolves it, or it expires.
const resolved = await guard.escalationStatus(decision.escalationId, { pollUntilResolved: true });
if (resolved.status !== "approved") throw new Error(`refused: ${resolved.reasonCode}`);
// Only NOW has anything been authorised — resolved.authorizationId is fresh,
// re-checked against the cap at approval time, not reused from the original request.
}