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": "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).