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

# OpenAI Agents SDK

> Use Xenovia with the OpenAI Agents SDK and Responses API.

The OpenAI Agents SDK is best paired with Xenovia through the **Responses API** (`POST /v1/responses`). Xenovia supports that endpoint natively, so each agent turn is traced and policy-checked without changing your orchestration flow.

## Setup

```bash theme={null}
pip install openai-agents
```

```python theme={null}
import os
from openai import AsyncOpenAI
from agents import Agent, Runner, OpenAIResponsesModel

client = AsyncOpenAI(
    api_key=os.environ["XENOVIA_API_KEY"],
    base_url=f"https://runtime.xenovia.io/a/{os.environ['XENOVIA_PROXY_ID']}/openai/v1"
)

model = OpenAIResponsesModel(
    model="gpt-4o-mini",
    openai_client=client
)
```

## Single agent

```python theme={null}
agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
    model=model
)

result = await Runner.run(agent, "What is the capital of France?")
print(result.final_output)
```

Each `Runner.run` call may produce multiple Responses API turns. Each turn creates an independent trace in Xenovia. Use a session ID to group them.

## Sessions via `previous_response_id`

The Responses API natively chains turns using `previous_response_id`. Xenovia's session plugin recognises this field and automatically resolves sessions across chained calls, so you do not need an explicit session ID header if the SDK manages `previous_response_id` for you.

To also group runs explicitly:

```python theme={null}
import uuid

session_id = str(uuid.uuid4())

client = AsyncOpenAI(
    api_key=os.environ["XENOVIA_API_KEY"],
    base_url=f"https://runtime.xenovia.io/a/{os.environ['XENOVIA_PROXY_ID']}/openai/v1",
    default_headers={"X-Xenovia-Session-Id": session_id}
)
```

Explicit `X-Xenovia-Session-Id` takes priority over the `previous_response_id` chain. Use explicit IDs when you need grouping across separate `Runner.run` calls.

## Multi-agent handoff

```python theme={null}
research_agent = Agent(
    name="Researcher",
    instructions="Search and summarise information on the given topic.",
    model=model
)

writer_agent = Agent(
    name="Writer",
    instructions="Write a clear report based on provided research.",
    model=model,
    handoffs=[research_agent]
)

result = await Runner.run(writer_agent, "Write a report on AI governance")
print(result.final_output)
```

Each agent in the handoff chain calls the Responses API independently. All calls are traced individually. Filter by `session_id` in the Traces view to see the full conversation in order.

## Agents with tools

```python theme={null}
from agents import function_tool

@function_tool
def get_policy_status(proxy_id: str) -> str:
    """Check the current policy status for a proxy."""
    return f"Policy active for proxy {proxy_id}"

agent = Agent(
    name="PolicyChecker",
    instructions="Check policy status when asked.",
    tools=[get_policy_status],
    model=model
)
```

Tool names defined in the agent are included in `input.tool_names` in the Rego policy. A block rule matching the tool name returns `403` before the model is called.

## Handling policy blocks

```python theme={null}
from openai import PermissionDeniedError

try:
    result = await Runner.run(agent, "Delete all user records")
except PermissionDeniedError as e:
    print(f"Blocked by policy: {e.message}")
```

The `X-Xenovia-Trace-Id` header on the `403` response identifies the blocking trace in the platform.
