Tawa docs
Integrations

OpenAI Agents SDK

Guardrail helpers using the Agents SDK's own input_guardrail/output_guardrail hooks.

The OpenAI Agents SDK ships purpose-built input_guardrail/output_guardrail hooks: input guardrails validate a message before it reaches the agent, output guardrails validate the agent's response before it reaches the user, and a tripwire raises a stoppable exception. tawaguard.agents_sdk wraps TawaGuard.analyze() as guardrails shaped exactly like that, so the Agents SDK runs the check itself — no manual block check before or after the agent call.

Install

pip install "tawaguard-sdk[agents]"

openai-agents is an optional extra — not a dependency of the base tawaguard-sdk package, so nothing pulls it in unless you import tawaguard.agents_sdk.

Add the guardrails

from agents import Agent, InputGuardrailTripwireTriggered, Runner
from tawaguard import AsyncTawaGuard
from tawaguard.agents_sdk import tawa_input_guardrail, tawa_output_guardrail

tawa = await AsyncTawaGuard.create("YOUR_API_KEY")

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
    input_guardrails=[tawa_input_guardrail(tawa)],
    output_guardrails=[tawa_output_guardrail(tawa)],
)

try:
    result = await Runner.run(agent, user_message)
except InputGuardrailTripwireTriggered as err:
    scan = err.guardrail_result.output.output_info  # the ScanResult that tripped it
    print(f"Blocked ({scan.action}): {scan.rationale}")

Both TawaGuard (sync) and AsyncTawaGuard are accepted — the sync client's blocking analyze() call runs off-thread via asyncio.to_thread so it can't stall the Agents SDK's event loop.

How tripping works

You don't raise anything yourself. The guardrail function returns a GuardrailFunctionOutput with tripwire_triggered set from the scan result, and the Agents SDK raises InputGuardrailTripwireTriggered/OutputGuardrailTripwireTriggered itself when that's True — halting the agent's execution immediately. The ScanResult that triggered it is always attached at err.guardrail_result.output.output_info, so you can inspect action, rationale, confidence, etc. from the except block.

The default blocking policy (default_should_block): always block on block/hard_block/soft_block, never on allow/ood, and defer to the server's own suggestion for the genuinely undecided actions (ambiguous/review/sandbox) — the same policy the Vercel AI SDK integration uses. The gateway already collapses review to allow or block per your app's setting, so against a current gateway the scan's action is allow or block and the default policy follows it directly. The undecided branch only applies to a gateway that predates the review flag.

Checking output too

tawa_output_guardrail scans the agent's final output the same way, raising OutputGuardrailTripwireTriggered instead:

except OutputGuardrailTripwireTriggered as err:
    scan = err.guardrail_result.output.output_info
    print(f"Output blocked ({scan.action}): {scan.rationale}")

It accepts either plain text or a structured Pydantic output type — a structured output is turned into text via model_dump_json() before being scanned.

Options

Both tawa_input_guardrail() and tawa_output_guardrail() accept:

OptionTypeDefaultDescription
should_blockCallable[[ScanResult], bool]default_should_blockDecide whether a scan result should trip the guardrail
extract_textCallable[..., str]scans the whole input/outputBuild the text handed to analyze()
on_reviewCallable[[ScanResult], None | Awaitable[None]]NoneCalled once per scan whenever result.needs_review is True, whether or not the guardrail trips, before the tripwire decision. May be a plain or a coroutine function. Errors raised inside it go to on_error, or are dropped when on_error is not set. See The review flag
on_errorCallable[[BaseException], None]NoneReceives an exception raised by on_review
cuid / tenant_id / policy_overrides / language—NoneForwarded straight to analyze() per call
namestr | None"tawa_input_guardrail" / "tawa_output_guardrail"Guardrail name, used for tracing

tawa_input_guardrail additionally accepts run_in_parallel (default True), matching the Agents SDK's own InputGuardrail option — whether the guardrail runs concurrently with the agent or before it starts.

Empty input/output (e.g. a tool-only turn with no text content) skips the scan entirely rather than sending an empty string — output_info is None and the tripwire never fires in that case.

Policy Studio integration

default_should_block only looks at the scan's raw action/suggestion — it never reads result.policy, the verdict from your org's Policy Studio rules. That verdict is already computed server-side on every scan; by default it's simply not checked.

There's no dedicated option for this yet (unlike the Vercel AI SDK integration's policy option) — should_block is already a plain callback, so pass your own that also checks result.policy:

from tawaguard.agents_sdk import default_should_block, tawa_input_guardrail

def policy_aware_should_block(result):
    if result.policy and "block" in result.policy.directives:
        return True
    return default_should_block(result)

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
    input_guardrails=[
        tawa_input_guardrail(tawa, should_block=policy_aware_should_block)
    ],
)

Activating a template in the dashboard's Policy Studio only changes what result.policy contains on future scans — it never blocks anything by itself. See Enforcing a policy for why, and the equivalent pattern for the raw SDK and the Vercel AI SDK.

On this page