Skip to main content
A ban is an operator-controlled block that stops an agent from running at all. Unlike a policy deny — which decides whether a single tool call is allowed while the agent runs — a ban refuses the whole run before the model executes. No tokens are spent, no tool fires, and the ban wins over any allow or approval_required decision the policy would have made. Bans are the “stop it now” switch: an agent is misbehaving, or an end-user is abusing your product, and you need it off across every conversation without editing and re-signing a policy. There are exactly two ban types, both scoped to a single project: A user ban matches the user_id from the request context your integration supplies at runtime. If a run has no HexgateContext, only the agent dimension is evaluated.

Ban vs. policy

A ban is a separate primitive, evaluated around policy rather than inside it. Reach for the right one: Bans live outside the policy on purpose: policy is per-agent, compiled to signed WASM, and evaluated locally, so folding a ban into it would mean recompiling and re-signing on every toggle — and a user ban spans every agent, so it can’t live in any single agent’s policy anyway.

Creating and lifting a ban

Bans are managed from the Bans page in the dashboard (org owner/admin only):
  • Create — pick a type (Agent or User), choose the target (an agent from the project, or a free-text user_id), and optionally add a reason. The reason is shown later alongside blocked attempts.
  • Revoke — lifting a ban is a soft delete: the record is kept as an audit trail of who banned what and when, but the target is allowed to run again.
Only one active ban can target the same agent or user at a time.
Bans take effect on the target’s next run. The SDK re-checks the ban feed at the start of each run (a cheap ETag/304 round-trip), so creating or lifting a ban does not interrupt a run that is already in progress — it applies from the next invocation.

Handling the ban in your code

When a banned agent or user tries to run, the SDK raises a typed AgentBannedError before the first token. This is the one thing you need to handle as a developer: the ban surfaces as a real exception, never as a disguised assistant message — so it can’t be mistaken for model output, isn’t written to conversation history, and gives your backend a single, explicit place to catch it. AgentBannedError subclasses RuntimeError, so an unhandled ban won’t silently pass as a normal result — it propagates like any other error until you catch it. Import it from hexgate.security.errors:

The fields on the error

The user_message default is written to be shown as-is:
  • Agent ban — “This agent is currently disabled by an administrator.”
  • User ban — “Your access to this agent has been suspended by an administrator.”

Catching it

Wrap the run and decide what your product should do — return a clean error to the caller, log it, alert, or fall back:
The same try/except works on every entrypoint — sync (invoke / run_sync), async (ainvoke / run), and streaming. If you want different behavior per ban type, branch on the stable code:
On streaming entrypoints the check runs before the first chunk, so a banned run yields nothing — no partial output and no fake terminal message. Wrap the call that opens the stream in your try/except; the AgentBannedError is raised there, before you start iterating chunks.

Seeing what was blocked

Every refused run reports a best-effort blocked attempt to the audit pipeline. The dashboard’s Blocked attempts panel (on the Bans page) lists them by time, type, target, and reason, with a detail drawer per attempt. Blocked attempts are retained for 90 days. These are distinct from the audit trail of policy decisions — a ban is refused before any tool call, so it has no tool, arguments, or outcome to record.

Adapter coverage

The ban check is wired into every run path across the native agent factory and all four framework adapters — OpenAI Agents, LangChain, Pydantic AI, and Google ADK — firing after the policy refresh and before the model call, on both sync and streaming entrypoints.
Bans require the platform. In fully local modes (HEXGATE_LOCAL_MODE, HEXGATE_LOCAL_POLICY, or no resolvable API key) there is no ban gate and bans are a no-op.

Where to next

  • Request context — how user_id reaches the runtime, the basis for user bans.
  • Policy decision — the per-tool-call decisions a ban sits in front of.
  • Audit trail — the decision telemetry that runs alongside blocked-attempt reporting.