Docs/OpenAI Agents SDK Guide
OpenAI Agents SDK Integration

OpenAI Agents + Tork Governance

Add on-device PII governance around OpenAI Agents SDK runs. Govern inputs, outputs and tool arguments, redact or deny by policy, and keep a local receipt for every decision.

I/O Governance

Validate all inputs and outputs

Tool Safety

Govern tool arguments for PII

Streaming

Works with streaming responses

Compliance

A local receipt per governed call

Installation

Install Tork with OpenAI Agents SDK dependencies.

bash
pip install tork-governance openai-agents

The adapter ships inside the on-device package: import it from tork_governance.adapters.openai_agents. It exports exactly three names: TorkOpenAIAgentsMiddleware, GovernedOpenAIAgent and GovernedRunner. The OpenAI Agents SDK itself is imported as from agents import Agent, Runner. PII detection and the allow/redact/deny decision run on your machine; no API key is required.

TorkOpenAIAgentsMiddleware

Central middleware for governing OpenAI Agents.

The middleware is the governance boundary around an agent run: callprocess_input() before Runner.run_sync() andprocess_output() after it. Each call returns aGovernanceResult and appends a summary tomiddleware.receipts.

pythonmiddleware_example.py
from agents import Agent, Runner
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

# On-device governance: PII detection and the decision run on this machine.
# No API key is needed. agent_id is recorded on the middleware's receipts.
middleware = TorkOpenAIAgentsMiddleware(agent_id="research-assistant")

agent = Agent(
    name="research-assistant",
    instructions="You are a helpful research assistant.",
)

# Govern the input, run the agent with the SDK's own Runner, govern the output
input_result = middleware.process_input("What are the latest developments in AI safety?")
run = Runner.run_sync(agent, input_result.output)
output_result = middleware.process_output(str(run.final_output))

print(output_result.output)                     # governed (redacted if PII) text
print(output_result.receipt.receipt_id)         # local receipt for this decision
print(middleware.receipts)                      # [{type, agent_id, receipt_id, action}, ...]

Deny instead of redact

Blocking inputs and outputs that contain PII.

The on-device SDK has one decision rule: when PII is detected it applies the Tork instance'sdefault_action (REDACT unless you set DENY). No exception classes exist; branch on result.action. The adapter'sGovernedOpenAIAgent / wrap_agent() wrapper calls agent.run(), which the OpenAI Agents SDK'sAgent does not have, so the explicit pattern below is the one that works.

pythongoverned_agent.py
from agents import Agent, Runner
from tork_governance import Tork, GovernanceAction
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

# By default PII is REDACTED and the text goes through. To block instead,
# build the middleware on a Tork whose default_action is DENY.
strict = Tork(default_action=GovernanceAction.DENY)
middleware = TorkOpenAIAgentsMiddleware(tork=strict, agent_id="customer-service")

agent = Agent(
    name="support-bot",
    instructions="""You are a customer support assistant.
    Help customers with their questions professionally.
    Never share internal company information.""",
)

def safe_run(user_input: str) -> str:
    input_result = middleware.process_input(user_input)
    if input_result.action == GovernanceAction.DENY:
        # PII in the user's message and the policy is DENY
        return "I cannot process that request. Please rephrase without personal information."

    run = Runner.run_sync(agent, input_result.output)

    output_result = middleware.process_output(str(run.final_output))
    if output_result.action == GovernanceAction.DENY:
        return "I apologize, but I cannot provide that information."
    return output_result.output

response = safe_run("How do I reset my password?")   # allowed
response = safe_run("My SSN is 123-45-6789")         # denied: PII detected

Tool Call Governance

Govern tool arguments and tool results for PII.

check_tool_call(tool_name, tool_args) governs the rendered arguments for PII and returns a GovernanceResult. It does not block by itself and it has no built-in list of dangerous tools; your tool body decides what to do with the result.

pythontool_governance.py
from agents import Agent, function_tool
from tork_governance import GovernanceAction
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

middleware = TorkOpenAIAgentsMiddleware(agent_id="tool-agent")

# check_tool_call() governs the string "<tool_name>: <tool_args>" for PII and
# returns a GovernanceResult. It never raises, and it has no notion of a
# "dangerous" tool — that judgement stays in your code.
def tool_args_allowed(tool_name: str, tool_args: dict) -> bool:
    result = middleware.check_tool_call(tool_name, tool_args)
    return result.action != GovernanceAction.DENY

@function_tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to a customer."""
    # Redact PII from the body before it leaves your system
    governed_body = middleware.process_output(body).output
    return f"Email sent to {to}: {governed_body}"

@function_tool
def execute_sql(query: str) -> str:
    """Execute a SQL query on the database."""
    if not tool_args_allowed("execute_sql", {"query": query}):
        return "Blocked: query contained personal data"
    return f"Executed: {query}"

agent = Agent(
    name="tool-agent",
    instructions="Use the tools to help the customer.",
    tools=[send_email, execute_sql],
)

# Every check is receipted on the middleware
for entry in middleware.receipts:
    print(entry["type"], entry.get("tool_name"), entry["action"], entry["receipt_id"])

Tool deny-lists are yours to keep

python
# There is no built-in list of blocked tool names in tork-governance 0.26.1.
# check_tool_call(tool_name, tool_args) governs the text "<name>: <args>" for
# PII and returns a GovernanceResult; it does not judge the tool itself.
# If you need a deny-list, keep it in your code:

DANGEROUS_TOOLS = {"shell", "exec", "eval", "subprocess", "rm", "drop_table"}

def tool_allowed(tool_name: str) -> bool:
    return tool_name not in DANGEROUS_TOOLS

GovernedRunner

What create_governed_runner() does, and when it applies.

GovernedRunner.run(agent, text) governs the input, callsagent.run(text) and governs the output. It works with any object that has a run() method. The OpenAI Agents SDK'sAgent does not, so for that SDK use the explicitprocess_input() / process_output() pattern.

pythonrunner_example.py
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

middleware = TorkOpenAIAgentsMiddleware(agent_id="multi-agent-system")

# GovernedRunner.run(agent, text) governs the input, then calls agent.run(text)
# on the object you pass, then governs the output. It therefore only works with
# an agent object that exposes a run() method. The OpenAI Agents SDK's Agent
# does not — Runner.run_sync(agent, text) is the SDK's entry point — so with
# that SDK the runner falls through to a placeholder string, not a model call.
# Use process_input()/process_output() around Runner.run_sync instead, as shown
# in the middleware example above.
runner = middleware.create_governed_runner()

class EchoAgent:
    """Any object with run(text) -> str works with GovernedRunner."""
    def run(self, text: str) -> str:
        return f"echo: {text}"

result = runner.run(EchoAgent(), "Contact me at jane@example.com")
print(result)   # "echo: Contact me at [EMAIL_REDACTED]"

Manual Input/Output Processing

Direct governance for custom implementations.

process_input() and process_output() are the whole surface: each returns a GovernanceResult with the action, the governed text, the PII findings and a local receipt.

pythonmanual_processing.py
from tork_governance import GovernanceAction
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

middleware = TorkOpenAIAgentsMiddleware(agent_id="manual-governance")

# process_input() / process_output() each return a tork_governance.GovernanceResult:
#   .action   GovernanceAction (ALLOW / REDACT / DENY / ESCALATE)
#   .output   the text to use (PII replaced when action is REDACT)
#   .pii      PIIResult: has_pii, types, count, matches
#   .receipt  Receipt minted on this machine

def process_user_message(message: str) -> str:
    result = middleware.process_input(message)
    if result.action == GovernanceAction.DENY:
        raise ValueError(f"Input blocked: {result.receipt.receipt_id}")
    if result.action == GovernanceAction.REDACT:
        print(f"Redacted PII types: {[t.value for t in result.pii.types]}")
    return result.output

def process_agent_response(response: str) -> dict:
    result = middleware.process_output(response)
    if result.action == GovernanceAction.DENY:
        return {"text": "Response blocked by safety policies.", "blocked": True}
    return {
        "text": result.output,
        "receipt_id": result.receipt.receipt_id,
        "input_hash": result.receipt.input_hash,
        "output_hash": result.receipt.output_hash,
    }

processed = process_user_message("Hello, how are you?")
# ^ ALLOW: returns the original text

processed = process_user_message("My credit card is 4111-1111-1111-1111")
# ^ REDACT (the default): returns "My credit card is [CREDIT_CARD_REDACTED]"

response = process_agent_response("Reach me at jane@example.com")
# ^ REDACT: text has the email replaced; receipt fields are returned

Streaming Support

Governance with streaming responses.

Govern the input before streaming starts and the complete output after it ends. Tokens are shown to the caller before the output check runs, so that check records and redacts what you keep, it cannot retract what was already streamed.

pythonstreaming.py
from agents import Agent, Runner
from tork_governance import GovernanceAction
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

middleware = TorkOpenAIAgentsMiddleware(agent_id="streaming-agent")

agent = Agent(name="assistant", instructions="You are a helpful assistant.")

async def stream_with_governance(user_input: str):
    """Govern the input before streaming and the full output after it."""
    input_result = middleware.process_input(user_input)
    if input_result.action == GovernanceAction.DENY:
        yield "Error: input blocked by governance policy"
        return

    # Stream with the SDK's own streaming runner. Tokens reach the caller
    # before the output check runs — the check cannot retract them.
    collected = []
    run = Runner.run_streamed(agent, input_result.output)
    async for event in run.stream_events():
        if event.type == "raw_response_event" and hasattr(event.data, "delta"):
            collected.append(event.data.delta)
            yield event.data.delta

    output_result = middleware.process_output("".join(collected))
    print(f"Receipt: {output_result.receipt.receipt_id} ({output_result.action.value})")

Multi-Agent Workflows

Govern complex agent teams and routing.

Share one middleware across specialised agents so every call is governed with the samepolicy_version and lands in the same receipts list.

pythonmulti_agent.py
from agents import Agent, Runner
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

# One middleware, one policy version, shared receipts for the whole team
middleware = TorkOpenAIAgentsMiddleware(agent_id="customer-support-team")

triage_agent = Agent(
    name="triage",
    instructions="""You are a triage agent. Reply with exactly one category
    for the customer request: billing, technical, or other.""",
)

billing_agent = Agent(
    name="billing",
    instructions="You handle billing questions. Never share card or bank details.",
)

technical_agent = Agent(
    name="technical",
    instructions="You handle technical support. Never share internal architecture.",
)

def governed(agent: Agent, text: str) -> str:
    """Govern in, run, govern out — the same three lines for every agent."""
    input_result = middleware.process_input(text)
    run = Runner.run_sync(agent, input_result.output)
    return middleware.process_output(str(run.final_output)).output

def handle_customer_request(request: str) -> str:
    category = governed(triage_agent, request).lower().strip()

    if "billing" in category:
        return governed(billing_agent, request)
    if "technical" in category:
        return governed(technical_agent, request)
    return governed(triage_agent, f"Handle directly: {request}")

print(handle_customer_request("I need help with my invoice from last month"))

Advanced Patterns

Async execution and compliance receipts

python
import asyncio
from agents import Agent, Runner
from tork_governance.adapters.openai_agents import TorkOpenAIAgentsMiddleware

middleware = TorkOpenAIAgentsMiddleware(agent_id="async-agent")

async def governed_run(agent: Agent, user_input: str) -> str:
    # Governance is synchronous and on-device (fast, CPU-bound);
    # only the model call is awaited.
    input_result = middleware.process_input(user_input)
    run = await Runner.run(agent, input_result.output)
    return middleware.process_output(str(run.final_output)).output

async def main():
    agents = [
        Agent(name=f"agent-{i}", instructions="Be helpful.")
        for i in range(3)
    ]

    results = await asyncio.gather(*[
        governed_run(agent, f"Answer question {i}")
        for i, agent in enumerate(agents)
    ])

    for i, text in enumerate(results):
        print(f"Agent {i}: {text[:100]}...")

    # One receipt entry per governed call, across every agent
    print(len(middleware.receipts))

asyncio.run(main())

Best Practices

Share one middleware

One TorkOpenAIAgentsMiddleware per system keeps a single policy version and one receipts list.

Govern tool arguments before side effects

check_tool_call() returns a GovernanceResult; act on result.action inside the tool. Nothing is blocked by default.

Branch on result.action

There are no exception classes. Compare against GovernanceAction.DENY and return a user-friendly message.

Use the SDK's own Runner for async and streaming

Governance is synchronous and on-device; await Runner.run() or iterate Runner.run_streamed() between the two checks.

Persist the receipts yourself

Receipts are local dataclasses with SHA-256 input/output hashes. They reach the dashboard only with Tork(api_key=...), as client attestations.

Decision Values

The adapter defines no exception classes. Every governed call returns aGovernanceResult whose .action is one of:

GovernanceAction.ALLOWNo PII found; output equals input
GovernanceAction.REDACTPII found and replaced with typed placeholders (the default)
GovernanceAction.DENYPII found and Tork was built with default_action=DENY
GovernanceAction.ESCALATEPII found and Tork was built with default_action=ESCALATE

Imports Reference

python
from tork_governance import Tork, GovernanceAction, GovernanceResult, Receipt
from tork_governance.adapters.openai_agents import (
    TorkOpenAIAgentsMiddleware,  # process_input / process_output / check_tool_call
    GovernedOpenAIAgent,         # Wrapper; needs an agent object with run()
    GovernedRunner,              # Runner; needs an agent object with run()
)

Next Steps

Configure policies in the dashboard and explore other framework integrations.

Documentation

Learn to integrate TORK

Upgrade Plan

Current: free

Support

Get help from our team