
How to build the record, train, evaluate, deploy, retrain loop for an SO-100 arm: real LeRobot commands, DAgger-style failure collection, and what to automate first.
What you need to know
- •A single fine-tune is not a product. The unit of progress is one turn of a loop: record, train, evaluate, deploy, collect failures, retrain.
- •Expert-only data compounds error: a policy with error rate epsilon on the expert's states can make up to T squared times epsilon mistakes over a T-step episode under its own distribution.
- •DAgger fixes the data distribution, not the model: run your policy, correct the states it visits, retrain on the union of everything.
- •LeRobot on main ships this as a CLI. lerobot-rollout has a dagger strategy; lerobot-edit-dataset merges, splits and deletes episodes.
- •Automate the deterministic parts first: dataset revisions, GPU lifecycle, checkpoint upload, run metadata. Keep rollout scoring manual.
- •On AY-Robots the training and inference legs are automated: a form picks model plus dataset, the backend rents a GPU by required VRAM, and /api/inference/pod serves the policy back to the arm.
One fine-tune is not a system
Most people who train a first policy on an SO-100 do the same thing: record forty episodes, run a training job, load the checkpoint, watch the arm do something roughly correct, stop. Build that demo. But it is a single point measurement, and you want a slope.
The slope comes from repetition with feedback: deploy, watch it fail in a specific way, record data about that failure, retrain, deploy again. Each turn should cost less than the last, because the pure mechanism has been automated away. The rest of this is about which parts are mechanism.
A turn is complete when a checkpoint that did not exist yesterday is running on the arm and you can say in one sentence what it does better than the last. If you cannot say that sentence, you did not close the loop, you spent GPU money.
What a full turn contains
Six stages. The last column matters most: where a stage goes wrong decides whether automating it saves time or hides a bug.
| Stage | What happens | Effort per turn | Where it goes wrong |
|---|---|---|---|
| Record | Teleoperate, save episodes with camera and joint streams | 30 to 120 min | Inconsistent resets, drifting cameras |
| Curate | Delete bad takes, fix task strings, merge earlier rounds | 10 to 30 min | Deleting the episodes with the hard cases |
| Train | Fine-tune a base VLA, or train a small policy from scratch | 2 to 6 h GPU | Wrong format version, no seed, checkpoints overwritten |
| Evaluate | Run the checkpoint under a fixed protocol and score it | 20 to 60 min | No protocol, so scores do not compare across weeks |
| Deploy | Serve the policy and let it run the task for real | Minutes | Inference too far from the servos, control loop stalls |
| Collect failures | Record what went wrong, with human corrections | 20 to 60 min | Recording only successes, which teaches nothing |

Why the loop exists: compounding error
Imitation learning from demonstrations is supervised learning on states the expert visited. At run time the policy visits states it causes. The moment it errs slightly it is somewhere the expert never was, and errors compound. Ross, Gordon and Bagnell formalized it: a classifier erring with probability epsilon under the expert's state distribution can make as many as T squared times epsilon mistakes over a T-step horizon under its own.
Naive behavior cloning: cost grows quadratically in episode length T. DAgger, arXiv 1011.0686 (v1 November 2010, v3 March 2011): with iterations N on the order of uT, some learned policy costs at most the expert's cost plus u times T times the training loss, plus a constant. Linear in T. Not a better network, just training on the states the learner visits.
Practically: your first thirty episodes teach the happy path, the next thirty teach almost nothing. What moves the number is data in the states your checkpoint drifts into, which you only find by running it. That is why the loop closes, and why how you collect data beats how much you have.
Turn one: record the seed dataset
Everything starts with a LeRobot dataset. The desktop client from the download page records one out of a teleoperation session, and the recording tutorial walks it end to end. The commands below are from the LeRobot cheat sheet on main, checked 2026-08-24.
- 1Find the ports
Once per arm. It asks you to unplug the USB cable, then prints the port that disappeared.
bashpip install 'lerobot[training]' lerobot-find-port lerobot-find-cameras - 2Calibrate leader and follower
Put every joint roughly mid-range first. The
--robot.idis the key LeRobot uses to find the calibration file later, so keep it stable.bashlerobot-calibrate \ --robot.type=so101_follower \ --robot.port=/dev/ttyACM0 \ --robot.id=my_follower_arm - 3Record the seed episodes
Right arrow saves, left arrow deletes the take and retries, Escape stops and encodes. Use the left arrow generously: a bad take costs nothing now and a confusing loss curve later.
bashlerobot-record \ --robot.type=so101_follower \ --robot.port=/dev/ttyACM0 \ --robot.id=my_follower_arm \ --robot.cameras="{ top: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30}, wrist: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30} }" \ --teleop.type=so101_leader \ --teleop.port=/dev/ttyACM1 \ --teleop.id=my_leader_arm \ --dataset.repo_id=${HF_USER}/pick_block_r1 \ --dataset.num_episodes=40 \ --dataset.single_task="put the red brick in a bowl" \ --display_data=true
The most common cause of a policy that works in exactly one setup: every episode started from an identical scene. Vary object position and lighting, keep your hands out of frame. See policy only works in one setup.

Turn one: train, and the format trap
Now pick a model. The policies page lists the five trainable here, and the head-to-head pages cover the pairs people agonize over. For a first loop, start cheap: ACT and SmolVLA give turns of about 1 to 3 USD over 2 to 5 hours, so you can afford to be wrong. GR00T N1.7 and Pi0.5 cost 4 to 12 USD over 3 to 6 hours.
| Policy | Params | Min episodes | GPU tier | Per action step | Dataset format |
|---|---|---|---|---|---|
| ACT | ~80 M, from scratch | 50 | RTX 4090 / 24 GB | 20 ms | LeRobot v3.0 |
| SmolVLA | ~450 M | 30 | RTX 4090 / 24 GB | 245 ms | LeRobot v3.0 |
| GR00T N1.7 | ~3 B, ~40 M trained | 50 | A100 / H100 80 GB | 152 ms | LeRobot v2.0 or v2.1 |
| GR00T N1.5 | ~3 B | 50 | A100 / H100 80 GB | 165 ms | LeRobot v2.0 or v2.1 |
| Pi0.5 | ~3 B, PaliGemma backbone | 50 | A100 / H100 80 GB | 485 ms | LeRobot v3.0 |
# LeRobot main, checked 2026-08-24
lerobot-train \
--dataset.repo_id=${HF_USER}/pick_block_r1 \
--policy.type=act \
--output_dir=outputs/train/act_r1 \
--job_name=act_r1 \
--policy.device=cuda \
--policy.repo_id=${HF_USER}/act_pick_block_r1 \
--steps=20000 \
--wandb.enable=true
# resume from a checkpoint
lerobot-train --config_path=${HF_USER}/act_pick_block_r1 --resume=trueA LeRobot v3.0 dataset crashes the GR00T loader, which wants v2.0 or v2.1. NVIDIA ships scripts/lerobot_conversion/convert_v3_to_v2.py in Isaac-GR00T for this, run from its own uv environment. The other direction is python -m lerobot.scripts.convert_dataset_v21_to_v30 --repo-id=<HF_USER/DATASET_ID>. Pin the format on turn one. See dataset rejected as v3.
Evaluate: the step nobody automates, and mostly should not
This is where homemade loops fall apart. LeRobot has lerobot-eval, but read its arguments: it takes --env.type and runs episodes in a simulator. There is no built-in scorer for a real arm, because scoring a real rollout means a human deciding whether the block ended up in the bowl. That judgement carries the information; automating it early means optimizing a proxy.
Standardize the protocol around the judgement instead, so two checkpoints a week apart are comparable. Change it only when you also re-score the old checkpoint.
| Protocol element | Fix it to | Why |
|---|---|---|
| Trial count | 20 trials, always | Below 20, 60 versus 70 percent is noise |
| Start positions | A written list of 5, four trials each | Otherwise the new checkpoint gets easier starts |
| Timeout | Fixed seconds per trial | Succeeding after flailing is not succeeding |
| Outcome classes | success / wrong grasp / missed grasp / stalled / collision | The distribution tells you what to record next |
| Who scores | One person, or video reviewed later | Scoring while operating is not scoring |
| What gets logged | Checkpoint id, dataset revision, protocol version, outcome | Separates data gains from hyperparameter gains |
The classes are the input to the next turn. Ten missed grasps means record wrist-camera data near the object. Ten stalls in one place means record recoveries from there. A success rate cannot tell you which. The failure mode index is organized the same way.
Deploy, and record what the policy does
Deployment used to be an afterthought in LeRobot. On main there is a dedicated CLI, lerobot-rollout, built around pluggable strategies whose names read like a list of what a deployment loop needs. On v0.5.1 you still use lerobot-record with a policy path, which is one reason to say which version you mean.
| --strategy.type | What it does | Use it for |
|---|---|---|
| base | Autonomous rollout, no recording | Checking the checkpoint loads and moves |
| episodic | Episode-oriented recording with reset phases | Running your evaluation protocol |
| sentry | Continuous recording with auto-upload | Long unattended runs |
| highlight | Ring buffer plus a keystroke to save | Rare failures, without hours of video |
| dagger | Human-in-the-loop, DAgger and RaC style | The failure-collection stage |
# LeRobot main: run the checkpoint, take over when it drifts
lerobot-rollout \
--strategy.type=dagger \
--strategy.num_episodes=20 \
--strategy.record_autonomous=true \
--policy.path=${HF_USER}/act_pick_block_r1 \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--teleop.type=so101_leader \
--teleop.port=/dev/ttyACM1 \
--dataset.repo_id=${HF_USER}/pick_block_dagger_r1 \
--dataset.single_task="put the red brick in a bowl"
# slow VLAs: overlap inference with execution instead of pausing at chunk boundaries
lerobot-rollout \
--strategy.type=base \
--inference.type=rtc \
--inference.rtc.execution_horizon=10 \
--policy.path=${HF_USER}/my_pi05_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--task="put the red brick in a bowl" --duration=60The control loop here is 20 to 485 ms per action step, and public-internet round trips on top turn a working policy into a hesitant one. Remote inference suits slow pick-and-place, not fast reactive motion. Real-time chunking helps: Black, Galliker and Levine report precise tasks succeeding with delays above 300 ms, over 30 percent of the prediction horizon, and 20 percent faster motion than synchronous inference. A mitigation, not a licence to put the GPU on another continent. See inference latency and action chunking.
Collect failures: corrections beat more demonstrations
Classic DAgger asks the expert to label every visited state without giving the expert control, which is awkward with a physical arm. HG-DAgger (arXiv 1810.02890, Kelly and colleagues, 2018) has the human take over instead, because labelling without control degrades label quality through perceived actuator lag. That matches a leader-follower rig: the policy drives, you grab the leader when it goes wrong.
- It lands in the states your checkpoint actually reaches, the distribution the DAgger bound cares about
- RaC (arXiv 2509.07953, September 2025) beats prior state of the art on three real bimanual tasks with 10 times less collection time and samples
- Sirius (arXiv 2211.08416) reweights samples by approximated human trust and reports 8 percent higher success in simulation, 27 percent on real hardware
- Each round doubles as an evaluation: the intervention rate is a number you can track
- It needs a human at the arm during the rollout, the expensive resource in the loop
- Segments are short and unbalanced against long autonomous episodes, so naive merging drowns them out
- Takeover moments are noisy: for a moment neither policy nor human is in control
- The leader arm must be present at deploy time, which not every deployment allows
- It biases the dataset toward the failures you happened to see that day
The RaC protocol is worth copying: when failure looks imminent, first rewind the arm to a state the policy handles, then drive the correct continuation. You teach recovery and correction at once, and recovery is what plain demonstration data never contains.
Retrain: merge, prune, rerun
Aggregation in DAgger is literal: train on the union, not the newest batch. LeRobot exposes the set operations as a CLI so you do not write dataset-surgery scripts, which is the glue code Sculley and colleagues warned about in 2015, estimating a mature system might be at most 5 percent machine learning code and 95 percent glue.
- 1Delete the takes you know are bad
Before merging, and write down the indices. Deleting hard episodes because they look messy is the easiest way to stall a loop.
bashlerobot-edit-dataset \ --repo_id ${HF_USER}/pick_block_dagger_r1 \ --operation.type delete_episodes \ --operation.episode_indices "[3, 11, 17]" - 2Merge round one with the intervention data
Merging writes a new dataset instead of mutating the inputs, so earlier rounds stay reproducible.
bashlerobot-edit-dataset \ --new_repo_id ${HF_USER}/pick_block_r2 \ --operation.type merge \ --operation.repo_ids "['${HF_USER}/pick_block_r1', '${HF_USER}/pick_block_dagger_r1']" - 3Hold out a split you never train on
No substitute for real rollouts, but it catches a merge that broke something.
bashlerobot-edit-dataset \ --repo_id ${HF_USER}/pick_block_r2 \ --operation.type split \ --operation.splits '{"train": 0.9, "val": 0.1}' - 4Retrain from base, not last week's checkpoint
Chaining fine-tunes accumulates drift you cannot audit. Going back to base costs a few dollars and keeps every checkpoint traceable to one dataset revision.
bashlerobot-train \ --dataset.repo_id=${HF_USER}/pick_block_r2 \ --policy.type=act \ --output_dir=outputs/train/act_r2 \ --job_name=act_r2 \ --policy.device=cuda \ --steps=20000
The same loop, two ways
All of the above runs on your own GPU or a box you rent. Right choice if you want to modify the trainer, if the data cannot leave the building, or if you have idle cards.
- Install with extras. Since v0.6.0,
pip install lerobotno longer pulls dataset or training dependencies; addlerobot[training]. - Pin a version. v0.6.0 replaced GR00T N1.5 with N1.7, so pin
lerobot==0.5.1for N1.5, and renamed--dataset.vcodecto--dataset.rgb_encoder.vcodec. - Record, train, deploy and aggregate with
lerobot-record,lerobot-train,lerobot-rolloutandlerobot-edit-dataset. - Build your own run registry. Nothing in the CLI records which dataset revision produced which checkpoint.
- Provision and destroy GPUs yourself. An idle rented GPU bills like a busy one.
# minimal run registry: one JSON line per turn
cat >> loop.jsonl <<'EOF'
{"turn": 2, "dataset": "user/pick_block_r2", "policy": "act", "steps": 20000, "ckpt": "outputs/train/act_r2/checkpoints/last", "protocol": "v1", "success": "13/20"}
EOFGR00T's fine-tuning entry point (gr00t/experiment/launch_finetune.py in Isaac-GR00T, a tyro CLI) exposes no seed. LeRobot's default seed is 1000. Comparing two GR00T checkpoints, part of the gap is just the run.
The platform automates the two legs that are pure mechanism and leaves the two that need judgement. The training docs describe the form: model, dataset, hyperparameters. The backend rents a GPU on a spot market by required VRAM and writes checkpoints to object storage. Datasets come from the public directory, a Hugging Face repo id, or your machine.
- Record with the desktop client from the download page, which writes LeRobot format from a teleop session.
- Train from the form. Defaults per policy: ACT batch size 8, learning rate 1e-5, 100000 steps, chunk size 100; GR00T N1.7 batch size 32, learning rate 1e-4, 20000 steps.
- Deploy through
/api/inference/pod, which auto-provisions a GPU pod serving the policy while the local robot client talks to it. - Score rollouts yourself. There is no automatic real-world evaluator here either.
- Drive the same operations from /cli or an agent at /mcp, which makes a scripted nightly turn practical.
Inference pods carry an idle watchdog and destroy themselves after an idle period, so a forgotten pod does not bill silently. That is the difference between a loop you leave running and one you babysit. Costs on the pricing page.
Base checkpoints are the vendors' own: nvidia/GR00T-N1.7-3B, nvidia/GR00T-N1.5-3B and lerobot/pi05_base. ACT has no base model at all. GR00T and Pi0.5 are cloud-only here; SmolVLA and ACT also run locally.
What to automate first, and what to keep manual
General MLOps answered the ordering question. Google's maturity model runs level 0 (all manual, predictions never logged), level 1 (training pipeline automated, triggered by schedule, new data or drift), level 2 (CI/CD for the pipeline code too). The useful part is the order: pipeline automation before deployment automation, logging before either.
| Part of the loop | Automate | Reason |
|---|---|---|
| Dataset revisions | First | Deterministic, and every later question depends on it |
| GPU provisioning and teardown | First | Deterministic, and forgetting it costs money |
| Checkpoint upload and naming | First | Manual naming is where runs get lost |
| Training launch with fixed defaults | First | A form or script beats retyping flags |
| Merging and pruning datasets | Second | Mechanical, but deciding what to prune is not |
| Running the evaluation protocol | Second | Automate the sequence of trials, not the scoring |
| Scoring rollouts | Late or never | This is the signal; a proxy replaces your objective |
| Deciding what to record next | Never | This is the entire skill |

- Same input, same output: automate it now.
- Someone must look at a robot and form an opinion: leave it manual, automate the scaffolding.
- Cheap to run, expensive to get wrong: automate it and log every input, so you can reconstruct it.
- Log the boring metadata on turn one: dataset revision, checkpoint path, protocol version, per-trial outcome. One JSON line, one afternoon saved.
A maturity ladder for one arm on one desk
| Rung | What is automated | Cost per turn | Signal you are here |
|---|---|---|---|
| 0 | Nothing. Commands by hand, folders named final and final2 | A day, mostly bookkeeping | You cannot say which dataset produced the running checkpoint |
| 1 | Training launch, GPU lifecycle, checkpoint storage, dataset revisions | An afternoon, mostly recording and scoring | You can rerun last week's training with one command |
| 2 | Plus dataset merge and prune, a scripted evaluation sequence, a run log | Two hours of human time | You can plot success rate against turn number and trust it |
| 3 | Plus retraining triggered when new intervention data lands | Under an hour, plus scoring | Several tasks or people; coordination costs more than automation |
Most single-arm projects should aim for rung two and stop. If you have not built rung one, train your first policy and then run it on the arm.
Where this breaks down, including here
- There is no real-world evaluator. Not in LeRobot, not here.
lerobot-evalruns in simulation. Every success rate above was counted by a person watching an arm. - Latency is physics. Cloud inference at 152 to 485 ms per action step plus a public round trip suits slow pick-and-place, not fast reactive motion. Only moving compute next to the servos changes it.
- DAgger assumes a competent expert on demand. On a real desk the expert is you at 9 pm, and the quality of your 9 pm corrections is now in the dataset.
- Format churn is real. GR00T needs v2.1 while Pi0.5 and SmolVLA want v3.0, and v0.6.0 renamed flags older tutorials still print.
- Automating too early hides regressions. A loop that retrains nightly and is never watched converges on something worse, and you hear it from a user, not a metric.
None of that argues against the loop. It argues for building the boring parts properly and keeping a person in the judging part. No hardware yet? The live arm runs with no signup, and the arena has 85 VLA models with 332 benchmark results. See also the SO-100 guide and what a VLA is.
Start turn one on your own arm
Pick a model and a robot, get the exact commands and defaults for that combination, and rent the GPU for the length of the run. A cheap-tier run costs about 1 to 3 USD.
Open the training guidesHow many episodes before the first training run?▾
The minimums here are 30 episodes for SmolVLA and 50 for ACT, GR00T N1.5, GR00T N1.7 and Pi0.5. Those are floors for the trainer, not targets for a working policy. Treat turn one as a calibration of your recording quality.
Retrain from base, or continue from my last checkpoint?▾
From base, almost always. Chaining fine-tunes makes each checkpoint depend on the whole run history, which you cannot audit. Retraining from base costs about 1 to 3 USD on the 24 GB tier and 4 to 12 USD on the A100 or H100 tier, and keeps every checkpoint traceable to one dataset revision.
Is DAgger still relevant now that VLAs are pretrained on huge datasets?▾
Yes, because it solves a distribution problem, not a capacity problem. A pretrained backbone reduces how much task data you need; it does not stop your policy drifting into states your demonstrations never covered. RaC matched prior state of the art in 2025 with 10 times less collection time.
Can I run the whole loop with the policy served from the cloud?▾
For slow pick-and-place, yes. For fast reactive motion, no. The control loop is 20 to 485 ms per action step before any network. Real-time chunking mitigates chunk-boundary pauses but does not remove the round trip.
What is the minimum bookkeeping for turn one?▾
One append-only file with, per turn: dataset revision, policy type and hyperparameters, checkpoint path, protocol version, per-trial outcomes. Enough to separate a data improvement from a hyperparameter one three turns later.
Which LeRobot version do these commands target?▾
LeRobot main as of 2026-08-24, after v0.6.1 (2026-08-03). On v0.5.1 (2026-04-07) there is no lerobot-rollout; you use lerobot-record with a policy path. v0.6.0 replaced GR00T N1.5 with N1.7 and made dataset and training dependencies optional extras.
Sources
- Ross, Gordon, Bagnell: A Reduction of Imitation Learning and Structured Prediction to No-Regret Online Learning (DAgger)
- Kelly et al.: HG-DAgger, Interactive Imitation Learning with Human Experts
- Liu et al.: Robot Learning on the Job, Human-in-the-Loop Autonomy and Learning During Deployment (Sirius)
- Hu et al.: RaC, Robot Learning for Long-Horizon Tasks by Scaling Recovery and Correction
- Black, Galliker, Levine: Real-Time Execution of Action Chunking Flow Policies
- Sculley et al.: Hidden Technical Debt in Machine Learning Systems, NIPS 2015
- Kreuzberger, Kuehl, Hirschl: Machine Learning Operations (MLOps), Overview, Definition, and Architecture
- MLOps: continuous delivery and automation pipelines in machine learning, maturity levels 0 to 2
- LeRobot cheat sheet: every CLI command with its flags
- LeRobotDataset v3.0: layout, streaming, and the v2.1 converter
- lerobot_rollout.py: strategies base, sentry, highlight, dagger, episodic
- lerobot_edit_dataset.py: delete_episodes, split, merge, modify_tasks
- LeRobot releases: v0.5.1, v0.6.0 and v0.6.1 with their breaking changes
- Isaac-GR00T: convert_v3_to_v2.py for the GR00T dataset loader
- Continuous Delivery for Machine Learning (CD4ML)
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started