xrun: smoke-test on a free Kaggle GPU, train on vast.ai, let the agent read the metrics A developer built xrun, a Rust CLI with a Python Textual TUI that automates GPU training runs across six vendors — vast.ai, Kaggle, Lightning AI, Google Colab, SSH servers and local machines — from a single YAML manifest. The tool rents instances, uploads data, launches training, follows stages and metrics, pulls checkpoints and destroys instances on completion, failure, idle timeout, metric plateau or cost/time limits, and ships a Claude Code/Codex skill so an agent can drive the same commands. A training-script hook writes events.jsonl and metrics.jsonl for a background poller, with a separate watchdog run every five minutes to catch a dead poller while an instance is still billing. I train 3D computer vision models, and classical ones at hackathons. For a long time every run came with the same chores: vastai create instance , scp the data up, start the script, check on it, scp the checkpoint back, remember to destroy the instance. Kaggle had its own version of that with kaggle kernels push . I wanted to hand the whole loop to an agent: launch on whichever platform fits, smoke-test on a free Kaggle GPU before paying for the real training, and collect metrics the same way everywhere, so the agent can read them and decide what to do next. That became xrun, a Rust CLI with a Python Textual TUI on top. One YAML manifest describes a run: where the GPU comes from, what to upload, what to run and what to bring back. xrun rents the instance, uploads the data, starts training, follows stages and metrics, pulls the checkpoints and destroys the instance when it's over. It supports six vendors: vast.ai, Kaggle, Lightning AI, Google Colab, your own server over SSH, and the local machine. name: resnet50 baseline vendor: vast vast | kaggle | lightning | colab | ssh | local vast: image: pytorch/pytorch:2.4.1-cuda12.1-cudnn9-devel gpu: { type: "RTX 4090", count: 1 } price: { max per hour: 0.55 } data: - src: data/train.h5 dst: /workspace/data/train.h5 run: cmd: python train.py args: --lr: 5e-4 --epochs: 30 artifacts: patterns: "checkpoints/best .pt" policy: on idle minutes: 30 no output for 30 minutes: destroy early stop: metric plateau: pull the best, then destroy metric: val f1 patience: 5 And the day-to-day loop: xrun launch exp/resnet50.yaml --detach --max-cost 5 xrun events