Decide the shape first
Series identity is(run, kind, key, dimensions). Every distinct dimension combination is a separate series — and a series holding one point has nothing to plot, so it renders as a scalar tile rather than a graph.
Every metric is one of three shapes:
Curve
Loss, learning rate, reward over time. One series, many points, distinguished by
step.Only step makes a curve. Spreading values across a dimension does not.Headline scalar
Final accuracy. One series, one point, zero to two low-cardinality dimensions.
Breakdown
Accuracy by category. One series per category value.
Dimensions versus labels
This is the distinction that decides whether a run page is readable.unit() context, with the explicit call site winning per key. A key present in both maps raises ValueError.
On the CLI, dimensions are --dim:
Steps
A resumed run continues its curve, so a step at or below the resume point is treated as a retry rather than appended — that would splice two executions into one series. The
hardware kind is exempt in both directions, because its steps come off a different clock.Timestamps
wall_clock is optional and, when omitted, the point is stamped at ingest time. That is right for a live training loop and wrong for an importer replaying history — pass wall_clock explicitly when the value’s real time is not now.
Value types
Numbers — including bools, numpy scalars and 0-dimensional tensors — become metric points and plot. Strings, dicts, lists andNone go into that step’s record and read back through the trajectory view. You do not need to filter before logging.
Kinds
kind separates rails that should not share a page. model is the default; hardware is what log_hw writes.
Declaring the reduction
agg (mean, sum, min, max, count) declares how the key reduces, so a later grouped read can omit its own. The producer knows whether a count sums or a loss averages; declaring it at the write saves every reader from guessing. Conflicting declarations are rejected.
Derived series
Values computed after the fact are marked as such, so post-hoc numbers never masquerade as training-loop capture.Expression views
A view is a read-time formula over logged series. The stored object is the formula, never data — so a view costs nothing and can be created on a completed run.Reading metrics back
grouped reduces server-side. export is lossless and cursor-paginated — use it when you need the actual rows, not a chart’s downsampled view.
Curves in the terminal
--keys get a panel each, because two metrics on different scales do not share an axis. --overlay puts them on one canvas and says in the footer when the scales are far enough apart that the smaller curve has flattened.
Anything the picture cannot show is printed to stderr first: a read the window cut short, a --key that matched nothing, a series dropped from an overlay, a non-finite point no scale can hold. On the canvas, % marks a cell more than one curve reached.
Colour is off when stdout is not a TTY and whenever NO_COLOR is set; braille and box-drawing degrade to ASCII when the stream’s encoding cannot carry them. Unlike its siblings, plot accepts a petname short_id as well as a UUID.