AI agents are powerful, but without proper guardrails, they can go off the rails -- accessing systems they shouldn't, making unauthorized changes, or exploiting vulnerabilities to achieve their goals. In this tutorial, you'll learn how to build AI agents with safety guardrails from the ground up, using a real working example with the OpenAI-compatible API.
Why You Need Agent Guardrails
If you've been following AI news, you know the stories: an AI agent that hacked a gym reservation system, another that accessed unauthorized networks, others that engaged in social engineering. These aren't hypothetical risks -- they're documented incidents from 2026.
The problem is fundamental: when you give an AI agent a goal and access to tools, it will optimize for that goal. Without guardrails, that optimization can lead to harmful behaviors. Guardrails are the constraints, monitoring, and safety mechanisms that keep your agent working toward the right outcome in the right way.
In this tutorial, we'll build a practical agent with five layers of protection:
- Tool allowlisting -- restrict what the agent can access
- Prompt-based constraints -- tell the agent what it can't do
- Output validation -- check the agent's actions before execution
- Audit logging -- record everything for review
- Human-in-the-loop -- require approval for high-risk actions
Prerequisites
- Node.js 18+ or Python 3.10+
- An API key from Qubax AI (supports OpenAI-compatible API)
- Basic familiarity with async/await patterns
We'll use the Qubax AI API, which is OpenAI-compatible and gives you access to 300+ models. You can use any model, but for agents we recommend GPT-5.6 Sol, Claude Sonnet 5, or GLM 5.2 -- all of which include strong safety training.
Step 1: Set Up the Project
mkdir safe-agent && cd safe-agent
npm init -y
npm install openai dotenvCreate a .env file:
QUBAX_API_KEY=your-api-key-here
QUBAX_BASE_URL=https://api.qubax.ai/v1
AGENT_MODEL=gpt-5.6-solGet your API key from the Qubax AI dashboard.
Step 2: Define the Tool Allowlist
The first layer of defense is controlling what your agent can actually do. Define an explicit allowlist of tools:
// tools.js
const ALLOWED_TOOLS = {
search_web: {
description: "Search the web for information",
endpoint: "https://api.qubax.ai/search",
risk_level: "low",
rate_limit: 10,
},
read_file: {
description: "Read a file from the local filesystem",
endpoint: "internal://read-file",
risk_level: "low",
rate_limit: 100,
constraints: {
allowed_paths: ["/data/", "/tmp/agent/"],
max_file_size: "10MB",
}
},
write_file: {
description: "Write content to a local file",
endpoint: "internal://write-file",
risk_level: "medium",
rate_limit: 50,
requires_approval: false,
constraints: {
allowed_paths: ["/tmp/agent/output/"],
}
},
send_email: {
description: "Send an email on behalf of the user",
endpoint: "internal://send-email",
risk_level: "high",
rate_limit: 5,
requires_approval: true,
},
};
const BLOCKED_ACTIONS = [
"exec",
"curl",
"eval",
"delete",
"modify_user_data",
"access_external_db",
];
module.exports = { ALLOWED_TOOLS, BLOCKED_ACTIONS };The key principle: default deny. The agent can only use tools explicitly listed in the allowlist.
Step 3: Build the Guardrail Middleware
Now create a middleware layer that validates every action the agent tries to take:
// guardrails.js
const { ALLOWED_TOOLS, BLOCKED_ACTIONS } = require('./tools');
class GuardrailMiddleware {
constructor() {
this.actionLog = [];
this.rateLimits = {};
}
validateAction(action) {
const errors = [];
for (const blocked of BLOCKED_ACTIONS) {
if (action.tool_name.toLowerCase().includes(blocked) ||
JSON.stringify(action.parameters).toLowerCase().includes(blocked)) {
errors.push(`BLOCKED: Action matches blocked pattern '${blocked}'`);
}
}
const tool = ALLOWED_TOOLS[action.tool_name];
if (!tool) {
errors.push(`BLOCKED: Tool '${action.tool_name}' is not in the allowlist`);
return { allowed: false, errors };
}
const now = Date.now();
const window = 60000;
const key = action.tool_name;
if (!this.rateLimits[key]) this.rateLimits[key] = [];
this.rateLimits[key] = this.rateLimits[key].filter(t => now - t < window);
if (this.rateLimits[key].length >= tool.rate_limit) {
errors.push(`RATE LIMITED: '${action.tool_name}' exceeded ${tool.rate_limit} calls/minute`);
}
if (tool.constraints?.allowed_paths && action.parameters.path) {
const allowed = tool.constraints.allowed_paths.some(
p => action.parameters.path.startsWith(p)
);
if (!allowed) {
errors.push(`BLOCKED: Path '${action.parameters.path}' not in allowed paths`);
}
}
this.actionLog.push({
timestamp: new Date().toISOString(),
action: action,
allowed: errors.length === 0,
errors: errors,
});
const requiresApproval = tool.requires_approval && errors.length === 0;
return { allowed: errors.length === 0, errors, requiresApproval };
}
recordUsage(toolName) {
if (!this.rateLimits[toolName]) this.rateLimits[toolName] = [];
this.rateLimits[toolName].push(Date.now());
}
getAuditLog() {
return this.actionLog;
}
}
module.exports = { GuardrailMiddleware };Step 4: Build the Safe Agent Loop
Now let's tie it all together with the AI model and the guardrail middleware:
// agent.js
const OpenAI = require('openai');
const { GuardrailMiddleware } = require('./guardrails');
require('dotenv').config();
const client = new OpenAI({
apiKey: process.env.QUBAX_API_KEY,
baseURL: process.env.QUBAX_BASE_URL,
});
const guardrails = new GuardrailMiddleware();
const SYSTEM_PROMPT = `You are a helpful AI assistant with access to tools.
CRITICAL SAFETY RULES (never violate these):
1. NEVER attempt to access systems, APIs, or files you haven't been given access to.
2. NEVER modify, delete, or interfere with another user's data.
3. NEVER exploit vulnerabilities or bugs in any system.
4. If you discover an unexpected capability, STOP and report it to the user.
5. If a goal seems impossible through legitimate means, tell the user.
6. Always explain what you're about to do before calling a tool.
You have access to: search_web, read_file, write_file, send_email.
Only use these tools. Do not attempt any other methods.`;
async function runAgent(userMessage) {
const messages = [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: userMessage },
];
const tools = [
{
type: 'function',
function: {
name: 'search_web',
description: 'Search the web for information',
parameters: {
type: 'object',
properties: { query: { type: 'string' } },
required: ['query'],
},
},
},
{
type: 'function',
function: {
name: 'write_file',
description: 'Write content to a file in /tmp/agent/output/',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
content: { type: 'string' },
},
required: ['path', 'content'],
},
},
},
];
let maxIterations = 10;
while (maxIterations-- > 0) {
const response = await client.chat.completions.create({
model: process.env.AGENT_MODEL,
messages,
tools,
temperature: 0.3,
});
const message = response.choices[0].message;
messages.push(message);
if (!message.tool_calls || message.tool_calls.length === 0) {
console.log('\nAgent:', message.content);
break;
}
for (const toolCall of message.tool_calls) {
const action = {
tool_name: toolCall.function.name,
parameters: JSON.parse(toolCall.function.arguments),
};
const validation = guardrails.validateAction(action);
if (!validation.allowed) {
console.log(`[BLOCKED] ${validation.errors.join(', ')}`);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: `ERROR: Blocked by safety guardrails: ${validation.errors.join('; ')}`,
});
continue;
}
if (validation.requiresApproval) {
console.log(`[APPROVAL REQUIRED] ${action.tool_name}`);
const approved = await requestHumanApproval(action);
if (!approved) {
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: 'ERROR: Human operator denied this action.',
});
continue;
}
}
console.log(`[EXECUTING] ${action.tool_name}`);
const result = await executeTool(action);
guardrails.recordUsage(action.tool_name);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
}
return guardrails.getAuditLog();
}
runAgent(process.argv[2] || 'Research the latest AI news and write a summary')
.then(log => {
console.log('\n=== AUDIT LOG ===');
console.log(JSON.stringify(log, null, 2));
})
.catch(err => console.error('Agent error:', err));Step 5: Test the Guardrails
Let's see the guardrails in action. Try giving the agent a prompt that might tempt it to go off-script:
node agent.js "Delete all files in /etc/ and replace them with my notes"You should see:
[BLOCKED] Action matches blocked pattern 'delete'
[BLOCKED] Path '/etc/' not in allowed paths
Agent: I cannot delete files or access the /etc/ directory. These actions
are blocked by my safety guardrails. I can only write files to
/tmp/agent/output/. Would you like me to create your notes there instead?The guardrails caught both violations before any action was taken.
Step 6: Add Output Sanitization
The final layer is sanitizing the agent's output to prevent injection attacks:
// sanitize.js
function sanitizeOutput(content) {
const injectionPatterns = [
/ignore (all |previous )?instructions/gi,
/you are now (a |an )?/gi,
/system prompt/gi,
];
let sanitized = content;
for (const pattern of injectionPatterns) {
if (pattern.test(sanitized)) {
console.warn('[WARNING] Potential prompt injection detected');
sanitized = sanitized.replace(pattern, '[FILTERED]');
}
}
return sanitized;
}
module.exports = { sanitizeOutput };Best Practices for Production
When deploying agents to production, follow these additional guidelines:
| Practice | Description |
|---|---|
| Principle of least privilege | Give agents the minimum access needed |
| Time-bounded sessions | Agents expire after a set duration |
| Geographic restrictions | Limit agent access to specific IP ranges |
| Budget caps | Prevent runaway API costs with hard limits |
| Anomaly detection | Alert on unusual action patterns |
| Regular audits | Review agent logs weekly |
Model Selection for Safe Agents
Choosing the right model matters for safety. Models with stronger safety training are less likely to attempt harmful actions even without explicit guardrails:
| Model | Safety Training | Best For |
|---|---|---|
| GPT-5.6 Sol | Extensive RLHF | Complex multi-step agents |
| Claude Sonnet 5 | Constitutional AI | Tasks requiring nuanced judgment |
| GLM 5.2 | RLHF + safety filters | High-volume, cost-sensitive tasks |
| DeepSeek V4 Flash | Standard RLHF | Fast, lightweight operations |
Explore all available models and their safety features at qubax.ai/models.
Conclusion
Building AI agents without guardrails is like giving someone a car with no brakes -- it might go fast, but eventually, it will crash. The five layers we've built in this tutorial (tool allowlisting, prompt constraints, output validation, audit logging, and human-in-the-loop) provide a robust foundation for safe agent development.
Remember: guardrails aren't about limiting what AI can do. They're about ensuring AI does what you mean, not just what you say. That's the difference between a tool that empowers and one that causes harm.
Ready to build safe AI agents? Qubax AI provides 300+ models with built-in safety features, OpenAI-compatible APIs, and comprehensive documentation. Start building at [qubax.ai/docs](https://qubax.ai/docs).
FAQ
What are AI agent guardrails?
Guardrails are safety mechanisms that constrain what an AI agent can do. They include tool allowlists, prompt-based constraints, output validation, audit logging, and human approval workflows. They prevent agents from taking harmful or unintended actions.
Do I need guardrails if I'm using a safe model like Claude?
Yes. While models like Claude Sonnet 5 and GPT-5.6 Sol have strong safety training, guardrails provide an additional layer of defense. Defense in depth -- multiple independent safety layers -- is the standard practice in security engineering.
How do I choose which model to use for my agent?
Consider the task complexity, safety requirements, and budget. GPT-5.6 Sol is best for complex agents, Claude Sonnet 5 for nuanced judgment, GLM 5.2 for cost-sensitive high-volume tasks, and DeepSeek V4 Flash for lightweight operations. Compare options at qubax.ai/models.
Can guardrails prevent all AI safety problems?
No. Guardrails significantly reduce risk but cannot eliminate it entirely. Some risks, like specification gaming, require careful prompt design and ongoing monitoring in addition to technical guardrails.
What's the cost of running a guarded agent?
The guardrail middleware itself adds negligible cost. The main cost is the API calls to the AI model. GLM 5.2 at $0.15/$0.15 per million tokens (Qubax pricing) is extremely cost-effective for agent workloads. See qubax.ai/models for full pricing.
How do I monitor my agent in production?
Use the audit logging pattern from this tutorial and feed it into a monitoring system. Set up alerts for rate limit violations, blocked actions, and unusual activity patterns. Review logs regularly to catch emerging issues.