Skip to content

Query language

Several Query API fields take short expressions instead of structured messages:

Field Expression Used by
seriesSummaries series summary selector with a function ListRuns, ListRLEnvironments
filter resource filter ListRuns, ListProjects, ListExperiments, ListDirectories, ListRLEnvironments, ListSpans, AggregateRLEnvironmentSeries, RunsFilter
orderBy ordering the same listings
seriesFilters series filter ListSeriesDefinitions
summaryQueries series summary query ListSeriesSummaries
seriesQueries series query ListFloatSeries, ListFloatSeriesBuckets, ListFloatSeriesTimestampBuckets, ListHistogramSeries, ListScatterSeries, ListRLEnvironmentFloatSeriesBuckets
seriesExpression, groupings selector and conditions AggregateRLEnvironmentSeries — see RL environments
  • Names are bare. Metric names, attribute paths and label keys are written as-is, never quoted — which is why ingestion rejects names containing whitespace or ( ) [ ] , = < > ! ~ " |.
  • Values are bare (gpu0, 0.5) or double-quoted ("adam w", with \ escapes). Quote any value containing whitespace or one of those characters.
  • Numbers are written plainly: 107, 56.2, 1e5, -3.
  • Booleans are True and False.
  • Timestamps are unquoted: 2026-09-30T09:00:00 or 2026-09-30T09:00:00.250.
  • Keywords (AND, OR, IN, NOT, EXISTS, CONTAINS, ANY, ALL, ASC, DESC, and option names such as scale and filter) may be written in upper case, lower case or capitalized: AND, and, And. Function names (last, avg, …) are lower case.
  • Whitespace between tokens is ignored.

A selector picks series of a run (or of an environment) and, usually, a statistic of them:

METRIC(OPTIONS):FUNCTIONS
train/loss():last
train/loss(scale=ML_STEP, filter=(gpu="0")):min
eval/accuracy(filter=(split IN ["val", "test"], seed != 0)):max
weights/layer4(value_kind=histogram):last_avg
train/loss(aggregate=(keys=[host])):last_avg

The parentheses are required, even when empty. Options, in any order:

Option Meaning
scale=S The series’ scale. Defaults to ML_STEP when omitted.
filter=(CONDITIONS) Label conditions the series must match.
aggregate=(keys=[K, …]) Address a pre-aggregated series produced by an aggregation rule; keys lists the labels it kept.
value_kind=K The series’ value kind: float (default), histogram, density_histogram, scatter or scatter_3d.
value_reducer=R In series queries on the float endpoints, read a histogram series as floats by reducing each histogram: min, max, avg, std, quantile(Q) (Q from 0 to 1) or fraction_above(X). Requires value_kind=histogram or density_histogram.
Series Functions
Float (regular) first, last, min, max, sum, avg, count, std — over all of the series’ points. last_min, last_max, last_avg, last_std, last_sum reduce across several matched series at each one’s last point.
Aggregated (aggregate=) min, max, sum, avg, count, std, plus the value across sub-series at the first or last step: first_min, first_max, first_avg, first_std, last_min, last_max, last_avg, last_std.
Counting histogram The last point only: last_count, last_min, last_max, last_sum, last_avg, last_std.
Density histogram last_min, last_max, last_avg, last_std.
Scatter None. Scatter selectors are only valid where no function is needed: EXISTS conditions and series summary queries.

Where a single value is needed — filters and orderings — give exactly one function. std is the population standard deviation.

filter=(…) takes comma-separated conditions, all of which must hold:

Condition Matches series whose label…
key=value or key==value equals the value
key!=value differs from the value
key IN [v1, v2] is one of the values
key NOT IN [v1, v2] is none of the values
key~="regex" matches the regular expression
key CONTAINS value contains the value as a substring

Conditions need not mention every label a series has; how many series a selector ends up matching decides how its function is computed, as described next.

Label resolution and across-series aggregation

Section titled “Label resolution and across-series aggregation”

A selector’s conditions are resolved against the series each run (or environment) actually logged:

  • Exactly one series matches — the function is computed on it. This is the case when equality conditions pin every label the series was logged with.
  • Several series match — because a label was left unconstrained or a non-equality operator was used. The function is then computed across them, giving one value per run (per environment for the RL RPCs). The combination is exact: min, max, sum and count combine directly, and avg and std are recomputed from the combined raw moments rather than averaged.
  • No series matches — the selector contributes nothing: a filter excludes the resource, an ordering leaves it in natural order, and a summary returns no value.

Two consequences:

  • first and last have no meaning across series and are rejected when a selector matches several. Pin every label, or use the last_avg / last_min / … family, which reduces across series at each one’s last point.
  • If a metric was logged both at exactly the requested label shape and at wider shapes (say, loss{host} and loss{host, worker}), a run holding the exact shape uses it alone — it already summarizes the wider ones — while runs with only wider shapes aggregate across them.

ListSeriesSummaries is the exception: it returns one row per matching series instead of combining them. aggregate=(…) selectors are unaffected too: their labels must be equalities naming the full kept label set.

A filter is a condition, or conditions combined with AND, OR and parentheses. AND binds tighter than OR:

attr(config/optimizer) == "adamw" AND (attr(config/lr) < 0.001 OR attr(sys/tags) CONTAINS "promising")
attr(PATH) OPERATOR VALUE
attr(PATH) EXISTS
Operator Meaning
= == equal
!= not equal
< <= > >= ordering comparisons, for numbers, strings and timestamps
~= matches a regular expression (strings)
IN [v, …] / NOT IN [v, …] is / is not one of the values
CONTAINS v a string set contains the value
CONTAINS ANY [v, …] / CONTAINS ALL [v, …] a string set contains any / all of the values
EXISTS the resource has the attribute

Numbers are compared according to the attribute’s stored type. Integer attributes compare exactly: a range comparison with a non-integer literal is rounded to the equivalent integer bound, while ==, !=, IN and NOT IN reject non-integer literals.

attr(sys/creation_time) >= 2026-09-01T00:00:00
attr(config/batch_size) IN [128, 256]
attr(sys/tags) CONTAINS ALL ["baseline", "v2"]
attr(notes) ~= "^ablation-"

ListRuns, ListRLEnvironments and AggregateRLEnvironmentSeries also filter on series, using selectors:

SELECTOR EXISTS
SELECTOR:FUNCTION OPERATOR NUMBER
train/loss():last < 0.5
eval/accuracy(filter=(split="val")):max >= 0.9
weights/layer4(value_kind=histogram) EXISTS

The operators are =, ==, !=, <, <=, >, >=; the value is a number. A selector matching several series of a run compares their combined value.

RPC Attribute conditions Series conditions Special fields
ListRuns, RunsFilter yes yes —
ListProjects, ListExperiments, ListDirectories yes — —
ListRLEnvironments environment attributes yes attr(environment_id), attr(latest_episode), attr(latest_rl_step) — see RL environments
ListSpans span attributes — attr(op_name), attr(step), attr(status) and more — see Agentic traces

An orderBy holds one ordering:

attr(PATH) [ASC|DESC]
SELECTOR:FUNCTION [ASC|DESC]
attr(sys/creation_time) DESC
attr(config/lr, float) ASC
train/loss():min

The direction defaults to ASC. An attribute ordering may name the type to sort as — float, int, bool, timestamp or string — after the path. Selector orderings are accepted where series conditions are (ListRuns, ListRLEnvironments); resources without a value keep their natural order.

ListSeriesDefinitions narrows the definitions it returns with selectors that have no function:

train/loss(filter=(gpu IN ["0", "1"]))
train/loss(aggregate=(keys=[host]))

Label conditions may use any operator and need not be complete. Include aggregate=(keys=[…]) to return only aggregated series with that label set.

ListSeriesSummaries takes selectors with one or more functions and returns one row per matching series:

train/loss(filter=(gpu~="^[0-3]$")):last,min,max
train/loss(aggregate=(keys=[host])):last_avg,last_std
eval/embedding(value_kind=scatter)

Conditions may be partial and use any operator. Aggregated series are only returned when aggregate=(…) is given. Scatter selectors take no functions and return the skeleton summary: the last step and its timestamp.

The series point RPCs take selectors with no function, optionally followed by transformations:

train/loss()
train/loss(filter=(gpu="0"))
train/loss(filter=(host="a")) | group_by(keys=[gpu], reducer=avg)
train/loss(aggregate=(keys=[host], reducer=avg))
weights/layer4(value_kind=histogram, value_reducer=quantile(0.9))
  • Every series matching the conditions is returned, each with its full definition.
  • aggregate=(keys=[…], reducer=R) reads a pre-aggregated series; reducer (min, max, avg or std) picks which of its aggregates to return as points.
  • | group_by(keys=[…], reducer=R) merges the matching series at query time, grouping by the listed labels and combining points at each step with min, max, avg or std. It is flexible but costs more than a pre-aggregated series, especially across many series. Only ListFloatSeries accepts transformations.
  • value_reducer=… turns histogram points into floats, so histogram series can be read on the float endpoints.
  • ListHistogramSeries and ListScatterSeries accept no transformations; queries there match only series of their own kind.

Smoothing (moving averages, exponential smoothing) is not offered server-side; apply it to the fetched points.