Your first guarded run
This page builds a small agent that sends emails, puts a guard on its email tool and sends the run to your dashboard. It takes about five minutes.
You need:
- Quard running, as Self-host Quard shows, and an agent key.
- Node.js 22.12 or newer.
- An OpenAI API key.
1. Install the SDK
mkdir first-agent
cd first-agent
npm init -y
npm install quard openaiInstalling from your copy of the Quard repository instead? Run pnpm install, pnpm --filter quard build and pnpm --filter quard pack there. That writes quard-0.0.0.tgz in the repository folder. Then run npm install /path/to/quard/quard-0.0.0.tgz openai here.
2. Add your settings
Make a file named .env in the same folder:
QUARD_AGENT_KEY=qk_live_...
QUARD_WEBHOOK_URL=http://localhost:4100
QUARD_CONTROL_URL=http://localhost:4200
OPENAI_API_KEY=sk-...The URLs point at webhook and control. Change them if Quard runs on another machine or on other ports.
The agent key is the only Quard secret your agent needs. With it, the SDK asks Quard for the key that hashes IBANs and emails.
3. Write the agent
Make a file named agent.mjs:
import OpenAI from "openai";
import { guard, isGuardRefusal, quard } from "quard";
quard.configure({
key: process.env.QUARD_AGENT_KEY,
webhookUrl: process.env.QUARD_WEBHOOK_URL,
controlUrl: process.env.QUARD_CONTROL_URL,
});
// Quard records every model call made through this client
const client = quard.wrap(new OpenAI());
// Your tool. This one only pretends to send.
async function sendEmail({ to, subject }) {
return `Sent "${subject}" to ${to}`;
}
// The guard: at most one email per run
const guardedSendEmail = guard(sendEmail, { type: "limit", name: "sendEmail", maxCallsPerRun: 1 });
const model = "gpt-5.4-mini";
const tools = [
{
type: "function",
name: "sendEmail",
description: "Send an email",
strict: true,
parameters: {
type: "object",
properties: { to: { type: "string" }, subject: { type: "string" } },
required: ["to", "subject"],
additionalProperties: false,
},
},
];
// Everything inside quard.run() is one run, by the agent "assistant"
await quard.run({ agent: "assistant" }, async () => {
let response = await client.responses.create({
model,
tools,
input: "Send two emails to dana@acme.com, one with the subject 'Hello' and one with 'Your order shipped'.",
});
// Run the tools the model asks for, then send it the results
for (let turn = 0; turn < 5; turn++) {
const calls = response.output.filter((item) => item.type === "function_call");
if (calls.length === 0) break;
const results = [];
for (const call of calls) {
const result = await guardedSendEmail(JSON.parse(call.arguments));
// A blocked call returns a refusal, which the model reads instead
const output = isGuardRefusal(result) ? result.text : result;
results.push({ type: "function_call_output", call_id: call.call_id, output });
}
response = await client.responses.create({ model, tools, previous_response_id: response.id, input: results });
}
console.log(response.output_text);
});quard.wrap() lets Quard see each model call. guard() wraps the tool, and Quard checks every call to it before it runs.
4. Run it
node --env-file=.env agent.mjsThe model asks to send two emails. The first one goes out. The guard blocks the second, so the model gets a refusal instead and says so:
I sent the "Hello" email to dana@acme.com. The second email was blocked because only one email is allowed per run.The wording changes from run to run, because a real model writes it.
5. See the run in the dashboard
Open the dashboard and go to Runs. The newest run is from the agent assistant. It completed, with two model calls and two tool calls. Open it to see the timeline: the first sendEmail call ran, and the second one was blocked by the limit guard.
The block also opened an incident, under Incidents, which traces the path that led to it.
Nothing showing up? Check that QUARD_WEBHOOK_URL answers at /health, and that your agent key is active under Settings.
Next steps
- Read about the other guards: source, action, approval, egress, limit and x402.
- Learn to read a run in the Runs guide.
- Have a person approve risky calls with an approval guard.
- Using the OpenAI Agents SDK? See OpenAI Agents SDK.