Skip to main content

The hierarchy

Exhausted and Gated subclass Blocked, so order your handlers from specific to general. A bare except Blocked placed first swallows all three and you lose the reason, which is the only field that tells you what to do next.

What each one means

The policy on the grant refused this action. Genuinely a policy conversation: either the agent should not be doing this, or the policy is drawn wrongly. Both are worth knowing and they look identical from a log.
The action was permitted and there was no room left, on spend or on call count. Retrying cannot help, and that is the point of it being a separate type. An agent that retries an exhausted grant is burning time against a ceiling that will not move until the window rolls or somebody raises it.
Untrusted content entered this trajectory and this action requires a clean context. The remedy is not to retry but to look at what was read and where it entered. See Clean context.
Not a refusal. A person has to decide before this proceeds. Carries a review_id to park on and resume from.
Rotascale could not be reached for a decision. Enforcement fails closed by default, so this raises rather than silently allowing an ungoverned action.

Why not one exception with a code

Because the four refusals send an operator to four different places, and a single type invites a single handler. “You were not allowed to do that” and “you had run out” have different owners, different remedies and different urgency. Merging them costs you the diagnosis at exactly the moment you need it.

Suppressed decisions

Decision.suppressed is true when a refusal was recorded but not applied, which is what observe and shadow do. The action proceeded; the record says it would not have. That flag is how you measure a rung before you climb it.