Skip to Content

How do I attribute Pydantic AI traces to end users?

Rius attributes Pydantic AI traces to an end user when you wrap each agent run in rius.user(...) with your own user identifier. Every span started inside that block carries user.id, and the Users page then shows that person’s traces, failures, cost and time to first token. Spans outside any user scope count as anonymous.

How it works

Pydantic AI emits its own OpenTelemetry spans for agent runs, model requests and tool calls. rius.init() installs the tracer provider and Agent.instrument_all(True) sends those spans through it. For the integration itself, see Pydantic AI.

The end user comes from a user scope that you open around each request. The scope rides OpenTelemetry context like a session scope: it nests (the innermost wins), unwinds when the block ends, follows async work and combines with rius.session(). Rius stamps user.id on every span started inside it, so the invoke_agent root, the chat generations and the execute_tool spans all name the same person.

A few rules come with this:

  • observe takes no user argument. A decorator is bound once at import time, but a user belongs to each request, so scope the request instead.
  • There is no process-wide default and no environment variable for the user. A global user would attribute every request the process handles to one person.
  • Nothing is minted for you. A span outside any user scope is anonymous.
  • Use an opaque identifier rather than an email or a name, because the value is stored on every span and shown in the console. If only sensitive identifiers exist, hash them first.

The Users section of the tracing guide covers the scope in full.

Set it up

  1. Install the SDK with Pydantic AI and call rius.init() as in the Pydantic AI guide. No extra is needed.
pip install glassflow-rius pydantic-ai
  1. Open the user scope around each run, using the identifier your application already has for that person. The scope has to wrap the call that starts the agent run.
import rius from pydantic_ai import Agent rius.init(api_key="ri_...", service_name="my-agent") Agent.instrument_all(True) support_agent = Agent( "openai:gpt-5-mini", instructions="Answer support questions; check accounts with the tool.", ) with rius.user(request.user_id): result = support_agent.run_sync(ticket)
  1. If the run belongs to a conversation, add a session scope too. Both scopes can be active at once, and the user then ties together every conversation that person had.
with rius.user(request.user_id), rius.session(conversation_id): result = support_agent.run_sync(ticket)
  1. Verify in one minute. Run one request inside the scope with a test ID such as test-user-1. Open Users, type test-user-1, click Find, and open the row. Once the trace has finished, the page shows 1 trace, your agent under Agents that served this user, and the trace under Recent traces.

Put the scope at the one place where each request enters your code, such as the request handler. Any code path that runs outside it lands in the anonymous row.

What you get

The Users page lists the end users your agents serve, not the members of your Rius organization. Figures count only finished traces.

  • A list per window. Choose 15m, 1h, 1d, 7d (the default) or 30d. Each row shows the user’s traces, failed traces, cost, the agents that served them and when they were last seen. Sort by traces, failed traces or cost. The list shows the top 50 for the sort, so search by exact user ID to reach anyone else.
  • Attribution coverage. A line above the list reads like “400 of 471 traces carry an end user”. The anonymous row always comes first. If it is large, some code paths run outside a user scope.
  • One page per user. Tiles show Traces, Failed traces, Error rate, Cost with average per trace, Sessions and TTFT p50. Below them you see up to three agents that served the user, their 8 most recent traces and their 6 latest traces with an errored span.
  • From a support ticket to the failing span. Search the user’s exact ID, open their page, check Failed traces, then open the trace from the time they wrote in.
  • Cost and failure patterns. Sort by Cost to see who drives spend, or by Failed to see whether a few users account for most failures. Compare a user’s numbers with the agent’s own on the Agents page.
  • Filtering elsewhere. The trace list has a User column, and the filter builder accepts the user_id field.
  • Written summaries. When root-cause analysis is on for the workspace, an admin can click Summarise this user for a readable account of that user’s recent runs. Each summary counts as one analysis against the analysis budget.

For the wider picture of watching agents in production, see Monitor AI agents in production.

When something else fits

  • If you run Pydantic AI without the Rius SDK, point your own OpenTelemetry setup at the Rius ingest endpoint and set user.id (or enduser.id, or enduser.pseudo.id) on the spans yourself. Rius takes the first one that is set.
  • If a process serves one person for its whole life, there is still no process-wide user. Open one user scope around the work.

Still stuck? Get help

Last updated on