nix-workflow: declare, freeze, and chain all your experiments

Declare

In functional languages, moving and non-moving parts are separated to the extreme. For experiments, the moving parts are parameters and runtime configuration — the non-moving part is the versioned package source code. Enforcing this separation means an experiment can be precisely declared and identified as purely static, JSON-like data: its parameters and runtime configuration, nothing else. This makes experiment setup clear, easy to understand and reference, which in turn enables pinning (freezing) — by definition, fixing the moving parts exactly, since we have captured all of them.

Freeze

Each experiment is its parameters and its runtime — both expressed as pure data in Nix. Nix pins them together naturally.

Two layers of freezing: input-addressed, to track and reason about experiment parameters forever; content-addressed, to reduce branching to only what is needed — upstream changes will not cause downstream branching if the upstream output does not change.

Chain

Assign a step to a variable, reference it or its output files via string interpolation, and its full lineage follows automatically:

dataset = output ''
  ${build_data_bin}/bin/build-data --name=demo --samples=500 --seed=42
'';

model = output ''
  ${train_bin}/bin/train --data=${dataset}/data.csv --epochs=10 --lr=0.001
'';

Here model depends on dataset. nix-workflow discovers this from the reference and builds them in the right order, caching any step whose inputs haven’t changed.