> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hexgate.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAI Agents SDK

> Wrap an Agent with HexgateRunner — drop-in replacement for agents.Runner.

<Note>
  New to the adapters? Start with the [wrapping overview and comparison
  table](/adapters/overview#how-framework-wrapping-works).
</Note>

<Info>
  **Compatible versions:** see the [compatibility
  table](/adapters/overview#compatible-framework-versions) for the verified
  `openai-agents` range.
</Info>

## `HexgateRunner`

`HexgateRunner` is a drop-in replacement for `agents.Runner`. It wraps the
agent's tools with a `PolicyEnforcer` at construction time and opens a `HexgateContext`
scope around each `Runner.run` / `run_sync` / `run_streamed` call so role
resolution happens at call time.

```python theme={null}
import asyncio
from agents import Agent, function_tool
from dotenv import load_dotenv

from hexgate.runtime import HexgateContext
from hexgate.adapters.openai import HexgateRunner

INSTRUCTION = (
    "You are a DevOps assistant for a Kubernetes platform. Help engineers read "
    "service logs, restart services, and scale deployments. Pull the service "
    "name, replica count, and environment (dev/staging/prod) from the request "
    "and act directly — the policy layer gates sensitive actions."
)


@function_tool
def read_logs(service: str, env: str) -> str:
    """Return recent log lines for `service` in `env` (dev/staging/prod)."""
    return f"(stub) {service}@{env}: 200 OK, 200 OK, WARN upstream slow"


@function_tool
def restart_service(service: str, env: str) -> str:
    """Restart `service` in `env`."""
    return f"(stub) restarted {service}@{env}"


@function_tool
def scale_deployment(service: str, replicas: int, env: str) -> str:
    """Scale `service` to `replicas` pods in `env`."""
    return f"(stub) scaled {service}@{env} to {replicas} replicas"


async def main():
    load_dotenv()

    agent = Agent(
        name="devops_agent",
        instructions=INSTRUCTION,
        tools=[read_logs, restart_service, scale_deployment],
        model="gpt-4o-mini",
    )

    runner = HexgateRunner()  # picks up HEXGATE_API_KEY from env
    result = await runner.run(
        agent,
        "Scale the checkout service to 5 replicas in staging.",
        hexgate_context=HexgateContext(user_id="engineer_1", session_id="session_1", user_roles=["operator"]),
    )
    print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())
```

## Under the hood

* `HexgateRunner.run` calls `wrap_openai_agent`, which builds a `PolicySet` for
  `(api_key, agent.name, tool_names)`, constructs one `PolicyEnforcer`, and returns
  a `dataclasses.replace`'d copy of the agent with policy-gated tool copies — your
  original `agent` is untouched.
* The runner opens an `async with hexgate_context:` scope around the underlying `Runner.run*`
  call. When the model calls a tool, the guard asks `enforcer.decide(...)` for a
  `Decision`. On non-allow, it returns `decision.as_error_message()` — a
  `[policy_denied]` or `[approval_required]` markered string the model can
  interpret and recover from.
* The run executes inside `propagate_attributes(user_id=..., session_id=...,
  metadata={"user_roles": ...})`, so Langfuse spans carry the caller identity.

`run_sync` and `run_streamed` work the same way.

## Runnable example

`examples/devops_openai.py` — `HexgateRunner` (OpenAI Agents SDK) end-to-end.
