Skip to main content
Guard a single tool’s arguments and results without hand-wrapping the function — declare inputGuardrails / outputGuardrails right on the tool, next to approval and restartSafe.
The guardrail fires on every invocation of send_email and never for any other tool.

Quick Start

1

Block a tool's arguments by rule

An input guardrail returns [false, 'reason'] to block. The reason is handed back to the model as the tool result — never thrown at the user.
2

Rewrite the arguments before the tool runs

Return [true, newArgs] to rewrite. The rewritten object is what the tool receives.
An input guardrail’s rewrite must stay an object of keyword arguments. Returning anything else (array, string, null) is a contract violation and blocks the call, because handing a non-object to the tool would throw inside your code instead of producing a message the model can act on.
3

Substitute the result

An output guardrail sees the raw result before it re-enters the LLM context. Return [true, newResult] to substitute, or [false, 'reason'] to block.
4

Chain several guardrails

Pass an array — each guardrail’s allowed value feeds the next; the chain short-circuits on the first failure.

Verdict shapes accepted

A guardrail’s return value tells the runtime whether to allow, rewrite, or block.

How per-tool guardrails run

Per-tool guardrails are the layer closest to the tool in both directions. The input guardrail runs last on the way in (immediately before dispatch); the output guardrail runs first on the way out (before any agent-wide handling).
The order inside FunctionTool.execute is restartSafe → approval → input guardrail → tool → output guardrail. Human approval stays upstream of every automated rewrite, so a reviewer audits exactly what the model proposed. The input guardrail runs immediately before dispatch, so “the tool never runs with arguments its own guardrail did not see” holds.

Detecting a denial

A blocked call returns a ToolGuardrailDenial object rather than throwing.
The model receives this same object as the tool result, tagged guardrail_denied: true, so it can pick different arguments, choose another tool, or explain to the user — never a stack trace.

Reusing an existing guardrail object

Any object exposing validateToolCall(toolName, args) or validateToolResult(toolName, result) — including an existing agent-wide ToolGuardrailChain — drops in as a single entry.
direction is passed through the base CallableToolGuardrail constructor rather than declared as a subclass readonly direction = INPUT field, because subclass field initializers run after super() and would leave the base constructor seeing undefined. You only hit this when subclassing CallableToolGuardrail directly.

Per-tool vs agent-wide guardrails

Per-tool guardrails share the same machinery as agent-wide ones — a guardrail written for one scope drops into the other.

Best Practices

When a rule belongs to one tool — “send_email recipients must be on-domain” — declare it on that tool, not agent-wide. An agent-wide guardrail fires for every tool and has to branch on toolName.
Returning [true, sanitisedArgs] is safer than [false, reason] and hoping the model retries correctly. Fix the call rather than bouncing it back.
When a result carries a secret, return [true, redacted] so the model still gets a usable answer, instead of [false, ...] which throws the whole result away.
A guardrail should inspect and transform its input with no side effects. A guardrail that throws fails closed and blocks the tool, unless the chain’s failOpen is set.

Guardrails

Agent-wide input and output guardrails.

Approval

Human-in-the-loop sign-off before a tool runs.

Per-Tool Guardrails (Python)

The same feature in the Python SDK.