MLflow integration¶
Install the optional integration first:
OCLP remains the authoritative provenance store. The MLflow adapter mirrors already-published OCLP records and selected payloads for experiment tracking; it never reads MLflow to create, validate, or query OCLP provenance.
Configure an MLflow mirror for a run¶
Attach MlflowAdapter to the workflow's @run declaration:
from oclp import MlflowAdapter, observe_run, run
@run(
name="Daily training",
adapters=(
MlflowAdapter(
experiment_name="daily-training",
tracking_uri="sqlite:///mlflow.db",
),
),
)
def workflow(): ...
with observe_run(workflow, publisher=publisher, source=source):
workflow()
The declaration is a reusable template. The runtime calls for_run() when
available, creating a fresh active mirror for each observation. When neither
tracking_uri nor artifact_location is set, the local publisher defaults to
a SQLite MLflow store and artifact directory beside the OCLP record directory.
The adapter mirrors canonical OCLP record JSON, UUID tags, typed Execution parameters, and top-level numeric Evidence details. Model payloads mirror by default. Each mirrored payload is stored beneath its OCLP Artifact UUID, so repeated same-named outputs, such as temporal-fold models, remain distinct in the MLflow artifact store.
Execution parameter keys include their OCLP Execution UUID because MLflow parameters are immutable within one MLflow run while a Computation can run repeatedly with different arguments.
Select Computation metrics and additional payloads¶
Use @mlflow outside @computation to keep MLflow presentation policy beside
the output it projects:
from oclp import JsonArtifact, MlflowMetrics, computation, mlflow
@mlflow(
metrics=(
MlflowMetrics(
output_port="metrics",
prefix="fold",
dimensions=("fold_number",),
),
),
)
@computation(
name="Train fold",
outputs={"metrics": JsonArtifact(name="Fold metrics")},
)
def train_fold(*, fold_number: int) -> dict[str, object]:
return {"metrics": {"rmse": 42.1}}
The selected output must be an application/json Artifact. The adapter reads
its verified bytes and logs only top-level numeric scalar fields. dimensions
must name declared scalar Execution parameters and prevent repeated
invocations from producing the same MLflow key. A collision is an adapter
failure: it follows the normal Diagnostic behavior unless strict=True.
@mlflow(payloads=("report",)) similarly mirrors a named non-model output
payload. Payload names are exact output ports, not Artifact display names.
Model payloads and canonical OCLP record JSON always mirror. The declaration
is inert when the active run has no MlflowAdapter, so the same Computation
works in environments without MLflow. Execution parameters already mirror
automatically, so the decorator is needed only for metric projections and
additional payloads.
@computation must be closest to the function. @mlflow must be outside it
so it can validate declared output ports. When a Computation also has a
run-local @artifact_set declaration, that decorator is also outside
@computation; their relative order does not matter.
Select workflow parameters¶
Use @mlflow outside @run when a small, explicit set of workflow arguments
should also appear in MLflow. The mapping is MLflow parameter name to
workflow argument name:
from oclp import MlflowAdapter, mlflow, run
@mlflow(
run_parameters={
"release_id": "release_id",
"temporal_fold_count": "fold_count",
},
)
@run(
name="Daily training",
adapters=(MlflowAdapter(experiment_name="daily-training"),),
)
def train(*, release_id: str, fold_count: int) -> None: ...
The adapter logs these as workflow.release_id and
workflow.temporal_fold_count, with matching
oclp.mlflow.workflow_parameter.<name>=true tags. This keeps them separate
from automatically mirrored, Execution-UUID-scoped parameters and makes their
provenance clear: they are application-chosen workflow context, not Core
Execution parameters.
Every selected argument must be JSON-compatible. The decorator rejects unknown
argument names at module load. A conflicting value for the same workflow.*
key within an MLflow run becomes an integration Diagnostic by default and
raises with MlflowAdapter(strict=True). The declaration is inert without an
MLflow adapter and never creates an OCLP record, Execution, or derivation edge.
run_parameters belongs only on @mlflow outside @run; metrics and
payloads belong only on @mlflow outside @computation.
Add application-selected comparison values¶
After the adapter is declared, retrieve its active session from the observed run and add values whose MLflow presentation is an application decision:
mlflow = observed.adapter(MlflowAdapter)
mlflow.log_metrics({"validation_rmse": 42.1})
mlflow.log_parameters({"candidate_family": "CatBoostRegressor"})
The adapter already mirrors OCLP Execution parameters and Evidence records, so use this only for additional application-specific comparison context.
Failure policy and model registration¶
By default, an MLflow failure does not undo OCLP publication. The runtime emits
an adapter-failed Event with an integration Diagnostic on the next real
Execution. Set strict=True to make a mirror failure fail the application
workflow.
For explicit registry publication, nominate the exact Artifact by its application-owned name:
from oclp import MlflowAdapter, MlflowModelRegistration
adapter = MlflowAdapter(
experiment_name="daily-training",
model_registration=MlflowModelRegistration(
artifact_name="Validated model",
registered_model_name="demand-model",
),
)
Registration is opt-in; the adapter does not infer promotion policy.