> For the complete documentation index, see [llms.txt](https://docs.dbnl.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dbnl.com/configuration/app-versioning-and-experiments.md).

# App Versioning & Experiments

{% hint style="info" %}
**Distributional is now** [**Talaria Scientific**](https://talariasci.com)**.** The DBNL product described in these docs has been sunset; this documentation is preserved for reference. Read [the announcement](https://distributional.com/blog/distributional-is-now-talaria).
{% endhint %}

DBNL recognizes span attributes that tag every span with an application **`version`** and/or **`experiment_variants`**, and rolls those up onto the `spans`, `traces`, and `sessions` rows, populating the `version` and `experiment_variants` columns of the [DBNL Semantic Convention](/configuration/dbnl-semantic-convention.md). `experiment_variants` is filterable via the [Experiment Filters](/workflow/logs.md#experiment-filters) in the Filter Builder wherever it appears ([Logs](/workflow/logs.md), [Explorer](/workflow/explorer.md), and [Segment](/workflow/segments.md) creation), so you can compare model / prompt / retriever variants across those views. The `version` column is ingested for every trace but is not yet a first-class filter target in the UI.

The Python SDK exposes first-class context managers (`dbnl.using_experiments`, `dbnl.using_version`) that propagate these attributes onto every span in a block. Node.js applications set the same attributes directly via the OpenTelemetry API.

## Application Version

{% tabs %}
{% tab title="Python" %}
The preferred way to tag traces with an application version is to pass `version=` to [`dbnl.init_tracing()`](/reference/python-sdk/sdk-functions.md#init_tracing) once at startup. It sets `service.version` on the provider's OTel `Resource`, which DBNL picks up as the trace / session `version` column.

```python
import dbnl

dbnl.login()
dbnl.init_tracing(
    project_id="{PROJECT_ID}",
    service_name="my-agent",
    version="2026.04.21",
)
```

For the rare case where a single process emits traces under multiple versions (blue/green, canary, replay), override per-trace with `dbnl.using_version(...)`:

```python
with dbnl.using_version("2026.04.21-canary"):
    agent.invoke({"input": user_query})
```

{% endtab %}

{% tab title="Node.js" %}
Set `service.version` on the `Resource` attached to your `NodeSDK` (or `TracerProvider`). DBNL picks it up as the trace / session `version` column.

```typescript
import { NodeSDK } from "@opentelemetry/sdk-node";
import { Resource } from "@opentelemetry/resources";

const sdk = new NodeSDK({
  resource: new Resource({
    "service.name": "my-agent",
    "service.version": "2026.04.21",
  }),
  // ...spanProcessors as in the OTEL Trace Ingestion guide
});
```

To override the version for a specific trace (blue/green, canary, replay), set the `dbnl.version` attribute on the **root span** of that trace. DBNL prefers `dbnl.version` over `service.version` when populating the trace / session `version` column.

```typescript
rootSpan.setAttribute("dbnl.version", "2026.04.21-canary");
```

{% endtab %}
{% endtabs %}

## Experiment Variants

Each entry in an experiment mapping is an independent experimental dimension you are A/B-ing (model, prompt, retriever, planner, etc.) mapped to the specific variant this invocation used.

{% tabs %}
{% tab title="Python" %}
Wrap an agent / chain invocation in `dbnl.using_experiments({name: variant, ...})` to stamp experiment variants onto every span emitted inside the block, including every auto-instrumented LLM, tool, retriever, and agent-step span.

```python
import dbnl

with dbnl.using_experiments({
    "llm_model": "gpt-5.4-nano",
    "system_prompt": "v3-concise",
    "retriever": "hybrid-bm25+dense",
}):
    agent.invoke({"input": user_query})
```

For a single experimental dimension, `dbnl.using_experiment("llm_model", "gpt-5.4-nano")` is a shorthand for the one-key case. Both are also usable as decorators and as `async with` context managers.
{% endtab %}

{% tab title="Node.js" %}
There are no first-class helpers in JS yet. Set the variants directly as span attributes on the **root span** of the trace. DBNL rolls trace-level attributes up from the root span, so this is enough for trace- and session-level filtering.

Each variant is encoded as a pair of indexed attributes:

```typescript
rootSpan.setAttributes({
  "dbnl.experiments.0.experiment.name": "llm_model",
  "dbnl.experiments.0.experiment.variant": "gpt-5.4-nano",
  "dbnl.experiments.1.experiment.name": "system_prompt",
  "dbnl.experiments.1.experiment.variant": "v3-concise",
  "dbnl.experiments.2.experiment.name": "retriever",
  "dbnl.experiments.2.experiment.variant": "hybrid-bm25+dense",
});
```

To also stamp child spans (so `spans.experiment_variants` reflects them), either set the same attributes on each span you care about or install a custom `SpanProcessor` that reads the variants off the OTel `Context` at span start.
{% endtab %}
{% endtabs %}

## Rollup Semantics

DBNL reassembles stamped span attributes into the `experiment_variants` and `version` columns. The rollup differs by table:

* **`spans`**: from each span's own attributes.
* **`traces`**: from the trace's **root span**. In Python, the root span is emitted inside the `with` block, so variants propagate automatically; in Node.js, set the attributes on the root span explicitly.
* **`sessions`**: from the session's **first trace** only. If you vary experiments across turns of a multi-turn session, only the first turn's variants land on the session row; to analyze the remaining turns, filter `traces` (or `spans`) by `session_id`.

## Bring-Your-Own TracerProvider (Python)

`dbnl.init_tracing()` installs the dbnl context-stamp span processor automatically. If you manage your own `TracerProvider` without calling `init_tracing()`, add [`dbnl.get_dbnl_context_stamp_processor()`](/reference/python-sdk/sdk-functions.md) to it yourself so that `using_experiments` / `using_version` have any effect:

```python
import dbnl
from opentelemetry.sdk.trace import TracerProvider

provider = TracerProvider()
provider.add_span_processor(dbnl.get_dbnl_context_stamp_processor())
```
