Filters & ordering
filter= and order_by= take builder objects, not strings. They cover the whole query grammar, validate
client-side, and escape values correctly — so a filter is wrong at construction, in Python, rather than as a
parse error from the server.
Attribute conditions
Section titled “Attribute conditions”AttrFilter(path) names an attribute; an operator turns it into a Condition:
from metrana.query import AttrFilter
AttrFilter("config/lr") < 0.01AttrFilter("config/lr") >= 0.001AttrFilter("sys/status") == "CLOSED"AttrFilter("sys/status") != "FAILED"Every operator also has a method spelling, for functional style or where an operator reads badly:
.lt(), .lte(), .gt(), .gte(), .eq(), .ne().
Beyond comparison:
.matches(regex) |
RE2-style regex, evaluated server-side |
.contains(value) |
substring on strings, membership on string sets |
.contains_any([...]) / .contains_all([...]) |
string sets |
.is_in([...]) / .not_in([...]) |
set membership |
.exists() |
the attribute is present at all |
Which of these are meaningful depends on the attribute’s kind — contains_all is a string-set operation,
and < on a string compares lexicographically, not numerically.
list_run_attribute_definitions reports the kinds.
Combining
Section titled “Combining”Use & and |:
(AttrFilter("sys/status") == "CLOSED") & (AttrFilter("config/lr") < 0.01)(AttrFilter("config/seed") == 0) | (AttrFilter("config/seed") == 1)Series conditions
Section titled “Series conditions”SeriesFilter(metric) filters on a summary of a series. Pick the function, then compare:
from metrana.query import SeriesFilter
SeriesFilter("loss").last() < 0.1SeriesFilter("accuracy").max() >= 0.95SeriesFilter("reward").with_scale("EPISODE").with_label("policy", "greedy").avg() > 100SeriesFilter("loss").exists()Labels here take the same operators and the same partial matching as a point fetch:
SeriesFilter("loss").with_label_matches("worker", "^gpu.*").avg() < 0.1SeriesFilter("loss").with_label_in("shard", ["0", "1"]).max() < 2.0SeriesFilter("loss").avg() < 0.1 # every series of the metricA condition still yields one value per run: where the labels match several of a run’s series, the function is computed across them, and the comparison is applied to that. See Across-label aggregation.
Ordering
Section titled “Ordering”from metrana.query import AttrOrderBy, SeriesOrderBy
AttrOrderBy("sys/creation_time").desc()AttrOrderBy("config/lr").asc()SeriesOrderBy("accuracy", "max").desc()SeriesOrderBy("loss", "last").with_scale("EPISODE").asc()Runs default to creation time descending when you pass no order_by.
Where each kind is valid
Section titled “Where each kind is valid”| Context | Attribute conditions | Series conditions |
|---|---|---|
Runs (fetch_runs, run selectors) |
✅ | ✅ |
RL environments (fetch_rl_envs, env selectors) |
✅ | ✅ |
| Projects, experiments, definition listings | ✅ | ❌ |
A Condition remembers whether it contains a series condition, so passing one where it cannot work is
rejected client-side with a message naming the context — not sent and refused.
Reserved paths
Section titled “Reserved paths”Well-known attribute paths are exported as constants, so a typo is an ImportError rather than an empty
result:
from metrana.query import SYS_STATUS, SYS_TAGS, SYS_CREATION_TIME, ENV_ID
AttrFilter(SYS_STATUS) == "CLOSED"AttrFilter(SYS_TAGS).contains_all(["baseline"])AttrOrderBy(SYS_CREATION_TIME).desc()Run and project system attributes
Section titled “Run and project system attributes”Every SYS_* constant, importable from metrana.query:
| Constant | Path |
|---|---|
SYS_NAME |
sys/name |
SYS_ID |
sys/id |
SYS_FAMILY |
sys/family |
SYS_EXPERIMENT_NAME |
sys/experiment/name |
SYS_EXPERIMENT_ID |
sys/experiment/id |
SYS_PROJECT_NAME |
sys/project/name |
SYS_PROJECT_ID |
sys/project/id |
SYS_PARENT_DIRECTORY_ID |
sys/parent_directory_id |
SYS_DESCRIPTION |
sys/description |
SYS_TAGS |
sys/tags |
SYS_ARCHIVED |
sys/archived |
SYS_CREATION_TIME |
sys/creation_time |
SYS_MODIFICATION_TIME |
sys/modification_time |
SYS_CLOSE_TIME |
sys/close_time |
SYS_OWNER |
sys/owner |
SYS_STATUS |
sys/status |
SYS_RUNNING_TIME_SECONDS |
sys/running_time_seconds |
SYS_FORKING_PARENT_RUN_NAME |
sys/forking/parent_run_name |
SYS_FORKING_PARENT_RUN_ID |
sys/forking/parent_run_id |
SYS_FORKING_STEP |
sys/forking/step |
SYS_DATA_READINESS_READY |
sys/data_readiness/ready |
SYS_DATA_READINESS_READY_TIME |
sys/data_readiness/ready_time |
SYS_STATUS takes the run states you see in the portal — CREATED, RUNNING, CLOSED, CRASHED (see
run status). SYS_ARCHIVED is the flag behind the runs table’s archived
toggle, so AttrFilter(SYS_ARCHIVED) == False reproduces the portal’s default view.
RL environment fields
Section titled “RL environment fields”| Constant | Meaning |
|---|---|
ENV_ID |
The environment’s env_id. |
ENV_LATEST_EPISODE |
Highest episode the environment has logged. |
ENV_LATEST_RL_STEP |
Highest RL step the environment has logged. |
RESERVED_ENV_FIELDS is the set of all three, useful if you need to tell a reserved field apart from one of
your own logged environment attributes:
from metrana.query import RESERVED_ENV_FIELDS
is_reserved = path in RESERVED_ENV_FIELDSSelecting runs and environments
Section titled “Selecting runs and environments”Every fetch that needs a scope takes it in one of four forms. The accepted union is named
RunsSelector for runs and EnvironmentsSelector for environments:
| Form | Example | Use when |
|---|---|---|
| A single name | "run-001" |
You know exactly which one. |
| A sequence of names | ["run-001", "run-002"] |
You have a fixed list, e.g. from fetch_runs. |
A Condition |
AttrFilter("config/lr") < 0.01 |
You want “whichever match”, without listing them. |
A RunsSelection / EnvironmentsSelection |
see below | You need ordering or a cap as well as a filter. |
The first three cover most code. Reach for the *Selection form when which ones you get depends on order —
“the five most recent closed runs” is a filter plus an ordering, not a filter alone:
from metrana.query import AttrFilter, AttrOrderBy, RunsSelection, SYS_CREATION_TIME, SYS_STATUS
client.fetch_float_series( RunsSelection( filter=AttrFilter(SYS_STATUS) == "CLOSED", order_by=AttrOrderBy(SYS_CREATION_TIME).desc(), ), "loss", max_runs=5,)Both fields are optional, and order_by is applied before any max_runs / max_environments cap — which
is the whole point: the cap keeps the first N in your order, not an arbitrary N. The default ordering differs
between the two:
RunsSelection— creation time descending.EnvironmentsSelection— environment id ascending.
EnvironmentsSelection is otherwise the same shape, and accepts both the
reserved environment fields and your own logged attributes.