Guardrails

Block a tool call your policy forbids, before any hook or tool runs. guardrails= takes the same agents.Hook objects as hooks=, and runs them first, at every hook point. Use it for safety checks, and keep hooks= for your application's code.

This guardrail stops the payment reviewer from opening a second case on an alert that already has one. Each pre_tool_call prints, to show the order. Save it next to reviewer.py from that page: it imports the two tools.

terminal> uv run python guarded.pyguardrail: client.get_alerthook: client.get_alertguardrail: client.open_caseAlert 4711 is a suspicious 9,800EUR payment [...] and a case file(CASE-4711) is already open for it.WHAT GUARDED.PY PRINTSPRE_TOOL_CALL, IN ORDEROneCasePerAlertguardrails=AuditLoghooks=get_alert, open_caseyour toolsskippedskippedagents.skip(...)Alert 4711 already has caseCASE-4711. Do not openanother.
Step 1 of 5. guarded.py runs the payment reviewer on alert 4711. Each pre_tool_call prints a line, to show the order.
import asyncio

from mistralai import agents

from reviewer import get_alert, open_case

CASES = {"4711": "CASE-4711"}  # alerts that already have a case: read them from your case system

class OneCasePerAlert(agents.Hook):
    async def pre_tool_call(self, ctx, call):
        print("guardrail:", call.tool_id)
        alert_id = call.arguments.get("alert_id")
        if call.tool_id == "client.open_case" and alert_id in CASES:
            return agents.skip(f"Alert {alert_id} already has case {CASES[alert_id]}. Do not open another.")

class AuditLog(agents.Hook):
    async def pre_tool_call(self, ctx, call):
        print("hook:", call.tool_id)

payment_reviewer = agents.Agent(
    model="mistral-large-4",
    name="payment-reviewer",
    instructions=(
        "Review the alert. Collect the context. If the payment is suspicious, open a case file. "
        "Reply in one sentence."
    ),
    tools=[get_alert, open_case],
    guardrails=[OneCasePerAlert()],
    hooks=[AuditLog()],
)

async def main() -> None:
    state = await payment_reviewer.run("Review transaction monitoring alert 4711.")
    if isinstance(state, agents.Completed):
        print(state.text)

asyncio.run(main())
Output
guardrail: client.get_alert
hook: client.get_alert
guardrail: client.open_case
Alert 4711 is a suspicious 9,800 EUR payment from account FR76-3000-4471 to a beneficiary added just 3 minutes earlier right after a password reset — a classic account-takeover plus structuring pattern (amount just under the 10,000 EUR threshold) — and a case file (CASE-4711) is already open for it.

Each tool call passed the guardrail first, then the hook. The guardrail returned agents.skip(...) on open_case, so neither the hook nor the tool ran, and the model got the reason in place of the result: its answer says the case is already open. call.tool_id is the tool's full ID, such as client.open_case (client is the default namespace of your own tools), so a guardrail can check built-in tools too. The Hook API reference lists the hook points and what each can return.

Guardrails check the data before your hooks run. If a hook in hooks= then changes that data, such as a tool call's arguments, no guardrail checks the new value.

To ask a person before a tool runs, use Approvals instead.

Sessions: talk with the agent over many turns.