How do I attribute AutoGen traces to end users?
Rius attributes AutoGen traces to an end user when you run the agent inside a rius.user() scope. Every span started in the scope carries that user’s ID as user.id. The Users page then shows each person’s traces, failures, cost and time to first token. The observe decorator takes no user argument, so the scope goes around the call, once per request.
How it works
A user is the person a request served. You pass the identifier your application already has, and the SDK stamps it as user.id on every span started inside the scope, including spans that auto-instrumentation creates. For AutoGen, that covers the invoke_agent and execute_tool spans the framework emits and the ChatCompletion generations from the openai integration. The AutoGen integration page lists these spans.
- Scope per request, not per function.
observeis bound once at import time, while a user is a property of each request. Open the scope in the code that receives the request, around your decorated entrypoint. - No default, no environment variable. A process-wide user would assign every request to one person, so a span outside any user scope is anonymous.
- Scopes nest and combine. The innermost user wins, and a user scope works with a session scope. Scoping a run with
rius.session()and your conversation ID, as the AutoGen page suggests, groups the turns, andrius.user()ties them to a person. See Tracing your code for both. - Pass an opaque ID. The value is stored on every span and shown in the console, so prefer an internal ID to an email or a name. If only sensitive identifiers exist, hash them first.
Set it up
- Set up AutoGen tracing as the AutoGen integration page describes:
rius.init(), theopenaiextra and anobservewrapper on the entrypoint. Do not wrap AutoGen tool functions withobserve, because the framework’s ownexecute_toolspans already cover them. - Wrap the call to your entrypoint in
rius.user(), using the user ID from your request.requestandhandle_ticketbelow are your own code, andhandle_ticketis the entrypoint from the AutoGen page.
import rius
async def serve(request):
with rius.user(request.user_id):
return await handle_ticket(request.ticket)- Add a session scope inside it if a conversation spans several turns:
async def serve(request):
with rius.user(request.user_id):
with rius.session(request.conversation_id):
return await handle_ticket(request.ticket)- Check it in one minute. Run one request with a test ID such as
test-user-1. Open Users, typetest-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.
In TypeScript the same scope is withUser(userId, fn). The AutoGen integration covers the Python framework.
What you get
The Users page is a register of the end users your agents serve, not your Rius organization’s members. It counts only finished traces, and shows these figures for a window from 15 minutes to 30 days, 7 days by default:
- The list. Per user: 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 users, 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”. Traces with no user on any span fall into the anonymous row. If that row is large, some code paths run outside a user scope.
- A user’s page. Tiles for Traces, Failed traces, Error rate, Cost with average per trace, Sessions and TTFT p50. Below them are the agents that served the user, their 8 most recent traces and their 6 latest traces with an errored span.
- Support lookups. When a customer says it didn’t work, search their ID, open Failed traces, then open the trace from the time they wrote in.
- Cost and blast radius. Sort by Cost to find your most expensive users. The Agents column shows which customers a change to one agent affects.
- Filtering. The trace list has a User column and a
user_idfilter. - Written summaries. With root-cause analysis on for the workspace, an admin can click Summarise this user for a readable account of that user’s runs.
The AutoGen traces in a user’s page keep the structure of the run: the root span, invoke_agent, execute_tool and the generation with model, tokens and cost.
When something else fits
- If one process serves exactly one person, you still scope that person with
rius.user(), because there is no process-wide user setting. Use the scope around each request in any server. - For coding agents, you do not set a user yourself. Sessions from version 0.6.2 and later of the Rius plugin for Claude Code, Codex (beta) and Cursor (beta) carry the developer’s sign-in email as
user.id, and OpenHands takes auser_idyou pass toConversation. - To see which agents your wider traffic runs through, start from Monitor AI agents in production.