GuidesAgent versions

Agent versions

When more than one build of the same agent sends traces to one Neens agent — a canary next to the current release, a rollout that is still draining, a staging build pointed at production data — you want to see each build separately. Neens reads an agent version off every trace and lets you group the Traces and Sessions charts, dashboard widgets and failure modes by it, so you can answer “is this failure only in v1.3?” or “how far has v2.4 rolled out?” without guessing.

At a glance

What sets itThe neens.version_label attribute on a span, or on the OTLP resource; service.version on the resource as a fallback
Where you see itTraces / Sessions chart → Group by: Agent version · the Agent Version filter and column · dashboard widgets (Agent version dimension) · each failure mode’s Agent versions card · Cost & Quality
API?group_by=version on GET /sessions/trend and GET /conversations/trend · ?version= on the list and trend endpoints · version dimension in dashboard widgets · version_distribution on GET /clusters/{id}
Traces without a versionKept as their own Unversioned group, never merged into a real version. Select them with version=__unversioned__

Tag your traces with a version

Use any string that names a build: a semantic version (v2.4.0), a release name, or a short commit SHA. Neens takes the version for a trace from the first of these that is present:

  1. a span attribute neens.version_label (the first span in the trace that carries one),
  2. the OTLP resource attribute neens.version_label,
  3. the OTLP resource attribute service.version (the standard OpenTelemetry one, so an agent that already sets it is versioned with no change).

An empty value counts as no version. The resource fallbacks apply to OTLP (POST /v1/traces); on POST /ingest/openinference and POST /ingest/raw, put neens.version_label on a span.

Set the version once on the tracer provider’s resource. Every trace the process exports then carries it:

import os
 
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
 
resource = Resource.create({
    "service.name": "support-agent",
    # The build that is running — e.g. injected by your deploy pipeline.
    "neens.version_label": os.environ.get("AGENT_VERSION", "v2.4.0"),
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(
    endpoint="https://<your-neens-host>/v1/traces",
    headers={"Authorization": "Bearer nk_live_your_key_here"},
)))
trace.set_tracer_provider(provider)

To set it per request instead (for example when one process serves two prompt variants), put it on the request’s top-level span:

with tracer.start_as_current_span("agent.handle_request") as span:
    span.set_attribute("neens.version_label", variant.version)
    ...

The version is read when a trace arrives. Traces that were sent before you added the attribute stay Unversioned.

Break the Traces chart down by version

Open Traces (or Sessions)

The chart above the list shows the volume for the selected time range.

Choose Group by → Agent version

The chart switches to stacked bands, one per version. Each band is a count of traces (or sessions) over time, and the bands add up to the same total as the ungrouped chart. Traces with no version form an Unversioned band. When there are more than seven versions, the busiest seven are shown and the rest are combined into Other.

Narrow the list to one version

Use Filters → Agent Version to show one version’s traces in the table, or Unversioned to find traces that are missing the attribute.

A rollout reads directly off the chart: the new version’s band grows while the old one shrinks, and anything still on the old build after the cutover is easy to spot.

On Sessions, a session made of several traces is counted once. If its traces carry different versions, it is counted under the lowest version label; it is Unversioned only when none of its traces carries a version.

Through the API:

curl -H "Authorization: Bearer nk_live_your_key_here" \
  "https://<your-neens-host>/api/sessions/trend?range=30d&group_by=version"
{
  "group_by": "version",
  "series": [
    { "key": "v2.4.0", "other": false, "total": 752, "counts": { "2026-09-28": 33, "...": 0 } },
    { "key": "v2.3.0", "other": false, "total": 62,  "counts": { "2026-09-28": 3 } },
    { "key": null,     "other": false, "total": 47,  "counts": { "2026-09-28": 2 } }
  ]
}

A null key is the unversioned group. Add version=v2.3.0 to scope the chart and its tiles to one version, or version=__unversioned__ for the traces without one.

The chart counts every trace in the list, including the traces a pre-prod evaluation replays, which carry the candidate’s version. Cost & Quality and the failure-mode breakdown below leave those replays out because they only look at production traffic.

Add a version breakdown to a dashboard

In a dashboard, click Add widget, open the Custom tab, pick a trace-grain measure such as Traces, Error rate, or p50 latency, and set Dimension to Agent version. A bar, donut, or table works best. Traces without a version show as Unversioned.

curl -X POST -H "Authorization: Bearer nk_live_your_key_here" \
  -H "Content-Type: application/json" \
  "https://<your-neens-host>/api/dashboards/<dashboard-id>/widgets" \
  -d '{"type": "bar", "measure": "error_rate", "dimensions": ["version"], "range": "7d"}'

version also works as a widget filter (for example "filters": {"version": ["v2.4.0"]}) and on the per-case business measures, where a case takes the version of its first trace. It does not apply to the cost, token, span, or score measures. The builder doesn’t offer those combinations, and the API rejects them with 422. See the full matrix in the metrics catalogue.

See which versions a failure mode hits

Open a failure mode from Failure Modes. The Agent versions card on its Overview tab lists, for each version:

ColumnMeaning
Traces in modeHow many of the failure mode’s traces ran on this version.
Share of modeThat count as a share of the failure mode.
Version tracesAll of the agent’s traces on this version between the failure mode’s first and last trace.
Hit rateTraces in mode ÷ version traces — how often this version’s traffic ends up in this failure mode.

Compare hit rates, not shares. A version that serves most of the traffic also owns most of every failure. For example, if v2.3.0 hits a failure mode at 26% and v2.4.0, serving four times the traffic over the same weeks, hits it at 0%, the failure is specific to v2.3.0 (here, v2.4.0 fixed it). A version with traffic in that period but no traces in the failure mode is listed with a 0% hit rate, not left out. Pre-prod replays are not counted.

The same breakdown is version_distribution on GET /clusters/{id}, and the get_failure_mode MCP tool returns it as versionDistribution. list_traces accepts a version filter.

Troubleshooting

SymptomLikely causeFix
Everything is UnversionedThe agent doesn’t set a version, or sets it under a different nameSet neens.version_label (or service.version on the OTLP resource), spelled exactly as shown
A version set with OTEL_RESOURCE_ATTRIBUTES doesn’t show up on OpenInference or raw tracesThe resource fallback only applies to OTLPSet neens.version_label on a span, or send over POST /v1/traces
Old traces have no versionThe version is read when a trace arrivesOnly traces sent after the change are versioned
Very many bands, mostly in OtherThe label changes per commitUse a release-level label (v2.4.0) instead of a per-commit SHA