Skip to content

Naming rules and limits

Names are validated identically on the client and the server, so a name the SDK accepts is one ingestion accepts. Validation is blacklist-based — Metrana rejects only what’s genuinely unsafe and allows everything else, including arbitrary Unicode (Chinese, Japanese, Cyrillic, emoji, …) and most ASCII punctuation.

There are three tiers, each building on the one before it.

Tier 1 — names (metric names, scales, attribute paths, label keys)

Section titled “Tier 1 — names (metric names, scales, attribute paths, label keys)”

A plain name must:

  • be non-empty;
  • not start with : (e.g. :metric ✗, but ns:metric ✓ and a:b:c ✓);
  • contain no whitespace or control characters (no spaces, tabs, newlines, NUL, …);
  • contain none of the query-language reserved characters: ( ) [ ] , = < > ! ~ " |

Everything else is allowed — including / (used for hierarchy), ., -, _, : (not leading), and punctuation like # $ + * ; { } @ ? &, plus any Unicode.

metrana.log("train/loss", v) # ✓ slash for hierarchy
metrana.log("model.layers.0", v) # ✓
metrana.log("my-metric_v2", v) # ✓
metrana.log("ns:metric", v) # ✓ colon allowed (just not leading)
metrana.log("loss_损失", v) # ✓ unicode
metrana.log("a=b", v) # ✗ '=' is reserved
metrana.log("a b", v) # ✗ whitespace

Use /-delimited prefixes to group related series (train/loss, eval/loss are distinct series); labels and the evaluation shorthand are the other way to split a name into distinct series.

Tier 2 — URL-segment names (run, project, environment ids)

Section titled “Tier 2 — URL-segment names (run, project, environment ids)”

Run names, project names, and environment ids are rendered into portal URL paths and used as by-name lookup keys, so they add two restrictions on top of Tier 1:

  • no URL-structural characters: / \ ? # % (these break or get rewritten in a URL path segment — e.g. the browser rewrites \/, and % starts an escape);
  • must not be a reserved portal route segment (case-insensitive) — names like login, dashboard, settings, runs, search would collide with a portal route and are rejected.
metrana.log_rl_environment_step("reward", r, rl_step=s, env_id="worker-3", episode=e) # ✓
metrana.log_rl_environment_step("reward", r, rl_step=s, env_id="env/0", episode=e) # ✗ '/' not allowed in an env id

So an environment id like env0 or cartpole-7 is fine; env/0, a?b, or dashboard are not. (Note / is fine in a metric name, which is not a URL segment — only the URL-segment names forbid it.)

Label keys and values are stored in a k=v&k=v encoding, so they reserve a couple more characters:

  • Label keys follow the Tier-1 name rules plus reserve & and =. (Non-empty, no leading :, no whitespace/control, none of ()[],=<>!~"|&.)
  • Label values are more permissive: only control characters, =, and & are rejected. Spaces, parens, colons, and Unicode are all fine, and an empty value is allowed.
metrana.log("r", v, labels={"policy": "greedy"}) # ✓
metrana.log("r", v, labels={"host": "node-0:gpu1"}) # ✓ colon fine in a value
metrana.log("r", v, labels={"a=b": "x"}) # ✗ '=' in a key
metrana.log("r", v, labels={"split": "a&b"}) # ✗ '&' in a value

Attribute paths are /-delimited Tier-1 names. Nested dicts/lists passed to log_config / config= / log_attributes flatten into these paths automatically (e.g. {"optimizer": {"lr": 3e-4}}config/optimizer/lr).

Two reservations apply by attribute kind:

  • Run attributes: the sys / sys/* namespace is reserved for system-managed fields — writing to it is rejected (use set_description / set_tags for the supported ones).
  • Environment attributes: there is no sys/* reservation, but the three RL-environment query-language special fields — environment_id, latest_episode, latest_rl_step (and their camelCase aliases) — are reserved so a config attribute can’t shadow them in a filter / order-by.

Like the name rules, every limit is enforced identically on the client and the server, and all of them sit far above realistic usage — they exist so no input, however malformed, can destabilize the ingestion backend. Lengths are in bytes (UTF-8), not characters.

Identifier Maximum
Metric name 256 bytes
Scale 64 bytes
Environment id 128 bytes
Label key / label value 64 bytes each
Attribute path (run and environment) 512 bytes
Run name, project name 512 bytes
Orchestration id 256 bytes (opaque token — skips the name rules, only control characters are rejected; see Distributed logging)
Input Maximum
Labels per series 8
String attribute value 16 KiB
String-set attribute (e.g. tags), total across values 32 KiB per update — a delta update is capped on its own added + removed contribution
Histogram bins per point 512 (see Histograms)
Scatter points per step 32,768 coordinate pairs (see Scatter plots)
Steps and episodes i64::MAX (9_223_372_036_854_775_807)

Points per call — no limit (the SDK chunks)

Section titled “Points per call — no limit (the SDK chunks)”

There is no cap on how many points one log* call may carry. The wire format bounds a single batch at 10,000 points, a single histogram update at 128 points, and a single scatter update at 128 point sets (32,768 coordinate pairs total), but the SDK splits an oversized call into capped batches transparently — steps, ordering, and delivery semantics are identical to logging the same data in smaller calls. Large environment-attribute batches are likewise split automatically. Only clients speaking the REST/gRPC API directly need to respect the wire caps themselves.

Anything over a limit is rejected synchronously with a MetranaValidationError from the log* call, same as an invalid name (see Errors).