LLM guardrails for Claude agents: which rules belong in code, and how to build them
LLM guardrails are the checks around a model that keep its inputs, its actions and its outputs inside your rules. Some guardrails are guidance in the prompt, and Claude follows them very well, but not with a guarantee. Any rule where one miss costs money, trust or safety belongs in code that runs on every call: a screen on what comes in, a hook that can block a tool call, and permissions that limit what the agent can touch at all.
What LLM guardrails are
A model decides what to say and which tool to call. Guardrails decide what's allowed. They sit at four points:
| Where | Guardrail | Kind |
|---|---|---|
| What comes in | Screen user input with a small, fast model; validate known injection patterns | Code |
| What the agent reads | Third-party content only in tool results, marked as untrusted | Structure + prompt |
| What the agent does | Hooks that check each tool call; permission rules; least privilege | Code |
| What comes out | A JSON schema for the output, validated before use; a person reviews the risky cases | Code + people |
The prompt still matters: it shapes tone, scope and how Claude refuses. But a prompt is guidance. Code is enforcement.
The rule: code for anything that must always hold
The exam guide for the Claude Certified Architect certification puts it plainly: when compliance has to be deterministic, such as identity verification before financial operations, "prompt instructions alone have a non-zero failure rate".
A simple test decides where a rule goes. If breaking it once costs money, trust or safety, enforce it in code. A refund limit, a verification step before account changes, a folder the agent must never write to: code. A friendly tone, a preferred answer format, which topics to stay on: prompt.
Example: a refund limit the agent can't skip
Take a bike-rental support agent built on the Claude Agent SDK, with a refund tool on its backend MCP server. Rule: refunds over $100 need a person.
```python from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
REASON = "Refunds over $100 need a person. Hand off with a summary."
async def refund_limit(input_data, tool_use_id, context): if input_data["tool_input"].get("amount", 0) > 100: return {"hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": REASON, }} return {} # allow
options = ClaudeAgentOptions(hooks={ "PreToolUse": [HookMatcher(matcher="mcp__cogwheel-backend__issue_refund", hooks=[refund_limit])], }) ```
Three details make it work:
- The matcher names the tool, not its arguments. The docs note that matchers match tool names only, so the amount is checked inside the hook.
- The reason is shown to Claude. A gate that only says no leaves the customer stuck; this one tells the agent what to do next.
- MCP tools are named
mcp__<server>__<tool>, so the hook targets exactly one tool.
A second gate makes a prerequisite: a PostToolUse hook watches the customer lookup and records, for the session, that the customer was verified. The refund hook then denies any refund for an unverified session, whatever the chat says.
Why hooks hold when prompts don't
In the Agent SDK, when Claude asks for a tool, hooks run first: before deny rules, ask rules, the permission mode and allow rules. The permissions docs add that "a hook deny applies even in bypassPermissions mode". When several hooks disagree, deny wins. And hooks run in your application process, outside Claude's context window, so nothing in the conversation can argue with them.
Claude Code works the same way: Claude Code hooks can block a command or an edit, and permission rules are enforced by Claude Code, not by the model.
Guardrails against prompt injection
Anthropic's guide on jailbreaks and prompt injection separates two threats: a user who attacks your application directly, and instructions hidden in content the agent reads, such as an email, a web page or a tool result. Its main defenses:
- Screen input with a lightweight model and a structured output, such as
{"is_harmful": true}. - Put untrusted content only in tool results, say where it came from, and state in the system prompt that it is data, not instructions.
- JSON-encode third-party strings, so injected text can't break out of its quotes into an instruction.
- Limit access. Least privilege, sandboxed tools and narrow permissions keep a successful injection small.
- Screen tool outputs with a small model before Claude acts on them, and red-team the agent with poisoned documents before launch.
Don't stop at "no"
A guardrail that blocks without a next step frustrates customers and staff. Pair each hard gate with a path forward: the deny reason tells Claude to hand off to a human, and the hand-off carries a structured summary (customer, booking, amount asked, root cause, recommended action), because the person picking it up can't read the chat.
Guardrails on the Claude Certified Architect exam
Domain 1 of the CCAR-F exam tests this in task 1.4 (programmatic enforcement versus prompt guidance, prerequisite gates, structured handoffs) and task 1.5 (hooks that block a refund above a threshold and redirect to a human). The usual wrong answer is a stronger sentence in the system prompt where the scenario needs a guarantee. See also AI agent architecture for where guardrails sit among the other building blocks.
More in all articles.
Sources: Hooks (Agent SDK); Configure permissions (Agent SDK); Connect to external tools with MCP; Mitigate jailbreaks and prompt injections, read on 22 October 2026; Claude Certified Architect Foundations exam guide v1.0, task statements 1.4 and 1.5.
FAQ
What are LLM guardrails?
Checks around a language model that keep it within your rules: screening what goes in, limiting and checking what it does with tools, validating what comes out, and monitoring the results. They range from prompt instructions to hard gates in code.
Are prompt instructions enough as guardrails?
Not for rules that must always hold. The Claude Certified Architect exam guide notes that prompt instructions alone have a non-zero failure rate when compliance must be deterministic, such as identity checks before financial operations. Use code for those.
How do I block a tool call in the Claude Agent SDK?
Register a PreToolUse hook with a matcher for the tool. Inside it, check the tool input and return a permission decision of deny with a reason. The reason is shown to Claude, so it can change course, for example by handing off to a person.
How do guardrails protect against prompt injection?
Deliver third-party content only in tool results, say where it came from, tell Claude in the system prompt that such content is data and not instructions, give the agent the least access it needs, and screen tool outputs with a small model before Claude acts on them.