Skip to Content

How do I trace an agent built with the OpenAI Agents SDK and deployed to a Kubernetes deployment?

Rius traces an OpenAI Agents SDK agent in a Kubernetes deployment when you call rius.init() once at process startup, before the agent runs, and wrap the entrypoint and tool bodies with observe. The agent keeps running in the pod as before, and spans export in the background. Call client.shutdown() when the pod receives SIGTERM so the last spans leave the pod, and name the agent with service_name or RIUS_AGENT_NAME. The OpenAI Agents SDK integration covers the framework side, and this page covers the deployment side.

How it works

  • One init per process. The OpenAI Agents SDK drives the OpenAI client underneath, so the openai integration captures every model call as a generation with tokens and cost. With a default init(), every installed integration turns on automatically, so call it once, at the top of your entrypoint module, before you build agents or call Runner. See Integrations for how enabling works.
  • Run structure comes from observe. The agent loop and tool executions run inside the framework, so an observe wrapper around the entrypoint gives you the root agent span, and one on each tool body gives you a tool span.
  • Export is in the background. Spans queue in the process and leave in batches from a background thread, so tracing never sits on your request path.
  • Each pod is its own process. Every process that calls init() has its own identity, and the heartbeat, on by default, keeps the agent visible on the Agents page for as long as the pod runs. The heartbeat suits a Deployment because the process is long-lived.
  • Shutdown and flushing. Kubernetes stops a pod with SIGTERM. The SDK installs no signal handlers, and Python’s default response to SIGTERM ends the process without running its exit hook, so you handle the signal. A pod stopped by an unhandled SIGTERM, or killed with SIGKILL, sends no final stopped ping, so its instance shows as gone, which is how Rius marks a crash. A hard kill can also lose the last batch of spans.

Set it up

  1. Install the SDK with the OpenAI extra in your image, next to the framework. The extra installs the instrumentation, not the library itself.

    pip install "glassflow-rius[openai]" openai-agents
  2. Put the API key in a Kubernetes Secret and expose it to the container as RIUS_API_KEY, next to RIUS_SERVICE_NAME, so init() needs no arguments. Store the API key in a Secret named rius with the key api-key, and reference it from the container. Kubernetes sends SIGTERM and waits terminationGracePeriodSeconds (30 by default) before SIGKILL. A batch export can take up to 30 seconds by default, so give the pod enough time.

    apiVersion: apps/v1 kind: Deployment metadata: name: my-agent spec: replicas: 2 selector: matchLabels: app: my-agent template: metadata: labels: app: my-agent spec: terminationGracePeriodSeconds: 60 containers: - name: agent image: registry.example.com/my-agent:1.4.0 env: - name: RIUS_SERVICE_NAME value: my-agent - name: RIUS_API_KEY valueFrom: secretKeyRef: name: rius key: api-key
  3. Call init() at startup, shut the client down on SIGTERM, turn off the framework’s own OpenAI-platform tracing if Rius should be the single path, and wrap the entrypoint and the tool bodies. Keep @function_tool outermost on tools.

    import signal import sys import rius from agents import Agent, Runner, function_tool, set_tracing_disabled from rius import SpanKind client = rius.init() # reads RIUS_API_KEY and RIUS_SERVICE_NAME set_tracing_disabled(True) def on_sigterm(signum, frame): client.shutdown() # drain queued spans, send the stopped heartbeat sys.exit(0) signal.signal(signal.SIGTERM, on_sigterm) @function_tool @rius.observe(kind=SpanKind.TOOL) def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) support_agent = Agent( name="support_agent", instructions="Answer support questions; check accounts with the tool.", tools=[account_status], model="gpt-5-mini", ) @rius.observe(name="handle-ticket", kind=SpanKind.AGENT) def handle_ticket(ticket: str) -> str: result = Runner.run_sync(support_agent, ticket) return str(result.final_output)
  4. Name the agent. Rius groups traces under the agent name, which defaults to the service name. Set RIUS_AGENT_NAME if you want a different name from service_name. Inside the framework, name each Agent with underscores, as in support_agent, because hyphens get rewritten in the generated handoff tool name and the framework warns on every run. To compare rollouts on the Agents page, set RIUS_MAIN_AGENT_VERSION to a new value on each release, since that is the version the page compares.

  5. If your server already handles SIGTERM, such as uvicorn, call client.shutdown() in its shutdown hook instead, for example a FastAPI lifespan. Start the process in exec form so it receives the signal as PID 1. Never call flush() inside request handlers, because it blocks until the export completes. The Reliability page covers batching and failure behavior.

  6. Scope each conversation with rius.session(), reusing your conversation or ticket id, so every turn groups together. Then send one request that calls a tool and open the trace in the console.

What you get

  • A root agent span for each run, the model call spans with model, tokens and cost, and named tool spans under it.
  • Cost per call as a list-price estimate in US dollars, computed by Rius from the model and token counts. You never send a cost value.
  • The agent on the Agents page under the name you chose, with an instance for each running pod.
  • A clean stop that shows as stopped when you call shutdown(), and a crash that shows as gone.
  • Sessions that group the turns of one conversation.

Handoffs produce no span of their own. They appear as consecutive model calls from different agents. If handoff boundaries matter, add an observe wrapper for each agent entry. Responses API calls are all named Response, so the span name does not tell you which agent spoke. Streamed runs carry token counts only when usage is enabled on the underlying OpenAI client.

When something else fits

  • If your agent is not built on the OpenAI Agents SDK, pick the page for your framework from Integrations.
  • If the pod’s runtime is Lambda-style and freezes between invocations, set heartbeat=False and flush at the end of every invocation instead of relying on the exit hook.
  • If the agent is written in TypeScript, there is no OpenAI Agents SDK integration today, so trace its calls with manual generations.

Still stuck? Get help

Last updated on