How do I group the traces of one conversation into a session with the Claude Agent SDK?
Rius groups the traces of one Claude Agent SDK conversation into a session when you wrap each turn in rius.session with your conversation id. Every span in that scope carries the same session.id, so each turn stays its own trace and the session ties the turns together. The Claude Agent SDK is traced from Python, and its own session_id makes a natural Rius session id.
How it works
The Claude Agent SDK drives the Claude Code runtime in a separate process, so you trace it manually. An observe wrapper makes each task the root AGENT span, and a manual generation wraps the query() loop. Both are shown on the Claude Agent SDK integration page.
A session adds one more layer around that. You mint the id and the SDK stamps it as the session.id attribute on every span started inside the scope. That includes the root span and the generation.
- One turn is one trace. A trace is one causal unit and stays short. The session is the correlation unit you assign across turns.
- The session is independent of the process. Grouping rides on the traces, so a conversation that moves between replicas or outlives a deploy stays one session.
- Scopes nest. The innermost session scope wins, and the scope unwinds with the block even on an error.
For the data model behind this, see Concepts. For the scoping calls in both SDK languages, see Tracing your code.
Set it up
-
Set up the task tracing from the integration page first:
rius.init, anobservewrapper withkind=SpanKind.AGENT, and the manual generation filled from theResultMessage. Install withpip install glassflow-rius claude-agent-sdk. TheclaudeCLI must be installed and authenticated. -
Wrap each call to your observed task in
rius.session, passing the id you already have for the conversation. Thewithblock sits outside the observed function, so the root span carries the id too.
import rius
rius.init(api_key="ri_...", service_name="my-agent")
# handle_task is the observed AGENT function from the integration page
async def chat_turn(conversation_id: str, message: str) -> str:
with rius.session(conversation_id):
return await handle_task(message)-
Call
chat_turnonce per user message, always with the sameconversation_id. If you resume conversations with the Claude Agent SDK, pass its ownsession_idas that id, so the Rius session matches the one you resume. -
If you have no id of your own, call
rius.session()with no argument. It mints a fresh UUID for the scope and yields it, so you can log it or hand it to the next turn:
async def chat_turn_new(message: str) -> str:
with rius.session() as session_id:
print(session_id) # log it, or hand it to the next turn
return await handle_task(message)- Run two turns with the same id, then open the traces in the console. Both should carry the same
session.idon the rootAGENTspan and on the generation.
For a CLI run or batch job that handles exactly one conversation per process, you can set the id once with init(session_id=...) or the RIUS_SESSION_ID environment variable. An active rius.session scope overrides that default. Do not use a process-wide default in a server that handles many users, because every user’s traces would merge into one session.
What you get
- One session per conversation. Each turn is a separate trace made of its root
AGENTspan and one generation with the model, tokens and cost. The session groups those traces under your conversation id. - An id that matches your own records. Because you pass the conversation id or the SDK’s
session_id, you can find the Rius session from an id you already log. - A check that grouping works. A trace with no session id is grouped as its own single-trace session. If an agent’s session count equals its trace count, nothing is setting
session.id, and those sessions are not meaningful groupings. - Per-turn detail, not inner steps. The generation records the turn’s totals. Tool calls and sub-turns inside the Claude Code process produce no spans, so a session shows turns, not the runtime’s internal steps.
To see how sessions fit with agents, users and alerts, read Monitor AI agents in production.
When something else fits
- If one process serves a single conversation, such as a CLI run or batch job, the process-wide
RIUS_SESSION_IDis simpler than scoping every call. - If you want to follow a person across conversations rather than one chat, add a user scope with
rius.user, described in Tracing your code. It sits alongside the session scope.