Skip to Content

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

Rius groups the traces of one conversation into a session when you wrap each turn’s Vercel AI SDK calls in withSession from @glassflow-ai/rius and pass your conversation id. Every span started inside the callback carries that id as session.id, so the turns of a chat read as one session. The Vercel AI SDK integration is TypeScript only, so there is no Python equivalent for it.

How it works

Each turn of a chat is its own trace, and the session is what ties the turns together. You mint the id, usually a conversation id you already have, and withSession stamps it as session.id on every span started in its scope. The scope rides OpenTelemetry context, so it follows async work the way the active span does. Scopes nest (the innermost wins) and unwind when the block ends, even on an error.

The spans from generateText and streamText are started inside that scope, so they carry the id too. That covers the agent span, the chat span for each model call and the execute_tool span for each tool. See Vercel AI SDK for what the integration records.

Because the grouping rides on traces, a session does not depend on the process serving it. A conversation that outlives a deploy or moves between replicas stays one session.

Set it up

  1. Set up Rius for the Vercel AI SDK as described in Vercel AI SDK. On ai 7, install @ai-sdk/otel and init() registers it. On ai 5 and 6, add experimental_telemetry: { isEnabled: true } to each call.
  2. Wrap the work of one turn in withSession, passing your own conversation id:
import { init, withSession } from '@glassflow-ai/rius' import { openai } from '@ai-sdk/openai' import { generateText } from 'ai' const rius = init({ apiKey: 'ri_...', serviceName: 'my-agent' }) await rius.ready async function handleTurn(conversationId: string, prompt: string) { await withSession(conversationId, async () => { const { text } = await generateText({ model: openai('gpt-4o-mini'), prompt, }) console.log(text) // every span of the turn carries session.id }) } await handleTurn('chat-881', 'Summarize this ticket') await handleTurn('chat-881', 'Now draft a reply') await rius.shutdown() // export queued spans before the process exits
  1. Use the same id on every turn of the conversation. Both turns above land in one session, chat-881.
  2. If you have no id of your own, call withSession with only a callback. Rius mints a fresh UUID for the scope and passes it to the callback, so you can log it or hand it to the next turn:
await withSession(async (sessionId) => { await handleTurn(sessionId, 'Start a new chat') })
  1. For a CLI run or batch job that handles exactly one session per process, set the id once with init({ sessionId }) or the RIUS_SESSION_ID environment variable. An active withSession 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.

To tie the same turns to a person, nest withUser with the user’s opaque id inside or around the session scope. Tracing your code shows both.

What you get

  • One session for each conversation, holding a trace for each turn, with the id you chose. You can correlate it with a conversation id you already store.
  • A way to read your setup: 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 every turn runs inside withSession.
  • Sessions that survive deploys and replica changes, because the id travels on the spans and not with the process.
  • The usual per-call detail inside each turn: models, token usage and cost computed by Rius.

The session concept has the full definition. For the wider picture of watching agents in production, see Monitor AI agents in production.

When something else fits

  • If your agent handles one job per process, the process-wide RIUS_SESSION_ID is simpler than scoping every call.
  • If you only need to follow a single request, you do not need a session: each trace already shows one run end to end.

Still stuck? Get help

Last updated on