pipekit-experiment¶
The boundary between pipekit pipelines and external orchestration tools — runtime-checkable protocols, a content-addressed model registry, JSON-roundtrippable artifacts, and thin per-tool adapters. Master plan reference: Report 12.
Protocols¶
pipekit_experiment.protocols
¶
Two protocols — ExperimentTracker and ModelRegistry.
The seam between pipekit pipelines and external orchestration tools. Adapters (MLflow, W&B, DVC, Hydra, Metaflow) satisfy these protocols structurally — no inheritance from pipekit-experiment is required.
Both protocols are runtime-checkable.
See master plan Report 12, section 2.2.
ExperimentTracker
¶
Bases: Protocol
Log metrics, parameters, and artifacts from a training run.
Implementations wrap external trackers — MLflow, W&B, ClearML, Neptune, ad-hoc CSV writers. Pipekit pipelines call these methods; the adapter translates to the tool's API.
Source code in packages/pipekit-experiment/src/pipekit_experiment/protocols.py
ModelRegistry
¶
Bases: Protocol
Store and retrieve trained models by content hash.
Implementations: filesystem-backed, S3-backed, MLflow-registry-backed.
The canonical identifier is the content hash; name is a
mutable tag that resolves to a hash.
Source code in packages/pipekit-experiment/src/pipekit_experiment/protocols.py
Run records¶
pipekit_experiment.run
¶
Run records, per-step metrics, and named artifacts.
Run— a single pipeline execution; identified byrun_idandpipeline_hash.RunMetrics— per-step metric series.RunArtifacts— named artifact URIs attached to a run.
See master plan Report 12, section 2.3.
Run
dataclass
¶
A single execution of a pipeline (training or inference).
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
str
|
Tracker-assigned identifier (or a uuid for local runs). |
pipeline_hash |
str
|
Pipekit-derived content hash of the pipeline. |
config |
dict[str, Any]
|
Flat dict of pipeline parameters logged at start. |
metrics |
dict[str, float]
|
Latest metric values (the running snapshot;
full per-step history lives in |
artifacts |
dict[str, str]
|
|
started_at |
datetime
|
UTC timestamp. |
ended_at |
datetime | None
|
UTC timestamp; |
status |
RunStatus
|
One of |
Source code in packages/pipekit-experiment/src/pipekit_experiment/run.py
to_dict()
¶
Return a JSON-friendly dict snapshot (datetimes → isoformat).
Source code in packages/pipekit-experiment/src/pipekit_experiment/run.py
RunArtifacts
¶
Named artifacts (files, models, plots) attached to a Run.
Attributes:
| Name | Type | Description |
|---|---|---|
items |
dict[str, str]
|
|
Source code in packages/pipekit-experiment/src/pipekit_experiment/run.py
RunMetrics
¶
Per-step metric series.
Attributes:
| Name | Type | Description |
|---|---|---|
values |
dict[str, list[tuple[int, float]]]
|
|
Source code in packages/pipekit-experiment/src/pipekit_experiment/run.py
Model registry¶
pipekit_experiment.registry
¶
Content-addressed model registries.
A trained model is identified by hash(operator_config, weights);
loading by hash is the canonical path. Names ("methane_emulator_v3")
are tags that resolve to hashes.
LocalModelRegistry— local-filesystem backend. Useful for dev, CI, and as the reference implementation.S3ModelRegistry— fsspec-backed (works for s3, gcs, az, file …). Behind the[s3]extra; raises a cleanImportErroriffsspecisn't installed.
Layout under root/ (or s3://bucket/prefix/):
<hash>/operator.json # pipekit operator state (round-trip via Operator.from_state)
<hash>/weights.bin # optional raw weight bytes
<hash>/metadata.json # tags, training run id, …
_tags/<name> # one-line file holding the target hash
The weights.bin blob is opaque to the registry — domain
operators round-trip their own weights via a separate channel
(pipekit_jax.JaxModelOp.with_weights etc.). The registry just
persists / retrieves the bytes alongside the operator config.
Both backends share their control flow via _ModelRegistryBase; the
subclasses only supply the storage primitives (local Path I/O vs
fsspec). References (hashes) and tag names are restricted to single
path segments so a caller-supplied string can never escape the
registry root.
Loading verifies content against its address: load and
load_weights recompute the hash of what is on disk and raise
HashMismatchError if it differs from the requested hash, and load
also checks that the rebuilt operator hashes back to it
(pipeline_hash(load(h), load_weights(h)) == h).
See master plan Report 12, section 2.4 and section 5.
HashMismatchError
¶
Bases: ValueError
Stored content does not match the hash it is stored under.
Raised on load when the bytes on disk (operator.json,
weights.bin) were modified after store, or when the operator
rebuilt from them hashes to something else. Subclass of ValueError.
Source code in packages/pipekit-experiment/src/pipekit_experiment/registry.py
LocalModelRegistry
¶
Bases: _ModelRegistryBase
Local-filesystem model registry.
Writes are atomic (temp file + os.replace), so a crash mid-write
can never leave a truncated operator.json or tag file behind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | PathLike[str]
|
Directory under which models are stored. Created on construction if it doesn't exist. |
required |
Storage layout (under root):
``<hash>/operator.json`` — operator state
``<hash>/weights.bin`` — optional weight bytes
``<hash>/metadata.json`` — tags + arbitrary metadata
``_tags/<name>`` — file containing the target hash
Source code in packages/pipekit-experiment/src/pipekit_experiment/registry.py
S3ModelRegistry
¶
Bases: _ModelRegistryBase
fsspec-backed model registry.
Works with any fsspec-supported scheme (s3://, gs://,
az://, memory://, plain file:// paths). Behind the
[s3] extra.
Unlike LocalModelRegistry, writes are only as atomic as the
backing store makes them (object stores typically are; plain
file:// through fsspec is not).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
Root URI (e.g. |
required |
storage_options
|
dict[str, Any] | None
|
Passed straight to |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
Source code in packages/pipekit-experiment/src/pipekit_experiment/registry.py
Artifacts¶
pipekit_experiment.artifacts
¶
Reproducibility artifacts — training and inference snapshots.
TrainingArtifact— captures everything needed to rebuild a trained model: the training pipeline YAML, dataset hash, trained model hash, tracker run id, registry URI, backend info, dep lock.InferenceArtifact— pins the trained-model hashes referenced by the inference pipeline. Subclass ofTrainingArtifactis overkill; it's a separate dataclass.
Both serialise as JSON via save / load. They're plain data carriers
— rerun and reload_model are convenience helpers that delegate
to a supplied registry / loader.
See master plan Report 12, section 2.5.
InferenceArtifact
dataclass
¶
Bases: _JsonArtifact
Reproducibility snapshot for an inference pipeline.
Pins the trained-model hashes referenced by the pipeline so a pipekit YAML round-trip resolves to the exact same weights.
Attributes:
| Name | Type | Description |
|---|---|---|
pipeline_yaml |
str
|
Serialised inference pipeline. |
pinned_model_hashes |
dict[str, str]
|
|
backend_info |
dict[str, Any]
|
Backend-stack info. |
deps_lock |
str
|
Lockfile contents. |
metadata |
dict[str, Any]
|
Arbitrary metadata. |
Source code in packages/pipekit-experiment/src/pipekit_experiment/artifacts.py
reload_models(registry)
¶
Resolve every pinned model via registry.load.
Returns a {operator-path: Operator} dict for callers to
substitute back into the pipeline.
Source code in packages/pipekit-experiment/src/pipekit_experiment/artifacts.py
TrainingArtifact
dataclass
¶
Bases: _JsonArtifact
Reproducibility snapshot for a training run.
Attributes:
| Name | Type | Description |
|---|---|---|
training_pipeline_yaml |
str
|
Serialised training pipeline (the
pipekit-train |
dataset_hash |
str
|
Content hash of the training dataset. |
trained_model_hash |
str
|
Content hash of the trained model (registry identifier). |
tracker_run_id |
str | None
|
External-tracker run reference (e.g.
|
model_registry_uri |
str
|
URI under which the model was stored. |
backend_info |
dict[str, Any]
|
Free-form dict describing the training stack (lightning version, JAX version, GPUs, …). |
deps_lock |
str
|
Contents of the uv / poetry lockfile. |
metadata |
dict[str, Any]
|
Optional arbitrary metadata (author, date, notes). |
Source code in packages/pipekit-experiment/src/pipekit_experiment/artifacts.py
reload_model(registry)
¶
Resolve the stored model via registry.load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
Any
|
Anything satisfying the |
required |
Source code in packages/pipekit-experiment/src/pipekit_experiment/artifacts.py
Adapters¶
Each adapter is gated behind its tool's optional extra and raises a
clean ImportError on first use if the underlying tool isn't
installed. The pure-pipekit surface remains importable either way.
DVC¶
Requires pipekit-experiment[dvc].
pipekit_experiment.adapters.dvc
¶
DVC adapter — dataset content-hash tracking.
DVC is fundamentally about data versioning, not metrics. This adapter focuses on dataset content-hash tracking via the DVC CLI:
add(path)— stagepathinto DVC's content-addressed store, producing a.dvcpointer file.pull(path)— fetch the data referenced bypath.dvcfrom the configured remote.hash(path)— return the content hash recorded inpath.dvc.
The adapter doesn't import the dvc Python package — it shells out
to the dvc CLI. That keeps the dependency surface minimal: any
DVC version installed on the system works. The [dvc] extra
declares the Python package for environments that prefer in-process
APIs; future versions can switch to the Python API without changing
the public interface here.
Behind pipekit-experiment[dvc] extra.
See master plan Report 12, section 3.2.
DVCDatasetVersioning
¶
Track datasets via the DVC CLI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
repo
|
str | Path
|
Path to the DVC-initialised git repository. Defaults to the current working directory. |
'.'
|
dvc_executable
|
str
|
Override the |
'dvc'
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
at first use if the |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/dvc.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
add(path)
¶
Stage path into DVC. Returns the .dvc pointer file path.
The returned Path is anchored under self.repo when path
is given as a relative path, so callers can rely on
add(...).exists() regardless of the process cwd.
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/dvc.py
hash(path)
¶
Return the content hash recorded for path.
Reads the .dvc pointer file and extracts the md5 /
hash field. Cheap (no DVC subprocess) once the pointer
exists.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
if no |
ValueError
|
if the pointer file is malformed. |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/dvc.py
pull(path)
¶
push(path)
¶
record_hash_in(path, sink)
¶
Convenience: stash the dataset hash into sink and return it.
Pairs nicely with TrainingArtifact construction::
meta: dict[str, str] = {}
dvc.record_hash_in("data/train/", meta)
artifact = TrainingArtifact(..., dataset_hash=meta["data/train/"], ...)
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/dvc.py
status(path=None)
¶
Return the textual dvc status output.
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/dvc.py
Hydra + hydra-zen¶
Requires pipekit-experiment[hydra].
pipekit_experiment.adapters.hydra
¶
Hydra + hydra-zen adapter.
Bridges pipekit operator graphs and Hydra configs. Two directions:
from_hydra_cfg(cfg)— instantiate a pipekitOperatorfrom a Hydra/OmegaConf config (or any object with a_target_key). Useshydra.utils.instantiate.to_hydra_cfg(op)— build a hydra-zenbuilds(...)spec from an operator'sstaterecord. The resulting object round-trips throughhydra-zen'sto_yaml/instantiate.from_yaml_sandboxed(text)— build a pipeline from untrusted_target_YAML, allowing only classes registered withpipekit.register. It never callshydra.utils.instantiate(which will call any dotted path): the YAML is translated into a pipekit state record and built bypipekit.from_state_sandboxed.
The adapter doesn't import hydra or hydra_zen at module load
— each method imports lazily so a user can from … import hydra as
hydra_adapter in code that branches on what's installed.
Behind pipekit-experiment[hydra] extra.
See master plan Report 12, section 3.3.
HydraConfigLoader
¶
Bidirectional bridge between pipekit operators and Hydra configs.
Stateless — every method is a @staticmethod. Construct once
and reuse, or call the static methods directly.
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/hydra.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
from_hydra_cfg(cfg)
staticmethod
¶
Instantiate an Operator from a Hydra-flavoured config.
cfg may be:
- An omegaconf.DictConfig / ListConfig.
- A plain dict with a _target_ key.
- Any object that hydra.utils.instantiate accepts.
Raises:
| Type | Description |
|---|---|
ImportError
|
if |
TypeError
|
if the instantiation result isn't an |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/hydra.py
from_yaml(text)
staticmethod
¶
Parse a Hydra YAML string back into an Operator.
Uses OmegaConf to load the YAML, then from_hydra_cfg to
instantiate. Requires both hydra-core and omegaconf
(the latter is a transitive dep of hydra-core).
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/hydra.py
from_yaml_sandboxed(text)
staticmethod
¶
Build a pipeline from untrusted _target_ YAML, safely.
Only classes registered with pipekit.register can be built, at
any depth (pipekit's own containers included); every class is
checked before anything is constructed. The YAML is parsed with
yaml.safe_load, interpolations (${...}) are not resolved,
and hydra.utils.instantiate is never called. Each _target_
must name an Operator class (or Class.from_config) in an
already-imported module; {_target_: hydra.utils.get_class,
path: module.Name} names an exception class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
A YAML document whose root has a |
required |
Raises:
| Type | Description |
|---|---|
ImportError
|
if PyYAML isn't installed. |
PermissionError
|
if a |
ValueError
|
if a node uses |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/hydra.py
to_hydra_cfg(op)
staticmethod
¶
Produce a hydra-zen builds(...) spec for op.
The returned object is the hydra-zen-generated dataclass that
hydra-zen's to_yaml and instantiate understand. For a
plain operator with primitive-typed config the round-trip is
lossless; operators with closures / callables (Lambda, Tap, …)
emit a debug-only spec.
Raises:
| Type | Description |
|---|---|
ImportError
|
if |
TypeError
|
if |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/hydra.py
to_yaml(op)
staticmethod
¶
Render op, nested operators included, as _target_ YAML.
Written from op.state: each operator becomes a mapping with
its _target_ and its config, nested operators become nested
mappings, an exception class becomes
{_target_: hydra.utils.get_class, path: module.Name}, and an
operator whose config is not a constructor call (a Graph) is
written as _target_: <Class>.from_config with a config:
mapping. The text loads back with from_yaml (Hydra) or
from_yaml_sandboxed to an operator with the same state.
Raises:
| Type | Description |
|---|---|
ImportError
|
if PyYAML isn't installed. |
TypeError
|
if |
ValueError
|
if any operator in the tree holds a closure
( |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/hydra.py
Metaflow¶
Requires pipekit-experiment[metaflow].
pipekit_experiment.adapters.metaflow
¶
Metaflow adapter — wrap a pipekit operator as a Metaflow @step.
Pipekit handles the compute; Metaflow handles scheduling and cross-step artifact tracking. The adapter is pure glue:
as_step(op, name, inputs)— returns a function suitable for decoration with@stepinside aFlowSpec.MetaflowStepAdapter.run(op, inputs)— invoke an operator with inputs read fromself.<input_name>and write outputs toself.<name>.
The adapter doesn't import metaflow at module load — the import
happens only when the user actually constructs a Metaflow flow with
the produced step.
Behind pipekit-experiment[metaflow] extra.
See master plan Report 12, section 3.5.
MetaflowStepAdapter
¶
Adapt a pipekit Operator for use as a Metaflow @step.
Use either:
as_step(op, name, inputs)to obtain a callable that reads its inputs fromself.<input_name>and writes the operator's output toself.<name>. Apply@stepto it inside aFlowSpec.run(op, namespace, name, inputs)to invoke the same logic synchronously, passing any attribute-style object (e.g. aSimpleNamespace; not a plain dict) asnamespace. Useful for testing flows outside Metaflow.
The adapter never inspects Metaflow internals; it only relies on
the self namespace conventions and the @step decorator.
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/metaflow.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
as_step(op, *, name, inputs, decorate=True)
staticmethod
¶
Build a Metaflow step function around op.
Returns a function with signature step(self) that reads
self.<input> for each inputs entry, applies op,
and writes the result to self.<name>.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
op
|
Operator
|
The pipekit operator to wrap. |
required |
name
|
str
|
Attribute name for the output (also used as
|
required |
inputs
|
list[str]
|
Input attribute names, in argument order. |
required |
decorate
|
bool
|
If |
True
|
Raises:
| Type | Description |
|---|---|
ImportError
|
if |
Source code in packages/pipekit-experiment/src/pipekit_experiment/adapters/metaflow.py
run(op, namespace, *, name, inputs)
staticmethod
¶
Invoke op against attributes of namespace and persist its output.
Reads each input_name from namespace and calls
op(*args). Writes the result back as namespace.<name>.
Returns the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
op
|
Operator
|
The pipekit operator to invoke. |
required |
namespace
|
Any
|
An object exposing |
required |
name
|
str
|
Attribute name under which to store the output. |
required |
inputs
|
list[str]
|
List of input attribute names to read from
|
required |