Agent Governance Foundation

How to Add Authorization Gates to OpenAI Agents SDK Tools

Agent Governance Foundation
openai-agents-sdkauthorizationpythonintegration

Every tool in an OpenAI Agents SDK agent is a FunctionTool — there's no separate "graph node" or "gate" concept to reason about, unlike some other agent frameworks. That makes the integration surface simple: one wrapper, applied once per tool. The interesting part is how that wrapper works, and why it doesn't reuse the SDK's own guardrail primitive.

Why you need this

FunctionTools can do real things — write to a database, call an internal API, send a message. The model decides which tool to call and with what arguments; nothing stops it from calling a tool your policy would reject unless something checks before the tool actually runs.

Prerequisites

pip install agf-sdk[openai-agents]
export AGF_API_KEY=agfk_your_key_here

Guard a function tool

guard_function_tool wraps an already-built FunctionTool and returns a new one — the original is never mutated. Swap the guarded version into your agent in place of the original:

import os
from agents import Agent, function_tool
from agf.client import AGFClient
from agf.keys import build_self_signed_chain
from agf.openai_agents import guard_function_tool

client = AGFClient(api_key=os.environ["AGF_API_KEY"])
AGF_AGENT_ID = "did:agf:agt_01abc"


def _refund_chain_provider(ctx):
    # Built fresh per call — a chain built once expires in 5 minutes.
    return build_self_signed_chain(AGF_PRIVATE_KEY_PEM, AGF_AGENT_ID, "tool:issue_refund", "agf")


@function_tool
def issue_refund(order_id: str, amount: str) -> str:
    return process_refund(order_id, amount)


guarded_issue_refund = guard_function_tool(
    issue_refund,
    client,
    agent_id=AGF_AGENT_ID,
    action_type="tool:issue_refund",
    chain_provider=_refund_chain_provider,
    validate_execution=True,
    report_outcome=True,
)

agent = Agent(name="support-agent", tools=[guarded_issue_refund])

Why not the SDK's own guardrail hook?

The OpenAI Agents SDK already ships ToolInputGuardrail — a native tool-level hook attached via function_tool(..., tool_input_guardrails=[...]). We looked at building on it and decided against it, for two concrete reasons:

  • Its raise_exception() behavior collapses every failure — DENY, REVIEW_REQUIRED, anything — into one generic ToolInputGuardrailTripwireTriggered exception. The real reason lives in a nested .output.output_info field, not as a distinguishable exception type your code can except on.
  • There's no output-side hook that sees both success and exception for the same call. Reporting an execution outcome (did the tool actually run, or not) needs exactly that.

guard_function_tool wraps on_invoke_tool directly instead — the same "wrap the tool object" pattern the LangChain and CrewAI adapters already use. That preserves AGFDeniedError and AGFReviewRequiredError as their real, specific types, and lets a single try/except around the real call drive accurate outcome reporting.

Accurate outcome reporting on tool errors

One real caveat, worth knowing before you turn report_outcome on: @function_tool's default error handling catches a tool's internal exception and turns it into a string result the model sees — before report_outcome ever gets a chance to see it. This is a real, verified behavior of the underlying SDK, not an AGF limitation.

If accurate "not executed" reporting on internal tool failures matters for your integration, build the tool with that catch disabled:

@function_tool(failure_error_function=None)
def issue_refund(order_id: str, amount: str) -> str:
    return process_refund(order_id, amount)

Without it, a tool that raises internally still gets reported as "executed" — which is accurate to what actually happened at the guard boundary (the tool did run; it just failed inside), just worth knowing isn't the same as "the failure was caught and reported."

Next steps


The AGF Python SDK is open source. GitHub →