Back to blog
Tutorial·8 min read·1507 words

How to Add Safety Guardrails to AI Agents — A Complete Developer Tutorial

AI agents can go rogue without warning. This complete tutorial shows you how to build a guardrail system that monitors, filters, and controls AI agent actions in real time — with full Python code examples.

How to Add Safety Guardrails to AI Agents — A Complete Developer Tutorial — illustration

With AI agents going rogue at major companies, there's never been a better time to add safety guardrails to your AI-powered applications. In this tutorial, you'll learn how to build a practical guardrail system that monitors, filters, and controls AI agent actions in real time.

We'll build this using Python and the OpenAI-compatible API format — the same format used by Qubax AI and most modern AI providers.

Prerequisites

  • Python 3.10+
  • An API key from Qubax AI or any OpenAI-compatible provider
  • Basic familiarity with Python async programming

Step 1: Set Up Your Environment

First, install the required packages:

bash
pip install openai pydantic structlog

Create a new project directory and set up your environment variables:

bash
export AI_API_KEY="your-api-key-here"
export AI_BASE_URL="https://api.qubax.ai/v1"

Step 2: Define Your Safety Policy

Before writing any code, you need a clear safety policy. This defines what actions are allowed, what requires human approval, and what's blocked outright.

python
from enum import Enum
from pydantic import BaseModel
from typing import Optional

class RiskLevel(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    BLOCKED = "blocked"

class SafetyRule(BaseModel):
    name: str
    description: str
    risk_level: RiskLevel
    pattern: str  # regex or keyword pattern to match
    action: str   # "allow", "require_approval", "block"

# Define your safety rules
SAFETY_RULES = [
    SafetyRule(
        name="no_file_deletion",
        description="Block attempts to delete files",
        risk_level=RiskLevel.BLOCKED,
        pattern=r"(rm\s+-rf|del\s+/[fqs]|os\.remove|shutil\.rmtree)",
        action="block"
    ),
    SafetyRule(
        name="no_network_exfiltration",
        description="Block data exfiltration attempts",
        risk_level=RiskLevel.BLOCKED,
        pattern=r"(curl\s+.*\|\s*bash|wget.*--post-data|requests\.post.*api\.telegram)",
        action="block"
    ),
    SafetyRule(
        name="production_database",
        description="Require approval for production DB access",
        risk_level=RiskLevel.HIGH,
        pattern=r"(prod|production|live).*\.(db|sql|database)",
        action="require_approval"
    ),
    SafetyRule(
        name="payment_actions",
        description="Require approval for payment-related actions",
        risk_level=RiskLevel.HIGH,
        pattern=r"(charge|payment|refund|transfer|stripe|paypal)",
        action="require_approval"
    ),
    SafetyRule(
        name="email_sending",
        description="Monitor email sending actions",
        risk_level=RiskLevel.MEDIUM,
        pattern=r"(send_email|smtp|mail\.send|resend\.emails)",
        action="log"
    ),
]

Step 3: Build the Guardrail Engine

The guardrail engine inspects every action the AI agent wants to take and decides whether to allow, block, or require approval.

python
import re
import logging
from dataclasses import dataclass
from datetime import datetime, timezone

logger = logging.getLogger(__name__)

@dataclass
class GuardrailResult:
    allowed: bool
    risk_level: RiskLevel
    matched_rules: list
    requires_approval: bool
    reason: str
    timestamp: str

class GuardrailEngine:
    def __init__(self, rules: list):
        self.rules = rules
        self.action_log = []
        self.pending_approvals = {}

    def evaluate(self, action: str, context: dict = None) -> GuardrailResult:
        '''Evaluate an agent action against safety rules.'''
        context = context or {}
        matched = []
        highest_risk = RiskLevel.LOW
        should_block = False
        should_require_approval = False

        for rule in self.rules:
            if re.search(rule.pattern, action, re.IGNORECASE):
                matched.append(rule.name)

                if rule.action == "block":
                    should_block = True
                    if rule.risk_level == RiskLevel.BLOCKED:
                        highest_risk = RiskLevel.BLOCKED
                elif rule.action == "require_approval":
                    should_require_approval = True
                    if highest_risk != RiskLevel.BLOCKED:
                        highest_risk = RiskLevel.HIGH

        result = GuardrailResult(
            allowed=not should_block,
            risk_level=highest_risk,
            matched_rules=matched,
            requires_approval=should_require_approval,
            reason=self._build_reason(matched, should_block, should_require_approval),
            timestamp=datetime.now(timezone.utc).isoformat()
        )

        # Log every evaluation
        self.action_log.append({
            "action": action[:200],  # truncate for logging
            "result": result,
            "context": context
        })

        if should_block:
            logger.warning(f"BLOCKED action: {matched} | {action[:100]}")
        elif should_require_approval:
            logger.info(f"Approval required: {matched} | {action[:100]}")

        return result

    def _build_reason(self, matched, blocked, approval):
        if blocked:
            return f"Action blocked by rules: {', '.join(matched)}"
        if approval:
            return f"Action requires approval: {', '.join(matched)}"
        if matched:
            return f"Action logged by rules: {', '.join(matched)}"
        return "No safety rules triggered"

    def get_action_log(self):
        '''Return the full action log for auditing.'''
        return self.action_log

Step 4: Wrap Your AI Agent with Guardrails

Now let's create a safe agent wrapper that intercepts every tool call:

python
from openai import AsyncOpenAI
import asyncio

class SafeAgent:
    def __init__(self, api_key: str, base_url: str, model: str = "gpt-5"):
        self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
        self.model = model
        self.guardrail = GuardrailEngine(SAFETY_RULES)
        self.max_tool_calls = 25  # Hard limit per session
        self.tool_call_count = 0

    async def execute_tool(self, tool_name: str, arguments: dict) -> dict:
        '''Execute a tool call with guardrail protection.'''
        self.tool_call_count += 1

        # Rate limit check
        if self.tool_call_count > self.max_tool_calls:
            return {"error": "Tool call limit exceeded. Session terminated."}

        # Serialize the action for guardrail inspection
        action_str = f"{tool_name} {str(arguments)}"

        # Evaluate against guardrails
        result = self.guardrail.evaluate(action_str, {
            "tool": tool_name,
            "call_number": self.tool_call_count
        })

        if not result.allowed:
            return {
                "error": f"Action blocked by safety guardrail: {result.reason}",
                "risk_level": result.risk_level.value
            }

        if result.requires_approval:
            # In production, this would integrate with your approval workflow
            approved = await self._request_human_approval(tool_name, arguments, result)
            if not approved:
                return {"error": "Action not approved by human operator"}

        # Execute the actual tool
        return await self._run_tool(tool_name, arguments)

    async def _request_human_approval(self, tool_name, arguments, result):
        '''Request human approval for high-risk actions.'''
        print(f"\n{'='*60}")
        print(f"APPROVAL REQUIRED for: {tool_name}")
        print(f"Arguments: {arguments}")
        print(f"Risk level: {result.risk_level.value}")
        print(f"Reason: {result.reason}")
        print(f"{'='*60}")
        response = input("Approve this action? (yes/no): ")
        return response.lower().startswith("y")

    async def _run_tool(self, tool_name, arguments):
        '''The actual tool implementation goes here.'''
        # Replace with your real tool implementations
        return {"status": "executed", "tool": tool_name, "args": arguments}

    def get_stats(self):
        '''Get session statistics.'''
        return {
            "total_tool_calls": self.tool_call_count,
            "action_log_entries": len(self.guardrail.get_action_log()),
            "remaining_calls": self.max_tool_calls - self.tool_call_count
        }

Step 5: Add Behavioral Monitoring

Beyond rule-based guardrails, you should monitor the agent's behavior patterns for anomalies:

python
class BehaviorMonitor:
    '''Detects anomalous agent behavior patterns.'''
    
    def __init__(self):
        self.action_history = []
        self.alerts = []

    def record_action(self, tool_name: str, success: bool):
        self.action_history.append({
            "tool": tool_name,
            "success": success,
            "time": datetime.now(timezone.utc)
        })
        self._check_patterns()

    def _check_patterns(self):
        # Check for rapid-fire tool calls (potential loop)
        if len(self.action_history) >= 5:
            recent = self.action_history[-5:]
            time_span = (recent[-1]["time"] - recent[0]["time"]).total_seconds()
            if time_span < 2:
                self.alerts.append({
                    "type": "rapid_fire",
                    "message": "Agent made 5+ tool calls in under 2 seconds"
                })

        # Check for repeated failures (agent may be stuck)
        recent_results = [a["success"] for a in self.action_history[-10:]]
        if len(recent_results) >= 5 and not any(recent_results):
            self.alerts.append({
                "type": "repeated_failures",
                "message": "Agent has failed 5+ consecutive actions"
            })

        # Check for tool diversity (agent using too many different tools)
        recent_tools = set(a["tool"] for a in self.action_history[-20:])
        if len(recent_tools) > 10:
            self.alerts.append({
                "type": "tool_explosion",
                "message": "Agent is using an unusually large number of different tools"
            })

Step 6: Put It All Together

python
async def main():
    agent = SafeAgent(
        api_key="your-api-key",
        base_url="https://api.qubax.ai/v1",
        model="gpt-5"
    )

    # Example: Agent tries to delete a file (BLOCKED)
    result = await agent.execute_tool("terminal", {"command": "rm -rf /"})
    print(f"Result: {result}")
    # Output: Action blocked by safety guardrail

    # Example: Agent tries to send email (LOGGED)
    result = await agent.execute_tool("send_email", {
        "to": "[email protected]",
        "subject": "Hello",
        "body": "Test message"
    })
    print(f"Result: {result}")
    # Output: Action executed (logged for monitoring)

    # Print session stats
    print(f"\nSession stats: {agent.get_stats()}")

asyncio.run(main())

Best Practices for Production

Here are the key principles to follow when deploying guardrails in production:

  1. Default to deny. If the guardrail can't determine whether an action is safe, block it. It's easier to whitelist safe actions than to recover from an unsafe one.
  1. Log everything. Every action — allowed, blocked, or pending — should be logged with full context. You need this for debugging, compliance, and post-incident analysis.
  1. Make guardrails tamper-proof. The AI agent should not be able to modify the guardrail rules or bypass the evaluation step. Run the guardrail in a separate process or service if possible.
  1. Test adversarially. Before deploying, try to break your own guardrails. Have a red team attempt to craft inputs that bypass the safety rules. See our guide to AI red teaming for techniques.
  1. Keep rules updated. New attack vectors emerge constantly. Review and update your safety rules regularly based on incident reports and new research.
  1. Combine rule-based and AI-based detection. Rules are fast and predictable but can't catch everything. Consider adding an AI-based "second opinion" layer that uses a small model to flag suspicious actions that don't match any rule.

Build safer AI agents with Qubax AI. Our API platform supports OpenAI-compatible tool calling, streaming, and integrates seamlessly with guardrail systems. Read our docs for more examples.

FAQ

What are AI agent guardrails?

Guardrails are safety systems that monitor and control what an AI agent can do. They intercept the agent's actions before execution and decide whether to allow, block, or require human approval based on predefined safety rules.

How do guardrails prevent rogue AI agents?

Guardrails inspect every action the agent attempts. If an action matches a dangerous pattern (like deleting files, accessing production systems, or exfiltrating data), the guardrail blocks it before it can execute. This prevents the most common forms of rogue behavior.

Should I use rules or AI-based filtering for guardrails?

Both. Rule-based filtering is fast and predictable — use it for known dangerous patterns. AI-based filtering (using a small model to evaluate actions) catches novel threats that rules miss. Combining both gives you the best protection.

How much latency do guardrails add?

Minimal. Rule-based guardrails use simple regex matching, which takes microseconds. Even AI-based second-opinion systems typically add less than 500ms. The safety benefit far outweighs the latency cost.

Can an AI agent bypass guardrails?

If implemented correctly, no. The guardrail must run in a separate trust zone that the agent cannot modify or bypass. The key principle is that the agent should never have direct access to tools — all tool calls must go through the guardrail layer.

What tools do I need to implement guardrails?

You need a programming language with regex support (Python, JavaScript, Go, etc.), a logging system, and optionally a small AI model for advanced detection. The guardrail system itself is straightforward to build — the hard part is defining the right safety rules for your use case.

Article tags

#ai-agents#ai-safety#guardrails#python#developer-tutorial
Share:Post on XTelegramLinkedInYHacker NewsReddit
Qubax AI

Qubax AI

AI Models at up to 99% off · Pay with crypto

Access GPT, Claude, Gemini, GLM & 340+ models through one OpenAI-compatible API. Up to 99% off. Pay with 200+ cryptocurrencies. Get $1 free credits — no credit card needed.

Related articles