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
Blocked
Blocked
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.
Exhausted
Exhausted
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.
Gated
Gated
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.
ReviewRequired
ReviewRequired
Not a refusal. A person has to decide before this proceeds. Carries a
review_id to park on and resume from.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.
