Skip to Content

How do I group the traces of one conversation into a session with Pydantic AI?

Rius groups the traces of one Pydantic AI conversation into a session when you wrap each agent run in rius.session() and pass the same conversation id every turn. Pydantic AI’s spans inherit the scope like any other span, so every span of the run carries session.id. Each turn stays its own trace, and the session ties the turns together. You add no wrapper to the framework itself.

How it works

A trace is one causal unit of work, so each call to your Pydantic AI agent produces its own trace. A session is the id your application assigns to tie several of those traces together. You choose the id, and the SDK stamps it as the session.id attribute on every span started inside the scope.

Pydantic AI emits its own spans for the agent run, each model request and each tool call, and the Pydantic AI integration sends them through the Rius tracer provider. Because the scope rides OpenTelemetry context, those framework spans pick up the session id with no changes to your agent code. Scopes behave the same way the active span does:

  • They nest, and the innermost scope wins.
  • They unwind when the block ends, even on an error.
  • They follow async work started inside the block.

The session does not depend on the process. A conversation that outlives a deploy or moves between replicas stays one session, because the grouping rides on the traces and on the id you pass. Tracing your code covers the same scope for any code you trace yourself.

Set it up

  1. Install the SDK and Pydantic AI. No extra is needed for this integration. The full setup is on the Pydantic AI integration page.
pip install glassflow-rius pydantic-ai
  1. Initialize Rius, switch on Pydantic AI instrumentation, and scope each run with the conversation id your application already has.
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.", ) def handle_turn(conversation_id, message): with rius.session(conversation_id): result = support_agent.run_sync(message) return result
  1. Call handle_turn with the same conversation_id on every turn of that conversation. Every run in the scope then carries the same session.id. A scope per turn and one scope around several runs both group the traces.

  2. 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:

with rius.session() as session_id: result = support_agent.run_sync(message)
  1. For a CLI run or batch job that handles exactly one session per process, set the id once instead, with init(session_id=...) or the RIUS_SESSION_ID environment variable. An active 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. Scope each request instead.

  2. Run two turns with the same id, then open the traces in the console and check that both carry it.

What you get

  • One session per conversation. The traces of each turn are grouped under the id you passed, so you read the conversation as a whole instead of as unrelated runs.
  • One trace per turn. Each turn keeps its own trace, with an invoke_agent root named after your agent, chat spans for model requests with tokens and cost, and execute_tool spans for tool calls. A turn stays short and easy to read in the waterfall.
  • An id you can join on. Because you choose the id, it can match a conversation id or job id you already store.
  • A clear signal when scoping is missing. 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. Check that the rius.session() block surrounds the run.
  • Room to add users. rius.user() scopes a user id the same way and combines freely with a session scope, so you can see both the conversation and the person.

For how sessions fit alongside alerts, cost and agent health, see Monitor AI agents in production.

When something else fits

  • If one process handles exactly one conversation, such as a CLI run or a batch job, set RIUS_SESSION_ID once and skip the scope.
  • If you do not need conversation grouping, leave the scope out. Each trace then reads as its own session, and per-trace cost, latency and errors still work.

Still stuck? Get help

Last updated on