Skip to Content

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

Rius groups the traces of one CrewAI conversation into a session when you wrap each crew.kickoff() call in rius.session() and pass your own conversation id. Every span started inside the block carries that id as session.id, so each turn stays its own trace and the session ties the turns together. CrewAI is a Python framework, so you use the Python SDK.

How it works

A trace is one run of your crew, and a session is the id you assign to tie several runs together. You mint the id, or reuse one you already have such as a conversation id or a job id, and rius.session() stamps it as session.id on every span in its scope. See Concepts for the data model.

  • Each call to your entrypoint around crew.kickoff() produces one trace, with your observe wrapper as the root AGENT span and one LLM generation for each model call.
  • Scope the session around the call to that entrypoint, not inside it. Then the root span carries session.id as well as its children.
  • The scope rides OpenTelemetry context, not a global. Scopes nest, with the innermost winning, and they unwind when the block ends, even on an error.
  • Because the grouping rides on traces, a conversation that outlives a deploy or moves between replicas stays one session.

Crews and tasks produce no spans of their own, so the session groups whole kickoffs. It does not split a kickoff by task. For per-task detail, wrap per-task entrypoints with observe, as the CrewAI integration page describes.

Set it up

  1. Install the SDK with the OpenAI extra, which captures the model calls CrewAI makes. The CrewAI integration page covers the full setup.
pip install "glassflow-rius[openai]" crewai
  1. Wrap your entrypoint with observe, then open a session scope around each call to it. Pass the same conversation id on every turn of that conversation.
import rius from crewai import Agent, Crew, Task from rius import SpanKind rius.init(api_key="ri_...", service_name="my-agent") support_agent = Agent( role="Support specialist", goal="Answer support tickets accurately and concisely.", backstory="You resolve product and account questions.", llm="gpt-5-mini", ) @rius.observe(name="handle-ticket", kind=SpanKind.AGENT) def handle_ticket(ticket: str) -> str: task = Task( description=f"Resolve this support ticket: {ticket}", expected_output="A short, direct answer.", agent=support_agent, ) return str(Crew(agents=[support_agent], tasks=[task]).kickoff()) def handle_turn(conversation_id: str, ticket: str) -> str: with rius.session(conversation_id): return handle_ticket(ticket) # every span carries session.id
  1. Call handle_turn once per user message, always with that conversation’s id. Here conversation_id and handle_turn are your own names.

  2. If you have no id yet, 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: handle_ticket(ticket)
  1. For a CLI run or batch job that handles exactly one session per process, set the id once with init(session_id=...) or the RIUS_SESSION_ID environment variable instead. An active session scope overrides that default. Do not use it in a server that handles many users, because every user’s traces would merge into one session. Tracing your code shows both forms.

What you get

  • The traces of one conversation share one session, so you read a whole conversation as one unit instead of as separate runs.
  • The session id is your own conversation or job id, so you can match a session in Rius to a record you already have.
  • Each turn remains its own trace, with the root AGENT span, the LLM generations with model, tokens and cost, and any TOOL spans you wrapped.
  • A check that the grouping works: when an agent’s session count equals its trace count, nothing is setting session.id, because a trace without an id counts as its own single-trace session. A lower session count than trace count means your turns are being grouped.

For how sessions fit alongside users and instances across your agents, see Monitor AI agents in production.

When something else fits

  • If nothing in your app spans several turns, leave the session id unset. Each trace then appears as its own session, which is the right result.
  • If one process serves exactly one conversation or job, the process-wide default with init(session_id=...) or RIUS_SESSION_ID saves you scoping every call.
  • If you also want to follow the person across conversations, add a user scope with rius.user(), which Tracing your code covers.

Still stuck? Get help

Last updated on