Guardrails
Enforcing security policies, input sanitization, and tool call approval gates.
While type and schema validations check for correctness (e.g. "is this argument a number?"), guardrails enforce policy (e.g. "should the model be allowed to view this specific user's folder?" or "require human approval before modifying an invoice").
Chai AI SDK supports three categories of guardrails with distinct behavior suited for different stages of the execution loop.
The Three Guardrail Types
All guardrails check actions using GuardrailAction, which returns one of three actions: allow, block (with a reason), or require-approval (with a reason, valid only for tools).
| Guardrail Type | Stage | Failure Behavior |
|---|---|---|
InputGuardrail | Before the first model call | Fails Fast: Throws an AgentError immediately. No model call occurs. |
ToolGuardrail | Right before a tool executes | Fails Gracefully: Returns an error string to the model, giving it a chance to apologize or recover. |
OutputGuardrail | Before final text is returned | Auto-Repair: Prompts the model with the rejection reason to rewrite the response. Can also mechanically transform output. |
Defining Guardrails
1. Input Guardrails
Input guardrails analyze the raw prompt. If the prompt contains forbidden terms (like secrets), the execution is aborted immediately:
import { type InputGuardrail } from "chai-ai";
const noSecretsGuardrail: InputGuardrail = {
name: "no-secrets",
check: ({ prompt }) =>
/api[_-]?key|password/i.test(prompt)
? { action: "block", reason: "Prompt contains sensitive search terms." }
: { action: "allow" },
};2. Tool Guardrails & Human-in-the-loop Approval
Tool guardrails execute right before a tool runs. They can request human approval via the onApprovalRequired handler:
import { type ToolGuardrail } from "chai-ai";
const sensitiveActionGuardrail: ToolGuardrail = {
name: "sensitive-action",
check: ({ toolName }) =>
toolName === "deleteDatabase"
? { action: "require-approval", reason: "Database deletion is irreversible." }
: { action: "allow" },
};To handle approval requests, configure onApprovalRequired on the Agent constructor. If omitted, or if it returns false/throws, the request fails closed and the tool call is blocked:
const agent = new Agent({
name: "admin-agent",
model,
tools: { deleteDatabase },
toolGuardrails: [sensitiveActionGuardrail],
onApprovalRequired: async ({ toolName, reason }) => {
// Show UI prompt or ask in terminal
return await askUserForPermission(reason);
},
});3. Output Guardrails
Output guardrails check the final generated response. They support an optional transform function (e.g. regex email redaction) that runs before verification:
import { type OutputGuardrail } from "chai-ai";
const redactEmailGuardrail: OutputGuardrail = {
name: "redact-emails",
transform: ({ text, output }) => ({
text: text.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[redacted]"),
output,
}),
check: ({ text }) =>
text.includes("[redacted]")
? { action: "block", reason: "Emails were detected and redacted." }
: { action: "allow" }
};Execution Flow Traces
Tool Guardrail: Approved vs. Denied
// If approved
Model Call → Tool Request: deleteDatabase()
→ sensitive-action checks deleteDatabase() → "require-approval"
→ onApprovalRequired() executes → returns true (approved)
→ runTool(deleteDatabase) executes
← agent returns success response
// If denied (fails closed)
Model Call → Tool Request: deleteDatabase()
→ sensitive-action checks deleteDatabase() → "require-approval"
→ onApprovalRequired() is undefined → approved = false
→ Intercept tool: runTool() is skipped
← Send tool result message: { error: "Database deletion is irreversible." }
→ Model sees error, apologizes: "I cannot delete the database without approval."Output Guardrail: Redaction & Repair
Model Call → returns "Contact me at bob@example.com"
→ redact-emails.transform() → "Contact me at [redacted]"
→ redact-emails.check() → block ("Emails detected")
→ Yield guardrail_triggered event
→ Push corrective prompt: "Your response was rejected: Emails detected. Respond again."
→ Model Call (Iteration 2) → returns "Please contact me through the portal instead."
→ redact-emails.check() → allow
← Final returned text: "Please contact me through the portal instead."