When the Gateway refuses a call or holds it for approval, the agent receives a refusal. Depending on the agent disclosure level, that refusal also carries an explanation of the policy decision: which rule decided, and possibly the thresholds involved. A clear explanation helps an agent recover (ask for approval, try a smaller amount, stop). It also tells the agent how your policy works, which it could use to work around it.
People always see the full explanation. The disclosure level only controls what the agent receives.
Disclosure levels
| Level | Dashboard name | The agent's explanation contains |
|---|---|---|
none | Refusal only | Nothing beyond the refusal itself (see the note below). |
rule | Rule and approval details (the default) | The policy's name, version and digest; the enforcement level; the decision and the policy's own effect; the deciding rule's ID and description (or "the default", or the check that refused the call), and the currency of a rule that applied fail-closed; for a held call, the approver roles, the number of approvers and the time limit; the end of an observe window; a message without condition values. |
conditions | Rule and thresholds | Everything in rule, plus the deciding rule's conditions with their thresholds and the call's values, and the amount band around the call in which the decision stays the same. The message includes the values. |
At every level, the agent's explanation never contains user IDs, delegation data, the other candidate rules, rules that did not apply, or a rule's tool list. Tool arguments are never used to build explanations.
Rule descriptions are sent to agents at rule and conditions. Never put secrets in a rule's description; the editor warns you when a document has descriptions (see Descriptions and guards).
What the agent receives
A refusal is an MCP tool result with isError: true. Its structuredContent.kblock holds the status (for example denied or approval_pending), the code, the detail and, when applicable, the action and approval request IDs and the expiry. See Action statuses and Error codes.
At rule or conditions, refusals of a recorded decision (denied, approval_pending, rejected, expired, cancelled) also carry the explanation as structuredContent.kblock.policy (a kblock.cloud/agent-explanation@1 document) and its message as a second text item starting with Why:. Refusals produced immediately before execution, such as POLICY_CHANGED after a policy change, carry no explanation.
For example, a 250 USD refund held for approval under a rule with a description produces this explanation at rule:
{
"schema": "kblock.cloud/agent-explanation@1",
"disclosure": "rule",
"policy": { "name": "Support refunds", "version": 3, "digest": "sha256:…" },
"enforcement": "enforce",
"decision": "require_approval",
"policy_effect": "require_approval",
"decided_by": {
"kind": "rule",
"rule_id": "refund-approval",
"description": "Refunds above 100 USD need a support lead"
},
"approval": { "roles": ["owner", "admin", "approver"], "approver_count": 2, "ttl_seconds": 14400 },
"message": "Rule 'refund-approval' of policy 'Support refunds' v3 requires approval. — Refunds above 100 USD need a support lead"
}At conditions, the same decision adds:
{
"conditions": [
{ "field": "action", "op": "matches", "pattern": "refund.issue", "value": "refund.issue" },
{ "field": "amount", "op": "gt", "value": { "amount": "250", "currency": "USD" }, "threshold": { "amount": "100", "currency": "USD" } },
{ "field": "amount", "op": "lte", "value": { "amount": "250", "currency": "USD" }, "threshold": { "amount": "1000", "currency": "USD" } }
],
"amount_band": { "currency": "USD", "gt": "100", "lt": "1000" },
"message": "Rule 'refund-approval' of policy 'Support refunds' v3 requires approval: amount 250 USD > 100 USD, amount 250 USD ≤ 1000 USD. — Refunds above 100 USD need a support lead"
}A successful call under an observe window carries _meta.kblock.policy with enforcement: "observe" and whether it was relaxed; at rule or higher it also gives the policy's own effect, the rule and the window's end. Under normal enforcement, successful results carry no policy information.
The level is fixed per decision
The explanation is computed and stored when the call is decided. When a call returns that recorded decision again (the same idempotency key, or a retry that resumes a pending approval), the agent receives exactly the same refusal, even if you changed the disclosure level meanwhile. A change applies to decisions made from then on, including the new decision a denied call gets when the agent retries it without an idempotency key.
The organization ceiling and the policy setting
Two settings combine. The agent receives the lower of the two:
| Setting | Where | Values | Default | Who can change it |
|---|---|---|---|---|
| Organization ceiling | Settings → What agents see of a decision | Refusal only, Rule and approval details, Rule and thresholds | Rule and approval details (rule) | Owners and admins |
| Policy setting | Policy → Editor tab → What agents see → Agent disclosure | The organization's setting (inherit), none, rule, conditions | inherit | Owners and admins |
A policy can show less than the ceiling, never more: with a rule ceiling, a policy set to conditions still gives rule.
Both changes require a signed-in owner or admin; management API keys cannot change them. Through the API: PATCH /orgs/:orgId with agent_disclosure (none, rule or conditions) for the ceiling, and PATCH /orgs/:orgId/policies/:id with agent_disclosure (inherit, none, rule or conditions) for a policy. They are recorded as organization.disclosure_changed and policy.disclosure_changed audit events.
The disclosure setting never changes a decision. It is not a version of the policy: changing it creates no new version and needs no activation.
What people, webhooks and receipts see
| Audience | What it sees |
|---|---|
| Members in the dashboard and through the API (any role), and management keys with a read scope | The full explanation (kblock.cloud/explanation@1) on action and approval pages, in dry runs and in simulations: every candidate rule, the conditions with their values, rules that did not apply. |
| Webhook endpoints | For action.allowed, action.denied, approval.requested and action.observe_override: a policy object with the policy ID, version, digest, rule ID, the policy's effect, the enforcement level, whether the call was relaxed and the observe window. action.denied also carries the reason code and message, which names the deciding rule. Never the explanation. |
| Receipt holders and upstream tools | The signed task's policy fields (policy, version, digest, decision, rule, effect, enforcement level, observe window) are the same at every disclosure level. See What a receipt proves. |
Sometimes a new decision gets only a minimal explanation: when K-Block has switched detailed explanations off, or when the full explanation cannot be built, for example because it would be larger than 16 KiB. A minimal explanation contains less than this page describes, for people and agents alike; the decision itself never changes. Action and approval pages mark it with Short explanation: detailed explanations are switched off on the Gateway. or The detailed explanation could not be built. The decision itself was not affected. If explanations keep looking shorter than described here, contact K-Block support and give them the action ID of an affected decision.