How do I trace a Python AI agent running in a Docker container?
Rius traces a Python agent in a Docker container when you call rius.init() once at process start and pass the API key and service name to the container as environment variables. Spans are exported from a background thread, and you flush before the process exits or freezes. Once init runs, every installed integration is switched on automatically, so your model calls arrive as spans with tokens and cost.
How it works
- One init per process. Call
init()as early as possible in your entrypoint. A second globalinit()logs a warning and returns the existing client unchanged. To reconfigure, callclient.shutdown()first. - Background export. Spans queue in memory and leave in batches from a background thread, so the network is never on your agent’s hot path. In Python, pending spans also flush at interpreter exit on a clean shutdown.
- Short runs and hard stops. A container that runs one task and exits can finish before the first scheduled batch fires, so call
client.flush()before it ends. If the container is killed hard, the last batch can be lost. - Freezing runtimes. If your container image runs in a Lambda-style runtime, the process is suspended between invocations and a frozen thread exports nothing. Call
client.flush()at the end of every invocation and turn the heartbeat off withRIUS_HEARTBEAT=false. - Replicas. Each process start is a fresh instance under the same agent name, so three containers show as three instances of one agent. Keep
service_nameconstant across replicas and deploys. - Forked workers. With gunicorn or multiprocessing inside the container, do nothing at fork time. The exporter re-creates its thread in each child, and each worker gets its own instance.
Set it up
-
Install the SDK in your image, along with the library your agent calls. An extra installs only the instrumentation, never the library itself. The installation page covers the full setup.
pip install "glassflow-rius[openai]" openai -
Pass the settings to the container as environment variables.
init()reads them, and explicit arguments win over the environment:RIUS_API_KEY: your key, which starts withri_. Keep it out of the image and inject it at run time.RIUS_SERVICE_NAME: a constant name for your agent. Without it, spans carryunknown_service.RIUS_SERVICE_VERSION: the build this container runs, for example your image tag. It is stamped on spans asservice.version.RIUS_MAIN_AGENT_VERSION: the version of the agent definition (prompt, tools, policy). It is stamped asrius.main_agent.version, the version the Agents page compares.
-
Call
init()once in the entrypoint, run your agent, then flush and shut down at the end of a short-lived run:import rius client = rius.init() # reads RIUS_API_KEY, RIUS_SERVICE_NAME and the rest # ... run your agent ... client.flush(timeout_millis=30_000) # force an export client.shutdown() # drain, stop the thread, release init()For a long-running server, you can skip the explicit calls in Python and rely on the exit hook on clean shutdown. Never call
flush()inside request handlers, because it blocks until the export completes. -
Keep the heartbeat at its default for a long-lived container. It is on by default, needs
RIUS_API_KEY(or anAuthorizationheader), and powers the Agents view.RIUS_HEARTBEAT_INTERVALdefaults to 15 seconds. Keep it at 15 or below, because the platform marks an instancestaleafter 30 seconds andgoneafter 60 seconds without a ping. Without credentials, or withRIUS_HEARTBEAT=false, the agent is marked no heartbeat and has no instances. -
If a busy agent drops spans, raise
OTEL_BSP_MAX_QUEUE_SIZE(default 2048) before lowering the sample rate. For quicker shutdowns, lowerOTEL_BSP_SCHEDULE_DELAY(default 5000 ms). The reliability page lists the batching variables and the retry rules.
What you get
- Every model call and tool call from the container as a trace, with tokens and a list-price cost estimate in US dollars computed by Rius. The integrations page lists the libraries and frameworks that are traced automatically.
- One agent in the Agents view, with one instance per container process.
- A clean exit and a crash told apart.
shutdown()sends a finalstoppedping, while an unhandledSIGTERMor aSIGKILLsends none, so the instance reportsgone. The SDK installs no signal handlers. - Spans from short jobs that would otherwise be lost, because you flush before the process ends.
- Optional partial spans (
RIUS_PARTIAL_SPANS), which export a content-free snapshot when each span starts. A run that crashes leaves a record of what it was doing.
When something else fits
- If the container runs a Lambda-style function, flush at the end of every invocation and turn the heartbeat off, since heartbeats are a long-lived-process signal.
- If the agent is written in TypeScript or JavaScript, use the TypeScript SDK, which registers no exit hook, so you must call
await client.shutdown()yourself.