nirs4all.pipeline.dagml.in_process_runner module
In-process dag-ml execution (Mechanism B) — the perf-parity twin of cli_runner.
cli_runner.run_cv_refit_bundle drives dag-ml-cli as a SUBPROCESS: it writes the DSL /
envelope / graph to disk, launches the process adapter, and reads back bundle.json. This module
runs the SAME campaign IN-PROCESS through the PyO3 bridge (dag_ml._dag_ml.run_cv_refit_in_process)
— no subprocess, no per-call import tax. The bridge owns the data path (the SAME envelope-based Rust
InMemoryDataProvider the CLI uses, so the per-fold data_views/sample_ids are produced
IDENTICALLY); only operator execution crosses back to Python, through an op_callback that closes
over the SAME node_runner.run_node() the subprocess adapter calls.
The returned {node_results, scores} carries the native ScoreSet (cross-fold OOF average +
the REFIT final/test reports) — byte-identical to the subprocess bundle.scores — so the caller
maps it into a RunResult with the SAME score-extraction (result._scores_to_run_result()).
Parity contract: in-process scores == subprocess scores == legacy, for any pipeline the static subprocess
path supports. The dataset / fold-children / sample-metadata are loaded with the EXACT same logic the
subprocess adapter uses (process_adapter._build_handler()), so identity (and therefore the wire
ids) match — the only divergence is that the materialization happens in THIS process, not a child.
Generated-view campaigns can also run in an isolated Python worker through
generated_subprocess; the static CLI has no generated-view callback.
- nirs4all.pipeline.dagml.in_process_runner.run_cv_refit_bundle(*, dsl: dict[str, Any], envelope: dict[str, Any], graph: dict[str, Any], dataset_path: str, selection_metric: str = 'rmse', sample_metadata: dict[str, dict[str, Any]] | None = None, dataset_pickle: str | None = None, dataset: Any | None = None, fold_children: dict[str, dict[int, list[int]]] | None = None, fold_feature_views: dict[str, tuple[Any, dict[int, int], set[int]]] | None = None, random_state: int | None = None, refit: bool = True, refit_top_k: int = 1) dict[str, Any][source]
Run a CV+refit bundle IN-PROCESS; return
{returncode, stdout, results, scores}.Drop-in for
cli_runner.run_cv_refit_bundle()(same keyword signature minus the subprocess-onlyworkdir/dagml_cli/venv_python): the router can dispatch to either by the env flag.scores— the nativeScoreSet({reports: [...]}), byte-identical to the subprocessbundle.scoresthe CLI emits; mapped byresult._scores_to_run_result().results— the per-nodeNodeResultframes (FIT_CV validation/OOF + REFIT predictions), matching the subprocessresults.jsonlcapture.returncode/stdout— always0/""on success; a bridge refusal raises (DagMlError) instead of returning a non-zero code, so the caller’sreturncode != 0guard is a no-op here (success-path parity).
datasetis the host’s ALREADY-MATERIALIZEDSpectroDataset(withfold_childrenand optionalfold_feature_viewsfor a fold-local augmentation run): when given, the resolver is built from it directly — no disk reload.run_via_dagmlalready materialized this exact dataset (its identity fingerprint equals the reloadable path’s, verified indataset._dataset_inputs()) and, for augmentation / rep-fusion, it IS the object that was pickled for the subprocess — so the resolver is byte-identical to the reload/pickle path. WhendatasetisNonethe dataset is loaded per_load_dataset().
- nirs4all.pipeline.dagml.in_process_runner.run_cv_refit_bundle_router(*, dsl: dict[str, Any], envelope: dict[str, Any], graph: dict[str, Any], dataset_path: str, workdir: Any, dagml_cli: str, venv_python: str, selection_metric: str = 'rmse', sample_metadata: dict[str, dict[str, Any]] | None = None, dataset_pickle: str | None = None, dataset: Any | None = None, fold_children: dict[str, dict[int, list[int]]] | None = None, fold_feature_views: dict[str, tuple[Any, dict[int, int], set[int]]] | None = None, random_state: int | None = None, refit: bool = True, refit_top_k: int = 1) dict[str, Any][source]
Route CV/refit to the direct binding, static CLI, or generated Python worker.
All branches return the SAME outcome shape —
{returncode, stdout, results, scores}— so the_run_*call sites readoutcome["scores"]uniformly (nobundle.jsonre-read). The in-process branch (the DEFAULT since the ADR-17 cutover —in_process_enabled()) drivesrun_cv_refit_bundle()here and getsscoresdirectly from the bridge. The subprocess branch (Mechanism A) drivescli_runner.run_cv_refit_bundle()and liftsscoresfrom thebundle.jsonthe CLI wrote.MECHANISM CASCADE (ADR-17): try in-process when it is selected AND the
dag_ml._dag_mlextension loads (_dagml_extension_loads()); if the extension does NOT load (a wheel install missing / with a broken.so), fall through to the subprocess CLI; the subprocess branch then requires thedag-ml-clibinary and raisesDagMlUnavailablewhen it is missing too (NEITHER mechanism available →run()falls back to legacy with a warning). Thedag-ml-cliexistence check lives HERE, in the subprocess branch ONLY — so an in-process-capable wheel never needs the sibling CLI.Crucially the extension probe gates ONLY the import/LOAD, never the campaign: a real in-process runtime/operator error from
run_cv_refit_bundle()propagates untouched (never silently rerouted to subprocess or swallowed into legacy).Keeping
workdir/dagml_cli/venv_pythonin the signature lets the call sites pass the SAME kwargs to either path; the in-process branch ignores those subprocess-only inputs.dataset(the host’s already-materializedSpectroDataset, withfold_childrenfor a fold-local augmentation run) lets the direct binding build its resolver without reloading it. The static CLI ignores it and re-materializes fromdataset_path/dataset_pickle; the generated worker uses its view store but reloads the fixed PLAN dataset separately.random_stateseeds the static CLI adapter as before. The direct host is seeded byrun_via_dagml; the generated worker seeds its own Python RNG and passes the same seed to DAG-ML’s control RNG. Generated views never use the static CLI because it has no callback or receipt transport.