Aira
Integrations

OpenAI Agents

Wrap OpenAI Agents tools through Aira's policy engine. authorize() runs before the tool body. Denied calls never run. Python and TypeScript.

Kind: gate · Pre-execution gate: yes · Peer dep: openai-agents (Python) or @openai/agents (TypeScript)

What this integration actually does

OpenAI Agents SDK supports guardrail functions and explicit tool wrappers that run BEFORE a tool executes. Either path can throw to abort, so wrapping the tool is a real authorization gate.

AiraGuardrail.wrap_tool() (Python) and AiraGuardrail.wrapTool() (TypeScript) take a tool function, call aira.authorize() before invoking it, and aira.notarize() after. Denied calls throw AiraToolDenied (Python) or an Error (TypeScript). Failed calls report outcome="failed" so the action transitions correctly in Aira.

Install

pip install aira-sdk[openai-agents]
npm install aira-sdk
# peer dep
npm install @openai/agents

Full example — gated tool call

gated-openai-agent.py
from aira import Aira
from aira.extras.openai_agents import AiraGuardrail
from agents import Agent, Runner, function_tool

aira = Aira(api_key="aira_live_...")
guardrail = AiraGuardrail(
    client=aira,
    agent_id="payments-agent",
    model_id="gpt-4o",
)

def send_wire_transfer_impl(amount: float, to: str) -> str:
    """Raw tool function — side-effect happens here."""
    return f"Wire sent: €{amount} to {to}"

# Wrap it — this is where authorize() runs
send_wire_transfer = function_tool(
    guardrail.wrap_tool(send_wire_transfer_impl, "send_wire_transfer")
)

agent = Agent(
    name="payments",
    instructions="You are a payments agent.",
    tools=[send_wire_transfer],
)

result = Runner.run_sync(agent, "Send €75,000 to vendor-x")
gated-openai-agent.ts
import { Aira } from "aira-sdk";
import { AiraGuardrail } from "aira-sdk/extras/openai-agents";
import { Agent, run } from "@openai/agents";

const aira = new Aira({ apiKey: "aira_live_..." });
const guardrail = new AiraGuardrail(aira, "payments-agent", {
  modelId: "gpt-4o",
  strict: false,
});

async function sendWireTransferImpl(args: { amount: number; to: string }) {
  return `Wire sent: €${args.amount} to ${args.to}`;
}

// wrapTool() returns the wrapped execute function
const sendWireTransfer = {
  name: "send_wire_transfer",
  description: "Send a wire transfer",
  parameters: {
    type: "object",
    properties: {
      amount: { type: "number" },
      to: { type: "string" },
    },
    required: ["amount", "to"],
  },
  execute: guardrail.wrapTool(sendWireTransferImpl, "send_wire_transfer"),
};

const agent = new Agent({
  name: "payments",
  instructions: "You are a payments agent.",
  tools: [sendWireTransfer],
});

const result = await run(agent, "Send €75,000 to vendor-x");

What happens when a policy denies

  1. The model decides to call send_wire_transfer.
  2. The wrapper calls aira.authorize() before the tool body runs.
  3. If the policy engine denies, the wrapper throws AiraToolDenied (Python) or an Error (TypeScript).
  4. OpenAI Agents catches the exception, marks the tool call as failed, and surfaces the result to the model.
  5. The real transfer function never runs. The PolicyEvaluation row is persisted for audit.

strict mode (TypeScript only — Python always fails closed)

new AiraGuardrail(aira, "payments-agent", { strict: true });
  • strict: false (default): if authorize() fails due to network/5xx, warn and let the tool run.
  • strict: true: throw on any authorize failure. Use for deployments where missing a signature = don't act.

POLICY_DENIED always throws regardless.

When to use this vs calling authorize() inline

Use wrap_tool() when...Call authorize() inline when...
You want every tool on the agent gated, no tool-body changesYou want to gate a specific branch inside a tool
You're building a standard OpenAI Agents agentYou need the action_uuid directly in the tool body
You're OK with simple tool_call action typesYou want custom action_type per tool or branch

Known limits

  • Notarize is non-blocking. Notarize failures are logged but don't wedge the agent. The receipt is missing, the agent keeps running.
  • Input guardrails are not yet wired. AiraGuardrail currently wraps tools but doesn't register as an inputGuardrail — if you want to gate the whole run before the model even sees the prompt, call aira.authorize(action_type="agent_run", ...) yourself at the top of your runner.

Proof it works

  • Vercel AI — same wrapTool() pattern
  • LangChain — callback-handler alternative
  • Policies — configuring what the policy engine checks

On this page