Skip to main content
A session is one conversation between your app and a user. A user is one person across all their conversations. The dashboard builds both by grouping trajectories that share a convo_id or a user_id. There’s no separate Session or User object: you pass strings, and the grouping happens server-side.

Conversations

Pass the same convo_id to every bento.begin or bento.track_ai call that belongs in one conversation.
Both turns show up under the same session row, with the timeline reconstructed from span timestamps.
convo_id becomes the OTel attribute gen_ai.conversation.id and lands in the traces.session_id column on our side. The attribute name and the column name don’t match for historical reasons. Use the kwarg.
A good convo_id is stable enough to survive between requests in the same conversation, unique enough that two conversations never share one, and opaque: a UUID, slug, or hash, never PII. Most apps already have a source for it. A chat thread row gives you its primary key, stringified. A web session gives you its UUID. A Slack or Discord channel gives you channel_id:thread_ts. If you have nothing yet, generate one with uuid4() at the start of the conversation and store it client-side.

Users

user_id is a pass-through string. We don’t store profile data: no email, no name, no traits.
It maps to gen_ai.user.id (OTel) and the traces.user_id column. You can filter the trace list by user_id, count distinct users, and pivot any metric by user. Use your internal stable user ID, like the users-table primary key or the auth provider sub claim. Pick one ID per real human, so the same person reads as one identity across devices, and treat it like a foreign key with no PII in it. For anonymous users, generate an ID and store it in a cookie or local storage. That gives you a stable identity without leaking PII.

How the dashboard groups

Grouping happens at query time, not ingest time, so the IDs you pass decide what the dashboard shows. There’s no migration to “create a session”. Existing trajectories start grouping the moment you start passing the right ID.

Setting IDs on a trajectory

A trajectory carries convo_id and user_id on its own span. Child spans don’t inherit them, so pass the IDs to each track_ai call too so per-span filters work.

What sessions are not

A session is a dashboard grouping, not a billing or quota concept: you’re billed on span volume. It has no TTL either. Keep passing the same convo_id for a year and the dashboard shows one very long session, so rotate the ID when a conversation logically ends. And a session isn’t user-scoped on its own. Two different user_ids sharing a convo_id look like one session with mixed users, so rotate convo_id whenever the user changes.