Docs Frameworks

OpenAI Agents SDK

Status: verified. The adapter ships in kiff-guard, passes the conformance suite, and was checked against the real openai-agents v0.17.4 SDK plus a live model call — confirming the guardrail is accepted and reject_content genuinely skips the tool.

Shape: inverted-control. The SDK runs the tool after the guardrail returns a verdict, so the adapter uses Guard.observe() / Guard.decide_only(), not a run callback. See how the guard works.

Install

pip install "git+https://github.com/kiffhq/kiff-guard.git#subdirectory=packages/python/kiff-guard"
pip install openai-agents          # the framework you're guarding

(The openai extra maps to the openai-agents package.)

Observe — audit with zero config

Attach the guard as a tool input guardrail on a function_tool:

from agents import Agent, function_tool
from kiff_guard import Guard
from kiff_guard.adapters.openai_agents import kiff_tool_input_guardrail

guard = Guard(mode="observe")     # zero-config audit; no KIFF account
kiff_gd = kiff_tool_input_guardrail(guard)

@function_tool(tool_input_guardrails=[kiff_gd])
def refund_order(order_id: str, amount_cents: int) -> str:
    ...

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

In observe mode the guardrail records + learns and always allows the tool to run. Read the trail from guard.receipts.

Activate

from kiff_guard import export_yaml
print(export_yaml("my-domain", guard.catalog))

Review and activate in the dashboard.

Enforce — govern at runtime

Build the guard with a client and mode="enforce"; the @function_tool wiring is unchanged:

from kiff_guard import Guard, HTTPClient, ToolMap

client = HTTPClient(api_key="kiff_live_...", tool_map=ToolMap().bind(...))
guard = Guard(client=client, tenant="<tenant>", agent="support", mode="enforce")
kiff_gd = kiff_tool_input_guardrail(guard)

On a withheld decision the guardrail returns ToolGuardrailFunctionOutput.reject_content(reason) — the SDK skips the tool and hands the reason to the model instead of the tool result. Enforce fails closed on a guard error by default.

The seam (verified, v0.17.4)

@tool_input_guardrail runs before the tool executes. The callback gets data.context.tool_name, .tool_arguments (raw JSON string — the adapter parses it to a dict), and .tool_call_id. It returns allow() to run or reject_content(message) to skip.

Not needs_approval. needs_approval is the heavyweight human pause: it pauses the run, surfaces interruptions, and resumes via RunState. KIFF’s gate is a machine decision, so it belongs in the synchronous tool input guardrail. A withheld approval_required can still be bridged into the SDK’s native needs_approval flow on top of the gate.

Boundary: tool input guardrails apply to function_tools only. Hosted/built-in tools (web search, code interpreter, computer, shell, apply-patch) and handoffs bypass this pipeline; governing those needs a different seam.