ML Experiment Tracking skill for Codex

Every number traceable to one reproducible run. Codex instruments training so each run carries its git commit, data version, resolved config and environment alongside metrics and artifacts. It compares runs on the same data across seeds, registers models in MLflow with challenger and champion aliases or W&B artifacts, and asks before moving the alias production loads. Offline modes keep logging working inside a sandbox.

  • AI & LLM Engineering
  • $ml-experiment-tracking
  • 158 lines
  • 2 files
  • ~1,700 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- ml-experiment-tracking

Installs to ~/.agents/skills/ml-experiment-tracking/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $ml-experiment-tracking, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Sets up experiment tracking and model registries with MLflow and Weights & Biases - logging params, metrics, artifacts, dataset versions and git commits, autologging, comparing runs, registering models with aliases such as champion, loading by models:/ URIs, and offline mode for sandboxes and air-gapped machines. Use when asked to add MLflow or W&B to a training script, compare experiments, make results reproducible, promote a model to production in a registry, or self-host a tracking server.

Use it when

  • Training scripts produce results nobody can reproduce later
  • You need a model registry with a clear promotion and rollback path
  • Experiments must be compared fairly across configs and seeds

Use something else when

How to check the result

Checking out a run's logged commit with its logged config and data version reproduces the metric within seed noise, and the champion alias loads in a clean environment.

The mistake it prevents

Comparing runs trained on different data versions or single seeds and declaring a winner from noise.

~/.agents/skills/ml-experiment-tracking/SKILL.md

--- name: ml-experiment-tracking description: Sets up experiment tracking and model registries with MLflow and Weights & Biases - logging params, metrics, artifacts, dataset versions and git… license: MIT ---

Done well means: any result in a report can be traced to one run that records the code commit, data version, config, environment and metrics; models are promoted by moving an alias after a recorded comparison; and tracking never blocks or crashes training.

Decide the tool

SituationDefault
Self-hosted, open source, model registry and serving hooks wantedMLflow
Team already on W&B, rich dashboards, sweeps, reportsWeights & Biases
Both appear in the repoKeep what AGENTS.md, requirements or CI already use
Network off (Codex sandbox, air-gapped)MLflow with a local sqlite:/// or ./mlruns store, or WANDB_MODE=offline

Never put tokens (WANDB_API_KEY, MLflow credentials) in code or committed config; read them from the environment.

What every run must record

- [ ] git commit SHA and dirty flag
- [ ] full resolved config (hyperparameters, seeds, model architecture)
- [ ] dataset identifier + version or content hash, and split definition
- [ ] environment: python, key library versions, hardware
- [ ] metrics per step/epoch for train and validation, final test metrics once
- [ ] artifacts: best model, plots (confusion matrix, curves), eval predictions sample
- [ ] tags: purpose ("baseline", "ablation-lr"), owner, ticket

MLflow

import subprocess, mlflow

mlflow.set_tracking_uri("sqlite:///mlflow.db")       # or http://mlflow.internal:5000
mlflow.set_experiment("churn-model")

sha = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
dirty = bool(subprocess.check_output(["git", "status", "--porcelain"], text=True).strip())

with mlflow.start_run(run_name="hgb-lr0.05") as run:
    mlflow.set_tags({"git_sha": sha, "git_dirty": str(dirty), "purpose": "baseline"})
    mlflow.log_params(cfg)                            # flat dict of scalars
    mlflow.log_param("data_version", data_hash)
    for epoch, m in enumerate(history):
        mlflow.log_metrics({"train_loss": m.train, "val_auc": m.val_auc}, step=epoch)
    mlflow.log_metric("test_auc", test_auc)
    mlflow.log_figure(fig, "plots/roc.png")
    mlflow.sklearn.log_model(pipe, name="model", input_example=X_val.head(5),
                             registered_model_name="churn-model")
  • MLflow 3 uses name= for log_model; older versions use artifact_path=. Match the installed version.
  • mlflow.autolog() covers sklearn, XGBoost, LightGBM, PyTorch Lightning and Transformers quickly; still add the git, data and purpose tags manually.
  • Local UI: mlflow ui --backend-store-uri sqlite:///mlflow.db --port 5000.
  • Team server: mlflow server --backend-store-uri postgresql://... --artifacts-destination s3://bucket/mlflow --host 0.0.0.0 behind an authenticating proxy; the open-source server has minimal built-in auth.

Registry and promotion

Stages (Staging/Production) are deprecated; use aliases.

from mlflow import MlflowClient
client = MlflowClient()
client.set_registered_model_alias("churn-model", "challenger", version=7)

# after comparison on the frozen holdout and user approval:
client.set_registered_model_alias("churn-model", "champion", version=7)

model = mlflow.pyfunc.load_model("models:/churn-model@champion")

Moving champion changes what production loads: confirm with the user and record the comparison run IDs in the version description before moving it. Rollback is moving the alias back to the previous version.

Weights & Biases

import wandb

run = wandb.init(project="churn-model", job_type="train", config=cfg,
                 tags=["baseline"], notes="HGB with target encoding")
for epoch, m in enumerate(history):
    run.log({"train/loss": m.train, "val/auc": m.val_auc, "epoch": epoch})
run.summary["test_auc"] = test_auc

data = wandb.Artifact("churn-data", type="dataset", metadata={"rows": len(df)})
data.add_file("data/train.parquet")
run.log_artifact(data)                     # versioned v0, v1, ... by content

model_art = wandb.Artifact("churn-model", type="model")
model_art.add_file("model.joblib")
run.log_artifact(model_art, aliases=["candidate"])
run.finish()
  • W&B records the git commit automatically when run inside a repo; confirm in the run overview.
  • WANDB_MODE=offline writes to ./wandb; sync later with wandb sync wandb/offline-run-* (needs network and approval). WANDB_MODE=disabled for tests.
  • Use run.use_artifact("churn-data:v3") in training so lineage links data to model.
  • Sweeps: wandb sweep sweep.yaml then wandb agent <id>; cap runs with count.

Framework hooks

FrameworkHook
Hugging Face Trainer / TRLreport_to="mlflow" or "wandb" in TrainingArguments
PyTorch LightningMLFlowLogger / WandbLogger
Kerasmlflow.autolog() or WandbMetricsLogger
Plain PyTorchExplicit log_metrics every N steps (see $pytorch-training)

Comparing runs

runs = mlflow.search_runs(experiment_names=["churn-model"],
                          filter_string="tags.purpose = 'baseline' and metrics.val_auc > 0.8",
                          order_by=["metrics.val_auc DESC"])
print(runs[["run_id", "params.learning_rate", "metrics.val_auc", "tags.git_sha"]].head(10))
  • Compare on the same split and data version; a filter on params.data_version avoids apples-to-oranges tables.
  • Differences smaller than seed-to-seed variance are noise: run 3 seeds before claiming a win.
  • Choose on validation, report test once for the chosen run.

Gotchas

  • Logging inside the inner loop every step with .item() slows GPU training; log every N steps.
  • Under DDP, only rank 0 should create the run and log, or you get N duplicate runs.
  • log_params values are truncated strings; store large configs as an artifact (mlflow.log_dict(cfg, "config.json")).
  • Tracking failures must not kill training: wrap remote logging in retries, or log offline and sync.
  • Artifacts can contain customer data (prediction samples). Check where the artifact store lives and who can read it before logging raw rows.

Verification

  • Open a run and reproduce it: check out the logged SHA, load the logged config and data version, retrain, and get the same metric within seed noise.
  • models:/<name>@champion loads in a clean environment and predicts on input_example.

Report template

**Experiment:** churn-model (MLflow, sqlite store) | **Runs compared:** 14, data_version a3f9c1
**Winner:** run 8e21... hgb lr=0.05 depth=6: val AUC 0.871 +/- 0.004 (3 seeds), test AUC 0.866
**Registry:** churn-model v7 -> alias challenger; champion move awaiting approval
**Repro check:** retrained from logged SHA/config: val AUC 0.869

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "ML Experiment Tracking"
  short_description: "Every number traceable to one reproducible run"
  default_prompt: "$ml-experiment-tracking Add MLflow tracking to train.py and register the best model"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem