Skip to main content
Four primitives carry the whole product, and the SDK emits them as you run your app. Traces, signals, evaluations, the trace timeline, cost and user breakdowns: every view is built from these four. Once you know what each one represents, you can predict how any chart is computed. A trajectory is one run of your agent, the root span of a trace. You open one with bento.begin(...) or the @bento.interaction decorator. Inside it sit spans. A span is one step within a trajectory: an LLM call, a tool call, or anything you choose to time. LLM calls come from bento.track_ai(...); tool calls come from bento.tool_span(...) or the @bento.tool decorator. An attribute is a typed key/value pair on a span, and it becomes a column or filter in the dashboard. You set attributes through the kwargs on the calls above, plus properties={...} for anything custom. A session is every trajectory that shares a convo_id. It’s a server-side grouping, not an SDK object: pass the same convo_id on every call and Bento ties the runs together for you. Under the hood these are OpenTelemetry spans, carrying OpenInference and GenAI semantic conventions. You can ignore that unless you’re wiring Bento into an existing OTel pipeline.

How they nest

A session holds the trajectories from one conversation, and each trajectory holds its spans:
A bare track_ai call with no surrounding begin is its own one-span trajectory. You don’t have to open a trajectory to record an LLM call.

What gets emitted

Every manual call produces one OTel span. Spans captured by the Google ADK integration (bento.instrument()) land in the same columns through OpenInference semantic conventions, and you write none of the per-call code in that case. The attributes below land in dashboard columns. Everything else lives in the per-span attributes JSON. See Attributes for the per-kwarg breakdown.

Span kinds

The openinference.span.kind attribute drives icons, colors, and filters in the dashboard. OpenInference defines other kinds (retriever, embedding, agent, chain). Set them by passing openinference.span.kind in properties.

Sessions are server-side

There is no Session object in the SDK. A session is whatever trajectories share the same convo_id string.
Both turns land under one session in the dashboard. user_id works the same way: a free-form string passed on every call. We don’t store profile data. See Sessions and users for ID-choosing rules.

Already on OpenTelemetry?

Drop in BentoLabsSpanProcessor and skip the analytics layer entirely. Any span you emit with OpenInference or GenAI semantic conventions lands in the right dashboard column. See OTel transport.