Your trained model, schema and verified structure. One file.
MLbundle packages a trained model with its manifest, locally inferred input schema,
extractable structure and a lock of its runtime environment in one compressed
.mlbundle file. The same library
writes the bundle and validates it when loaded, preserving feature order, class
labels and output meaning across the training/runtime boundary.
Use it to hand a model to MLSuite, inspect its metadata or build your own consumer around the validated native model.
A model file alone leaves the next application to recover its inputs, labels, environment and supporting metadata. Separate descriptions can drift from the model they are supposed to describe.
MLbundle keeps them together and checks model-derived claims against the loaded
model. Schema inference runs locally through MLschema;
real training rows are included only when explicitly requested. Native formats
preserve the supported model without conversion, and a PEP 751 pylock.toml
pins the exact wheels a Linux or Windows runtime needs to serve it.
MLbundle 0.3.1 requires Python 3.14 for export with the schema extra. The installation command for this release is:
python -m pip install "mlbundle[sklearn,schema,lock]==0.3.1"To use a local checkout, run python -m pip install -e ".[sklearn,schema,lock]"
from its root. The base library requires Python >=3.14; schema inference uses
mlschema >=0.3.0,<0.4 on Python 3.14 only.
Choose xgboost, catboost or onnx instead of sklearn, or install [all].
Export always needs schema; lock provides uv, which resolves the runtime lock
through your package index unless you pass environment_lock=. Metadata-only
inspection needs only the base package; load and CLI verification need the
matching model extra.
Train a model, export its schema without including training rows, then inspect and load the bundle:
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from mlbundle import export, inspect, load
training = pd.DataFrame(
{
"temperature": [0.0, 1.0, 0.1, 0.9],
"humidity": [1.0, 0.0, 0.9, 0.1],
"target": ["cold", "warm", "cold", "warm"],
}
)
model = DecisionTreeClassifier(random_state=7).fit(
training.drop(columns="target"), training["target"]
)
export(
model,
"trained.mlbundle",
training_data=training,
target_names=["target"],
)
metadata = inspect("trained.mlbundle") # No model deserialization.
print(metadata.manifest.features.names) # ["temperature", "humidity"]
bundle = load("trained.mlbundle") # Recomputes native claims and structure.
result = bundle.predict_record({"temperature": 0.0, "humidity": 1.0})[0]
print(result.predicted_label) # cold
print(result.probabilities) # (1.0, 0.0)The target column is excluded from the input schema. Add sample_rows=N only
when you intend to include the first N real feature rows. Without
training_data=, export infers a default schema from synthetic values.
Existing destinations require overwrite=True. Native feature names take
precedence; otherwise mappings use zero-based positions. load and inspect
accept paths, bytes or streams. The native model remains available as
bundle.native_model; mutating it invalidates the verified metadata.
| Operation | Purpose |
|---|---|
export(model, destination, ...) |
Validate a native round trip, infer the schema locally and write a bundle. |
inspect(source) |
Validate the container, manifest, hashes and declarations without loading a model. |
load(source) |
Load through a closed adapter and verify model-derived claims and structure. |
bundle.predict_proba(data) |
Classifier probabilities as plain lists. |
bundle.predict(data) |
Regressor predictions as plain lists. |
bundle.predict_record(record) |
Map one record through the schema and return frozen output-major results. |
bundle.row_from_record(record) |
Validate and order record values for native inference or explanations. |
check_environment(view) |
In the serving process, require exactly the bundle's lock plus mlbundle. |
The CLI exposes metadata inspection and full verification:
mlbundle inspect trained.mlbundle
mlbundle inspect trained.mlbundle --json
mlbundle verify trained.mlbundleInspection does not prove that metadata describes the model. Full verification loads the model and compares its claims. Neither operation establishes authorship or training history.
Export locks the installed versions of pydantic, the model libraries (and pandas
for scikit-learn pipelines that select columns by name) and their dependencies as pylock.toml for CPython with the GIL on Linux x86_64 and
aarch64 and on Windows amd64. mlbundle itself is pinned by the manifest's
writer_version.
A runtime installs that lock plus mlbundle==writer_version in an isolated
environment, for example uv pip install -r pylock.toml, and calls
check_environment before loading. Installing a lock runs its packages' code:
the runtime decides which packages and indexes it trusts. See
the lock contract.
| Library | Storage | Support boundary |
|---|---|---|
| scikit-learn | skops | Closed fitted classifier/regressor families and supported transformers. |
| XGBoost | Native UBJSON | Exact single-target tree wrappers and closed objectives. |
| CatBoost | Native CBM | Exact numeric, single-target symmetric classifiers/regressors. |
| ONNX | Original ONNX bytes | Self-contained tabular models with supported signatures; no conversion. |
| Composite pipelines | skops + UBJSON or CBM | Supported sklearn preprocessing with an XGBoost/CatBoost final estimator. |
Custom types fail at export. Sklearn and CatBoost own their class labels; XGBoost can bind declared labels to a native copy. ONNX has no visualization structure, and all-sklearn Pipeline structure is null. The adapter contracts define the exact support limits.
Package versions and wire-format versions are independent. Format 1 is not frozen;
incompatible schema shapes, bundles without pylock.toml and 0.3.0 bundles of
pipelines selecting columns by name whose lock lacks pandas require re-export. The security design has not had an
independent review; do not accept bundles from untrusted parties.
The reader bounds ZIP/JSON data, verifies integrity and uses closed native loaders. Hashes detect corruption, not deliberate tampering. Native parsers and inference still need process isolation and application-owned resource limits. Read SECURITY.md for guarantees, limits and private reporting.
Training rows stay local by default, but feature names, categories, labels and learned structure can reveal information. Schemas, environment records and locks remain author declarations. Model training, hosting, signing and conversion are outside the library's scope.
The documentation site includes installation, adapter guides, record prediction and API reference. It renders the normative contracts from this repository unchanged:
- Bundle format and verification
- Schema, mappings and explicit samples
- Supported adapters and structure
- Sklearn state and trusted types
- Composite pipelines
- Runtime environment lock
- ONNX layouts and bounds
The playground provides editable examples and inspection of the
generated .mlbundle file. It runs scikit-learn examples in the browser and
inspects uploaded files without loading their models.
The Python library uses uv and Python 3.14:
uv python install 3.14
uv sync --locked --all-extras
uv run pre-commit install
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest
uv buildTests use the installed package and enforce 80% branch-inclusive coverage. CI validates the built wheel on Windows and Linux. Read AGENTS.md for contribution rules and publishing for the automatic release workflow.
The landing, docs and playground use Vite+ and a
pnpm workspace. With vp installed, run from the repository root:
vp install
vp run check
vp run buildStart a site with vp run dev:landing, vp run dev:docs or
vp run dev:playground. Their local ports are 4340, 4341 and 10021 respectively.
Each site also accepts vp run dev, vp run build and vp run preview from its
directory. Astro sites run their framework CLI through vp exec; the playground
uses Vite+ directly. vp run check runs frontend lint, landing formatting, all
three sites' type checks, and the docs and playground unit tests. Runtime and
browser tests live in playground/.
Actions runs Python and frontend checks on pushes and PRs to main and develop.
After all checks pass on main, it deploys the three tested site builds to
Cloudflare. Manual CI Pipeline runs on main use the same gates and can
redeploy without publishing Python. A change to project.version on main
automatically starts the PyPI workflow after its version and branch preflight.
See website deployment and publishing.
MIT. The design takes ideas from MLflow's versioned manifest and native storage, not its code.
