> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bentolabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Data model

> Trajectories, spans, attributes, sessions. The four primitives that drive every chart in the dashboard.

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](/concepts/trajectories) 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](/concepts/attributes) 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](/concepts/sessions) 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](https://github.com/Arize-ai/openinference) and [GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/) 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:

```text theme={null}
Session                  ← grouped server-side by convo_id
├── Trajectory A         ← one agent run, opened by bento.begin
│   ├── LLM span         ← bento.track_ai
│   ├── Tool span        ← bento.tool_span or @bento.tool
│   └── LLM span         ← bento.track_ai
└── Trajectory B         ← next turn, same convo_id
    └── ...
```

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](/python/integrations) (`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.

| Concept      | OTel attribute            | Dashboard column |
| ------------ | ------------------------- | ---------------- |
| Span name    | `span.name`               | Span name        |
| User         | `gen_ai.user.id`          | User             |
| Conversation | `gen_ai.conversation.id`  | Session          |
| Model        | `gen_ai.request.model`    | Model            |
| Provider     | `gen_ai.system`           | Provider         |
| Input        | `input.value`             | Input            |
| Output       | `output.value`            | Output           |
| Span kind    | `openinference.span.kind` | Kind             |
| Custom       | (your `properties` keys)  | Attributes JSON  |

See [Attributes](/concepts/attributes) for the per-kwarg breakdown.

## Span kinds

The `openinference.span.kind` attribute drives icons, colors, and filters in the dashboard.

| Kind        | Emitted by                                                | Meaning                                      |
| ----------- | --------------------------------------------------------- | -------------------------------------------- |
| *(default)* | `bento.track_ai`                                          | A model call. Carries `gen_ai.*` attributes. |
| `tool`      | `bento.tool_span`, `@bento.tool`, `interaction.tool_span` | A tool or function call.                     |

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.

```python theme={null}
bento.track_ai(event="turn_1", convo_id="chat_99", user_id="user_42", ...)
bento.track_ai(event="turn_2", convo_id="chat_99", user_id="user_42", ...)
```

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](/concepts/sessions) for ID-choosing rules.

## Already on OpenTelemetry?

<Tip>
  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](/python/otel-transport).
</Tip>
