Skip to content

Runs & projects

Three methods cover the top of the hierarchy: projects, the experiments inside them, and the runs.

client.fetch_projects()
client.fetch_projects(filter=AttrFilter("sys/name").matches("^rl-"), max_projects=50)

One row per project: name, runs_count, experiments_count, then one column per requested attribute path.

Projects are the one resource whose filters and ordering accept attributes only — a project has no series of its own, so a SeriesFilter is rejected client-side rather than sent and refused.

An experiment is a grouping over runs, so listing them is a list of names:

client.list_experiments()
# ['lr-sweep', 'ablations', 'baseline']
runs = client.fetch_runs(
experiment="lr-sweep",
filter=AttrFilter("config/lr") < 0.01,
order_by=AttrOrderBy("sys/creation_time").desc(),
attributes=["sys/status", "config/lr"],
series_summaries=[SeriesSummary("loss", "last")],
max_runs=100,
)

One row per run: name, experiment, then attribute columns (object dtype, None where the run lacks the attribute), then summary columns (float64, NaN where the run has no such series).

fetch_runs depaginatesmax_runs is a cap you choose, not a page size, and the total number of matching runs is available independently of it:

runs.attrs["total_count"] # matching runs, ignoring max_runs / skip

Unlike the series fetches, the runs table has no 1000-run ceiling: its rows are the result, so it pages until you stop it.

Choosing columns without knowing the names

Section titled “Choosing columns without knowing the names”

attributes= and series_summaries= also accept a pattern, resolved against the project’s definitions before the fetch. Useful when you want “everything under config/” without enumerating it:

from metrana.query import AttributeFilter, MetricFilter, SummaryFunction
client.fetch_runs(
attributes=AttributeFilter(prefix="config/"),
series_summaries=MetricFilter(prefixes=["train/"], functions=[SummaryFunction.LAST]),
)

AttributeFilter takes prefix (a subtree) or regex (a full-path RE2 match), plus optional kinds. MetricFilter takes prefixes / regex / scales and the summary functions to apply.

Each pattern costs one extra round trip — the definitions lookup — so an explicit list of columns is the cheaper form when you already know them.

fetch_runs answers “this column across many runs”. For the opposite — every attribute of one run — use the listing, which returns a plain dict:

client.list_run_attributes("run-001")
# {'sys/status': 'CLOSED', 'sys/tags': ['baseline'], 'config/lr': 0.003, ...}
client.list_run_attributes("run-001", filter=AttributeFilter(prefix="config/"))

Values come back as Python types: str, int, float, bool, list[str] for string sets, and datetime for timestamps.