Skip to content

Discovering what exists

Before you can filter on an attribute or fetch a series, you have to know it exists. The list_* family answers that — cheap metadata queries, no point data — and it is usually where an exploratory session starts.

What attributes runs carry, and of what kind:

client.list_run_attribute_definitions()
# [AttributeDefinition(path='config/lr', kind=AttributeKind.FLOAT),
# AttributeDefinition(path='sys/tags', kind=AttributeKind.STRING_SET), ...]

Narrow by scope or by pattern:

from metrana.query import AttributeFilter
client.list_run_attribute_definitions(experiment="lr-sweep")
client.list_run_attribute_definitions(run="run-001")
client.list_run_attribute_definitions(filter=AttributeFilter(prefix="config/"))
client.list_run_attribute_definitions(filter=AttributeFilter(regex=r"^config/.*_rate$"))
# Run-level attributes across the whole project, of these two kinds only:
client.list_run_attribute_definitions(filter=AttributeFilter(kinds=["float", "int"]))

The same shape covers the other two resources that carry attributes:

client.list_project_attribute_definitions() # paths valid in a PROJECT filter
client.list_rl_env_attribute_definitions("rl-001") # paths valid in an ENVIRONMENT filter
client.list_rl_env_attribute_definitions("rl-001", environment="envA")

Comparing several RL runs — say, one aggregation chart per run over a shared set of filters — needs the paths valid in any of them. The across-runs variant takes a list of runs (up to 100) and returns the deduplicated union over all their environments:

client.list_rl_env_attribute_definitions_across_runs(["rl-001", "rl-002", "rl-003"])

There is no environment parameter here on purpose: environment ids are not comparable across runs. Scope to environments with the single-run method instead.

kind tells you which operators are meaningful — CONTAINS ALL is a string-set thing, and < on a string orders lexicographically rather than numerically — so listing definitions is how you build a valid filter rather than guessing one.

Getting attribute values (not definitions)

Section titled “Getting attribute values (not definitions)”

The definition listings tell you what can exist. To read one resource’s actual values:

client.list_run_attributes("run-001")
client.list_rl_env_attributes("rl-001", "envA")

Both return a plain dict[str, object] with Python-typed values — str, int, float, bool, list[str] for string sets, datetime for timestamps.

A metric is a name plus a scale, and the label keys its series are split by:

client.list_metric_definitions()
# [MetricDefinition(metric_name='loss', scale='ML_STEP', label_keys=('worker',),
# value_kind=SeriesValueKind.FLOAT), ...]
client.list_metric_definitions(prefixes=["train/"], scales=["ML_STEP"])
client.list_metric_definitions(regex=r"^grad_", value_kinds=["histogram"])
client.list_metric_definitions(runs=["run-001", "run-002"])

value_kind is what tells you whether to reach for fetch_float_series, fetch_histogram_series, or fetch_scatter_series.

Inside an RL run, the environments have their own metrics:

client.list_rl_env_metric_definitions("rl-001")
client.list_rl_env_metric_definitions("rl-001", environments=["envA", "envB"])

As with attributes, the across-runs variant unions the environment metrics of several runs (up to 100, no environment scoping), and takes the same prefixes / regex / scales narrowing:

client.list_rl_env_metric_definitions_across_runs(["rl-001", "rl-002"])
client.list_rl_env_metric_definitions_across_runs(["rl-001", "rl-002"], prefixes=["train/"])

A series is a metric with its label values pinned — the actual thing points are stored against. A metric logged without labels has exactly one series, and the two listings coincide; they diverge only once series were logged with labels. Where a metric definition says “loss, split by worker”, a series definition says “loss where worker=w0”:

client.list_series_definitions(run="run-001")
# [SeriesDefinition(metric_name='loss', scale='ML_STEP', key='loss(worker=w0)',
# value_kind=SeriesValueKind.FLOAT, labels={'worker': 'w0'}), ...]
client.list_series_definitions(series=SeriesQuery("loss").with_label_matches("worker", "^w"))

key is the column name fetch_float_series produces for that series, so a discovery result maps straight onto a fetched frame — no string assembly on your side. Default parts are omitted: a plain metrana.log metric’s key is just its name (loss, not loss(scale=ML_STEP)), so frame["loss"] works the way you would guess.

If your series were logged with labels, you often want just the values one key takes before building a filter. (Not using labels? Skip this — an unlabelled metric has nothing to list here.)

client.list_series_label_values("run-001", "worker")
# {'worker': ['w0', 'w1', 'w2', 'w3']}
client.list_series_label_values("run-001", ["worker", "policy"])

LabelKeyFilter narrows the values rather than the keys — useful when a key has thousands:

from metrana.query import LabelKeyFilter
client.list_series_label_values("run-001", LabelKeyFilter("worker", prefix="w1"))