Skip to Content
Rius

How do I attribute the Anthropic API traces to end users?

Rius attributes your Anthropic API traces to an end user when you wrap the request in a user scope: rius.user in Python or withUser in TypeScript. Every span started inside the scope carries that user’s ID as user.id, including the span Rius creates for each Anthropic call. The Users page then shows each user’s traces, failures, cost and sessions. For the wider picture of watching agents in production, see Monitor AI agents in production.

How it works

The Anthropic integration traces each messages.create call without changes to your call sites. It does not know who the request was for, so you supply that.

  • A user scope stamps the identifier you pass as the user.id attribute on every span started inside it. This includes spans created by auto-instrumentation, so the Anthropic call span gets it without extra code.
  • Rius reads the end user from user.id, enduser.id or enduser.pseudo.id, taking the first one that is set.
  • Scopes nest, and the innermost wins. They unwind when the block ends, follow async work, and combine with a session scope.
  • observe takes no user argument, because a decorator is bound once at import time and a user belongs to each request. Put the scope around the request instead.
  • There is no process-wide default and no environment variable for the user. A global user would attribute every request a process handles to one person.
  • A trace with no end user on any span counts as anonymous. See Tracing your code for the full user and session scopes.

Set it up

  1. Install the Anthropic integration and call init() once at startup, as covered in Anthropic. Use the client exactly as before.
  2. Wrap the code that handles one request in a user scope, passing the ID your application already has for that person. In Python:
import rius from anthropic import Anthropic rius.init(api_key="ri_...", service_name="my-agent") client = Anthropic() with rius.user(request.user_id): response = client.messages.create( model="claude-haiku-4-5-20251001", max_tokens=200, messages=[{"role": "user", "content": query}], )
  1. In TypeScript, use withUser around the same work:
import { withUser } from '@glassflow-ai/rius' await withUser(request.userId, async () => { await client.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 200, messages: [{ role: 'user', content: query }], }) })
  1. Scope the whole request, not only the Anthropic call, so tool calls and agent steps in the same run carry the user too.
  2. Prefer an opaque ID over an email or a name. The value is stored on every span and shown in the console. If only sensitive identifiers exist, hash them first.
  3. Verify in one minute. Run one request inside a 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.

What you get

The Users page is a register of the end users your agents serve. Only finished traces count, so a run appears once every span of it has ended. Per user it shows:

  • Traces and Failed traces, meaning traces whose root span ended in error.
  • Error rate, the share of the user’s traces with at least one errored span, including runs that recovered.
  • Cost, total and average per trace. Cost is a list-price estimate in US dollars, computed by Rius from the model and token counts of the Anthropic calls. You never send it.
  • Sessions the user’s traces belong to, and the agents that served them.
  • TTFT p50, the median time to first token over the user’s streaming calls. Auto-instrumented Anthropic streams do not record time to first token, so trace those calls as a manual generation with record_first_token() if you need it. See Tracing your code.

The user list covers 15m, 1h, 1d, 7d (the default) or 30d, sorts by traces, failed traces or cost, and shows the top 50. To reach anyone else, search by exact user ID. This takes you from a support ticket to the failing span: find the user, check Failed traces, open the trace from the time they wrote in.

A line above the list says how many traces carry an end user, for example 400 of 471. Everything else is in the anonymous row. If that row is large, some code paths run outside a user scope. You can also filter the trace list by user, by the user_id field in the filter builder, or by the user_id argument in the MCP tools list_users and user_activity_summary.

When something else fits

If a process serves exactly one person, such as a CLI run, you still scope that work with a user, because Rius has no process-wide user default. For coding sessions, the Rius plugin attributes traces to the developer’s sign-in email from version 0.6.2, so you do not set a user yourself. When a trace is anonymous, the Sent by column in the trace list shows whose session it is.

Still stuck? Get help

Last updated on