How do I group the traces of one conversation into a session with the Anthropic API?
Rius groups the traces of one Anthropic conversation into a session when you wrap each turn in rius.session in Python, or withSession in TypeScript, with your conversation id. The SDK stamps that id as session.id on every span in scope, including the span for each Anthropic call. Each turn stays its own trace, and the session ties the turns together. Your Anthropic calls need no changes.
How it works
A trace is one turn of your chat, from request to answer. A session is the id your application assigns to tie those turns together. You choose the id, so it can be the conversation id you already store.
- Open the session scope around the turn, not inside it, so the root span of the trace carries
session.idtoo. - Anthropic calls are traced by the Anthropic integration once
init()has run. Eachmessages.createcall becomes a model-call span, and it picks up the session id from the surrounding scope. - Scopes follow OpenTelemetry context. They nest (the innermost wins), unwind when the block ends even on an error, and follow async work.
- A session does not depend on the process. A conversation that outlives a deploy or moves between replicas is still one session, because the grouping rides on the traces.
- Wrapping the turn in
observegives you one trace per turn with the Anthropic call nested inside it. See Tracing your code for the details.
Set it up
- Install the SDK and the Anthropic instrumentation, then call
init()once at startup. The Anthropic integration page covers this in full, including ordering rules for CommonJS apps.
pip install "glassflow-rius[anthropic]" anthropic
npm install @glassflow-ai/rius @opentelemetry/api
npm install @arizeai/openinference-instrumentation-anthropic- In Python, wrap each turn in
rius.sessionwith your conversation id:
import anthropic
import rius
rius.init(api_key="ri_...", service_name="my-agent")
claude = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
@rius.observe
def handle_turn(message: str) -> str:
response = claude.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=200,
messages=[{"role": "user", "content": message}],
)
return response.content[0].text
def on_message(conversation_id: str, message: str) -> str:
with rius.session(conversation_id):
return handle_turn(message) # every span of the turn carries session.id- In TypeScript, use
withSessionthe same way. Name the observed function, or its spans trace as anonymous:
import Anthropic from '@anthropic-ai/sdk'
import { init, observe, withSession } from '@glassflow-ai/rius'
const rius = init({ apiKey: 'ri_...', serviceName: 'my-agent' })
await rius.ready // auto-instrumentation is attached before the first call
const claude = new Anthropic() // reads ANTHROPIC_API_KEY
const handleTurn = observe(async function handleTurn(message: string) {
const reply = await claude.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 200,
messages: [{ role: 'user', content: message }],
})
const [block] = reply.content
return block.type === 'text' ? block.text : ''
})
async function onMessage(conversationId: string, message: string) {
return await withSession(conversationId, async () => {
return await handleTurn(message) // every span of the turn carries session.id
})
}-
Pass the same conversation id on every turn. If you have no id yet, call
rius.session()with no argument (orwithSessionwith only a callback) to mint a UUID. The scope hands you that id, so you can store it and reuse it on the next turn. -
In a short script, await
rius.shutdown()before the process exits so queued spans are exported.
What you get
- One session per conversation. Each turn appears as its own trace, and all of them share the session id you chose.
- Model, token counts and a cost on each Anthropic call. Cost is a list-price estimate in US dollars, and Rius computes it from the model and tokens.
- A quick check that grouping works. If an agent’s session count equals its trace count, nothing is setting
session.id, and each trace is counted as its own single-trace session. - Sessions that survive deploys and replica changes, because they depend on the traces and not on the process.
- A path to per-person views. Add
rius.userin Python orwithUserin TypeScript around the same turn to tie several conversations to one person. For the wider picture, see Monitor AI agents in production.
When something else fits
- If your agent handles exactly one session per process, such as a CLI run or a batch job, set the id once with
init(session_id=...)in Python,init({ sessionId })in TypeScript, or theRIUS_SESSION_IDenvironment variable. An active scope overrides it. - Do not use a process-wide id in a server that handles many users, because every user’s traces would merge into one session. Scope each request instead.
- For one-shot calls with no conversation, skip the scope. Each trace is then its own session.