A modern CLI tool for building, running, and managing ML.NET models with filesystem-based MLOps.
MLoop fills the gap left by the discontinued ML.NET CLI, providing a simple yet powerful command-line interface for the entire machine learning lifecycle - from training to prediction to deployment.
Core Mission: Enable anyone to achieve production-quality ML models with minimal coding and ML expertise, while maintaining the flexibility for advanced customization.
1. Convention Over Configuration
- Filesystem-based contracts: Just drop your CSV in
datasets/, train, and predict - Zero configuration required: Intelligent defaults make it work immediately
- Git-friendly MLOps: All experiment metadata tracked as files, not databases
- No daemon, no servers: Each command runs independently and exits cleanly
2. AutoML-First, Minimal Coding
- One command to train:
mloop train datasets/train.csv --label price --time 60 - Automatic model selection: MLoop's trainer search finds the best ML.NET algorithm and settings for your data
- No feature engineering required: Optional FilePrepper integration for complex preprocessing
- Production-ready in minutes: From CSV to deployed model in 3 commands
3. Extensibility Through Dynamic Scripting
- Optional customization: Start simple, add complexity only when needed
- Code-based hooks: Inject custom logic at any pipeline stage (pre-train, post-train, etc.)
- Custom metrics: Define business-specific optimization objectives
- C# scripting: Full IDE support with IntelliSense and debugging
- Zero overhead: <1ms performance impact when extensions aren't used
4. Minimum Cost, Maximum Value
- Development cost: 3-command workflow vs traditional multi-week ML projects
- Knowledge cost: AutoML handles algorithm selection automatically
- Operational cost: Filesystem-based MLOps eliminates infrastructure complexity
- Time cost: From CSV to production model in minutes, not days
| Traditional ML Workflow | MLoop Workflow |
|---|---|
| Weeks: Data prep → Feature engineering → Model selection → Training → Deployment | Minutes: init → train → predict |
| Requires: Python, Jupyter, scikit-learn, pandas, Docker, Kubernetes | Requires: .NET CLI only |
| Expertise: ML engineering, DevOps, data science | Expertise: Basic CSV understanding |
| Cost: Engineering team, infrastructure, training | Cost: Developer time only |
| Result: Custom solution, high maintenance | Result: Production-ready, git-trackable MLOps |
- 15 ML Task Types: Full ML.NET coverage — classification, regression, clustering, ranking, forecasting, recommendation, anomaly detection, NLP, image, and more
- AutoML Training: Automatic model selection — a trainer search over ML.NET's trainers, scored over folds
- Multi-Model Projects: Manage multiple models (churn, revenue, etc.) in one project
- On-Demand DL Runtimes:
mloop runtime install torchdownloads native runtimes only when needed - Smart Predictions: Production model auto-discovery and batch processing
- Filesystem MLOps: Git-friendly experiment tracking (no database required)
- Fast Preprocessing: Integrated FilePrepper (20x faster than pandas)
- Extensibility: Code-based hooks and custom metrics
- Zero Config: Works immediately with intelligent defaults
- Encoding Detection: Automatic CP949/EUC-KR to UTF-8 conversion for Korean text
- Self-Update: Update to latest version with
mloop update
| Category | Tasks | Runtime |
|---|---|---|
| Tabular | Binary Classification, Multiclass Classification, Regression | Built-in |
| Unsupervised | Anomaly Detection (PCA), Clustering (K-Means) | Built-in |
| Ranking | Learning to Rank (LightGBM, FastTree) | Built-in |
| Time Series | Forecasting (SSA), Time Series Anomaly (SR-CNN) | Built-in |
| Recommendation | Matrix Factorization (collaborative filtering) | Built-in |
| Deep Learning | Image Classification, Object Detection, Text Classification, NER, Sentence Similarity, QA | On-demand (mloop runtime install) |
Image Classification (directory loader → TensorFlow transfer learning,
tfruntime) and Object Detection (COCO/YOLO loader → AutoFormerV2,torchruntime) are wired end-to-end through the runtime gate. Install the matching runtime to train. Both support the fulltrain → predict → evaluateloop over image directories: image classificationevaluatereports multiclass accuracy, object detectionpredictemits per-image detections (label + score + box) as JSON andevaluatereports mAP (map_50/map_50_95) via ML.NET's built-in object-detection evaluator.
What the text and vision trainers can and cannot do — measured. Text classification, sentence similarity, NER and question answering each train, predict and evaluate end to end. Their limits come from the ML.NET trainers underneath, so they are stated here rather than discovered:
- The text trainers start from English pretrained models (NAS-BERT, RoBERTa). Other languages train, but from English sub-word pieces.
- NER tags whole words — one label per space-separated word. Where words carry attached particles,
as in Korean, an entity often starts or ends inside a word: on a Korean time-expression set, 67% of
entity spans did, so the model learns
8월20일부터as a date as a whole. - Question answering writes its answer one byte per character, so an answer in Korean, Chinese,
Japanese or Cyrillic comes out with those characters missing and never matches exactly
(dotnet/machinelearning#7749). Training says
how many answers are affected and where
exact_matchstops. Use it for Latin-script text. - Object detection on a CPU is slow: AutoFormerV2 took 47 minutes per epoch over 16 images of 1920×1080 (ten epochs by default). It trains on one thread, as a guard against an intermittent native crash in TorchSharp; that guard is part of the cost.
- The pretrained weights download on first use and ML.NET's downloader can fail on large files (dotnet/machinelearning#6980); training then prints the file, the folder it is looked for in, and the command that fetches it.
.NET Tool (Recommended for .NET developers):
dotnet tool install --global mloopThe package ID on NuGet.org is mloop. This is a
framework-dependent tool, so it reuses your installed .NET runtime — ideal for slim Docker images:
RUN dotnet tool install --global mloop
ENV PATH="$PATH:/root/.dotnet/tools"Standalone Binary:
Download from GitHub Releases and place in your PATH.
| Platform | Binary |
|---|---|
| Windows x64 | mloop-win-x64.exe |
| Linux x64 | mloop-linux-x64 |
| macOS x64 | mloop-osx-x64 |
# Self-update to latest version anytime
mloop updateTime-series training needs an OpenMP runtime on Linux. forecasting and time-series-anomaly
compute an FFT through ML.NET's MKL native library, which on Linux is published without the OpenMP
runtime it links against — it expects the system to provide one. Everything else, including
mloop detect, works with no extra setup; those two report what is missing if it is not there.
# Debian/Ubuntu — LLVM's runtime is ABI-compatible with the one MKL asks for
sudo apt-get install -y libomp5
sudo ln -sf /usr/lib/x86_64-linux-gnu/libomp.so.5 /usr/lib/x86_64-linux-gnu/libiomp5.soOn macOS running Apple silicon these two are unavailable: ML.NET publishes no osx-arm64 build of
that library, so there is nothing to install. The rest of MLoop, mloop detect included, is
unaffected. Containers generated by mloop docker already install this for you.
# 1. Initialize project
mloop init my-ml-project --task regression
cd my-ml-project
# 2. Add training data
cp ~/data/train.csv datasets/train.csv
# 3. Train model (60 seconds)
mloop train datasets/train.csv --label price --time 60
# 4. Add the rows you want scored, then predict
cp ~/data/new.csv datasets/predict.csv
mloop predict
# ✅ Output: predictions/default-predictions-TIMESTAMP.csvThat's it! Your model is trained and ready to use.
mloop init <project> --task <type> # Initialize ML project
mloop train <data> --label <label> [options] # Train with AutoML
mloop predict [model] [data] # Run predictions
mloop list [--json] # View experiments
mloop promote [exp-id] [--latest|--best] [--json] [--decide-only] # Promote to production (auto-select newest/best; --decide-only reports the pick without moving the pointer)
mloop rollback [--to <exp-id>] [--json] # Production back to the experiment it held before
mloop export onnx [exp-id] [--data <file>] [-o <dir>] [--verify] # Model as ONNX + manifest, for runtimes without ML.NET
mloop detect <data> [--column <col>] # One-shot TS-anomaly detection with control limits (no training)
mloop evaluate <model> <test> <label> # Evaluate performance
mloop info <data> # Dataset profiling with encoding detection
mloop analyze profile <data> # Column types, null %, cardinality, constant columns
mloop analyze correlation <data> # High-correlation pairs and multicollinearity
mloop analyze importance <data> -l <col> # Feature importance ranking (requires label)
mloop analyze outliers <data> # Outlier count, rate, isolation-forest threshold
mloop analyze distribution <data> # Skewness, kurtosis, normality tests
# add --json to any for structured, LLM-consumable output
mloop validate # Validate mloop.yaml configuration
mloop prep plan --set <type[:method]> [--columns ...] [--json] # Declare prep step (policy only, no data change)
mloop prep run [options] # Run preprocessing pipeline
mloop features select --drop/--keep <cols> [--reset] [--json] # Declare feature include/exclude (policy only)
mloop compare <exp1> <exp2> # Compare experiment metrics
mloop compare --metrics-file <path> --json # Provided-state: rank/select best from supplied metrics (no local .mloop/; distributed consumers)
mloop serve # Start REST API server
mloop docker # Generate Docker deployment files
mloop update # Self-update to latest version# Multi-file training - auto-merge same-schema CSVs
mloop train --data file1.csv file2.csv --label Target --task regression
# Auto-discovery merge from datasets/ folder
mloop train --auto-merge --label Target --task regression
# Handle Korean/Chinese encoded files (CP949, EUC-KR auto-converted)
mloop train korean_data.csv --label label --task regression
# Output: [Info] Converted CP949 → UTF-8
# Drop missing label values (default for classification)
mloop train data.csv --label label --task binary-classification --drop-missing-labels
# Machine-readable training: stdout becomes an NDJSON event stream
# (phase / trial / warning / result / error — one JSON object per line, flushed per event)
mloop train data.csv --label label --task binary-classification --time 60 --jsonImage classification reads a directory whose subfolders are class labels (folder name = label):
mloop init vision --task image-classification
cd vision
# Lay out images: datasets/images/<class>/<files>
# datasets/images/OK/ img001.jpg ...
# datasets/images/NG/ img101.jpg ...
mloop runtime install tf # one-time TensorFlow CPU runtime (~182MB; Windows, Linux x64)
mloop train --task image-classification # auto-detects datasets/images/Supported extensions: .jpg .jpeg .png .bmp .gif. The trainer uses TensorFlow transfer learning,
so the tf runtime must be installed first. After training, mloop predict classifies new images
and mloop evaluate exp-001 datasets/images reports multiclass accuracy over a labelled directory.
Object detection reads a directory holding a COCO-format annotations file plus the referenced images:
mloop init detector --task object-detection
cd detector
# Edit datasets/coco/annotations.json (COCO format), with images alongside it:
# datasets/coco/
# ├── annotations.json # images[], annotations[] (bbox=[x,y,w,h]), categories[]
# ├── img001.jpg
# └── img002.jpg ...
mloop runtime install torch # one-time TorchSharp (libtorch CPU) runtime
mloop train --task object-detection # auto-detects datasets/coco/The COCO bbox ([x, y, width, height], absolute pixels) is converted to the x0 y0 x1 y1 order
the AutoFormerV2 trainer expects. Conventional annotation file names — annotations.json and
_annotations.coco.json (Roboflow) — are auto-detected; file_name is resolved relative to the
annotations file. YOLO datasets (images/ + labels/*.txt) are also auto-detected. The trainer
uses the torch runtime, which must be installed first.
After training, predict and evaluate run over the same directory layout:
mloop predict datasets/yolo # → predictions/<model>-detections-<ts>.json (label + score + box per image)
mloop evaluate exp-001 datasets/yolo # → mAP (map_50 / map_50_95)Full documentation: docs/GUIDE.md
MLoop uses Convention over Configuration - intelligent defaults that work out of the box.
my-ml-project/
├── .mloop/ # Project marker (like .git)
├── mloop.yaml # Project configuration
├── datasets/ # Training data → train.csv, predict.csv
├── models/
│ └── {model-name}/ # Per-model namespace (default, churn, revenue, etc.)
│ ├── staging/ # Experiments (exp-001, exp-002, ...)
│ └── production/ # Promoted model
└── predictions/ # Outputs (timestamped CSVs)
# Train multiple experiments
mloop train datasets/train.csv --label price --time 60 # exp-001
mloop train datasets/train.csv --label price --time 120 # exp-002 (better)
mloop train datasets/train.csv --label price --time 180 # exp-003 (best!)
# Review experiments
mloop list
# Output:
# ┌─────────┬───────────┬─────────────┬──────────────┐
# │ ID │ Status │ Best Metric │ Stage │
# ├─────────┼───────────┼─────────────┼──────────────┤
# │ exp-003 │ Completed │ 0.9523 │ ★ Production │
# │ exp-002 │ Completed │ 0.9401 │ Staging │
# │ exp-001 │ Completed │ 0.9278 │ Staging │
# └─────────┴───────────┴─────────────┴──────────────┘
# Use production model
mloop predict # Auto-uses exp-003Manage multiple models within a single project - perfect for complex ML systems.
# Train different models for different targets
mloop train --name churn --label Churned --task binary-classification
mloop train --name revenue --label Revenue --task regression
mloop train --name ltv --label LifetimeValue --task regression
# Each model has independent experiments
mloop list --name churn # Shows churn experiments only
mloop list --name revenue # Shows revenue experiments only
mloop list # Shows all experiments across models
# Promote and predict per model
mloop promote exp-001 --name churn
mloop predict data.csv --name churn
# Serve multiple models via API
mloop serve
# All endpoints except /health require a JWT: mint one with `mloop token`
# (add --role admin for write endpoints) and send it as `Authorization: Bearer <token>`.
# A programmatic caller must issue/refresh the token itself — the server has no token endpoint.
export MLOOP_TOKEN=$(mloop token --quiet)
# GET /predict?name=churn -H "Authorization: Bearer $MLOOP_TOKEN"
# GET /predict?name=revenue -H "Authorization: Bearer $MLOOP_TOKEN"Directory Structure with Multiple Models:
models/
├── churn/
│ ├── staging/exp-001/
│ └── production/
├── revenue/
│ ├── staging/exp-001/
│ └── production/
└── default/
└── staging/exp-001/
The section above shows the ones a first project uses. This is the whole surface — mloop <command> --help has the options for any of them.
| Command | What it does |
|---|---|
init |
Initialize a new ML project with multi-model support |
train |
Train a model using AutoML |
predict |
Make predictions with a trained model |
evaluate |
Evaluate model performance on test data |
promote |
Promote an experiment to production |
rollback |
Return production to the experiment it held before |
threshold |
Choose a binary classifier's decision threshold for a target precision, or show or remove it |
calibrate |
Fit a classifier's probability calibration on labeled rows without retraining, or show or remove it |
export |
Export a model as ONNX with a manifest, for runtimes without ML.NET (export onnx [--verify]) |
list |
List all experiments |
compare |
Compare experiments side by side |
status |
Show project status at a glance |
info |
Display dataset profiling information |
analyze |
Read-only EDA aspects: profile, correlation, importance, outliers, distribution |
detect |
One-shot time-series anomaly detection, no training required |
preprocess |
Run the preprocessing scripts in .mloop/scripts/preprocess/, or the rule-discovery workflow |
prep |
Data preprocessing tools |
features |
Feature selection policy, written to mloop.yaml |
validate |
Validate project configuration |
logs |
View prediction logs |
feedback |
Manage prediction feedback for model monitoring |
sample |
Sample data from CSV files or prediction logs |
trigger |
Evaluate retraining triggers for models |
drift |
Compare recent inputs and answers with the training data (PSI) |
serve |
Start the REST API server for model serving |
token |
Issue a JWT bearer token for the local serve API |
docker |
Generate Docker configuration for model deployment |
new |
Generate new project components (hooks) |
runtime |
Manage on-demand ML runtime downloads |
encoder |
Install a catalog encoder (install dinov2-small), add your own, list or remove the encoders embed runs |
embed |
Embed images as vectors with a pretrained encoder |
update |
Check for and install CLI updates |
Every command that produces a result worth consuming programmatically accepts --json. Two rules
hold across all of them, so a script never has to special-case a command:
stdout carries the answer, stderr carries everything else. In --json mode, progress, tables and
diagnostics go to stderr; stdout receives exactly one document (or, for mloop train --json, one
newline-delimited JSON event per line). This holds at every exit, including the ones that fail:
mloop list --json > result.json # a document, even when there are no experiments
mloop list --bogus --json > result.json # Error: unrecognized option — stdout still gets {"error": "..."}, usage goes to stderrAn unrecognized option is reported as one, rather than being read as a value. Pass a value that
really does begin with two dashes after a bare --:
mloop promote --lastest # Error: '--lastest' is not an option of 'promote'
mloop promote -- --lastest # '--lastest' is the experiment idExit codes.
| Code | Meaning |
|---|---|
0 |
The command did what was asked. Also returned for a reported skip — mloop evaluate with no production model writes the reason to stderr, emits its usual document with nothing measured, and exits 0, because having nothing to evaluate is an outcome rather than a failure. Detect it structurally: experimentId is null. |
1 |
The command could not do what was asked: a rejected command line, a missing model or file, a failed validation, an unusable schema. --json consumers get the reason on stdout as {"error": "..."} unless the command emits a richer document that already describes the failure. |
--help and --version are answers, not failures: they print to stdout and exit 0.
- User Guide - Complete usage guide with concurrent job management
- Using MLoop from AI agents - MCP (mloop-mcp), CLI
--jsonand HTTP - Examples - Sample workflows
- Job Management Scripts - Sequential and parallel execution tools
- Philosophy & Design - Core mission and design principles
- Architecture - System design and technical decisions
- Prediction response contract - What
predict --jsonandPOST /predictreturn, field by field - Embedding MLoop.Core - In-process consumers: DL-weight pruning recipe & security floor
- Contributing - Contribution guidelines and development workflow
- Releasing - Maintainer procedure: walk the new-user path on the built binary, run suites serially, confirm CI on the pushed commit, then publish
With Microsoft discontinuing ML.NET CLI and Model Builder updates, MLoop provides:
- ✅ Active Development - Modern tooling with latest ML.NET 5.0
- ✅ Production Ready - From prototyping to deployment
- ✅ Git Friendly - All state as files, no database
- ✅ Extensible - Hooks and custom metrics
- ✅ Fast - FilePrepper integration (20x speedup)
- .NET 10.0+
- ML.NET 5.0.0+
MIT License - Built on ML.NET by Microsoft.