<!-- https://avertgate.com/docs/decisions · How it works -->

# Decision types

Every call produces exactly one decision. It always has the shape `{ decision, reason, ruleId }` and is written to the audit log.

| Decision | Meaning | What the agent sees |
| --- | --- | --- |
| ALLOW | The call passed policy and inspectors; the tool ran. | The (scanned) tool result. |
| BLOCK | Policy violation, secret/PII, injection or loop. In the request phase the tool is *not* run. In the response phase (suspicious result) the tool already ran; only the result is withheld from the agent. | A safe error message. |
| ESCALATE | The call awaits a human's approval (Slack). If approved it proceeds; if denied or timed out it becomes BLOCK. | A result or an error depending on the outcome. |
| SHADOW_BLOCK | "Would have been blocked" in shadow mode. The call is *not* stopped. | The normal result. |
| SHADOW_ESCALATE | "Would have gone to approval" in shadow mode. The call is not stopped. | The normal result. |

## The decision object

<!-- decision.json -->
```json
{
  "decision": "BLOCK",
  "reason": "argument \"amount\" = 750 exceeds max 500",
  "ruleId": "issue_refund"
}
```

`ruleId` points at the rule that decided: usually the tool name, or a detector name such as `loop-breaker`, `dlp_exfiltration`, `drip-exfiltration` or `ingress_prompt_injection`.

## How a blocked call reaches the agent

Through the MCP proxy the agent receives a standard tool error containing the reason. In the SDK an `AvertGateSecurityError` is thrown; with `catchErrors: true` it is returned as `{ error: "..." }` instead (handy for feeding back into an LLM loop).
