The big picture

How LeRobot is organized

5 min read · for SO-101 users

Think of LeRobot as five Lego pieces: a robot driver, a teleop controller, a dataset format, a trainer script, and a policy. The 0.5.0 release didn't change which pieces exist — it only added a Unitree G1 driver and shuffled some internal plumbing.

What this means for you: when you run lerobot-record, you're using the robot driver + teleop + dataset format. When you run lerobot-train, you're using the dataset + trainer + policy. Every other page on this site zooms into one of those five pieces.

  ┌─────────┐    ┌──────────┐    ┌──────────┐
  │ Teleop  │───▶│  Robot   │───▶│ Dataset  │
  │ (leader)│    │(follower)│    │ (record) │
  └─────────┘    └──────────┘    └────┬─────┘
                                      ▼
                                ┌──────────┐    ┌──────────┐
                                │  Policy  │◀───│ Trainer  │
                                │ (ACT…)   │    │ (train)  │
                                └──────────┘    └──────────┘

Recording flows left-to-right; training flows right-to-left. A trained policy then drives the robot directly during evaluation.

The basics

What is a policy?

A policy is the trained brain. You feed it what the robot sees (camera frames + joint angles) and it outputs what the robot should do next (target joint positions, or a short chunk of upcoming motions). ACT, Diffusion, SmolVLA, π0 are all different recipes for that brain.

What is a teleop?

A teleop (teleoperator) is whatever you use to drive the robot while recording demos. For SO-101 that's almost always a leader arm — a smaller twin you grip with your hand. The follower mirrors your motion. Other options: keyboard, gamepad, or a phone for 6-DoF (six degrees of freedom) tracking.

What's a robot driver?

The piece that talks to the actual motors over a USB or CAN bus. It reads joint angles and writes target positions every loop iteration. You don't usually touch it directly; the CLI scripts use it for you.

What's a LeRobotDataset?

A folder of recorded demos saved as Parquet tables (the numeric data: joint angles, actions, timestamps) plus MP4 videos (the camera frames), with a small JSON index that ties them together. It uploads cleanly to the Hugging Face Hub.

What ties it together?

The CLI scripts (lerobot-record, lerobot-train, lerobot-eval) and a set of config dataclasses — plain Python classes whose fields become CLI flags. Almost every flag you've ever passed is documented as one of those fields, so --help works on everything.

How you actually use it

The three CLI commands you'll hit most:

lerobot-record # Capture demos using a teleop + a robot lerobot-train # Train a policy on a recorded dataset lerobot-eval # Run a trained policy and measure success

A typical SO-101 session: record 50 episodes → push the dataset to the Hub → fine-tune an ACT or Diffusion checkpoint on it → load the fine-tuned policy back and run lerobot-eval. All five Lego pieces, in order.

Things to know

First-party hardware support

LeRobot ships drivers for SO-100/101, Koch v1.0/v1.1, ALOHA-style bimanual setups, OpenManipulator-X, OpenArm, LeKiwi, Reachy 2, Hope Jr., and Unitree G1. If you're on SO-101, you're on the most well-trodden path.

Every flag is documented

Pretty much every CLI flag corresponds to a field on a config dataclass. --help on any lerobot-* command will print the full set of options for the type you've selected (e.g. --policy.type=act swaps in the ACT-specific flags).

0.5.0 didn't add or remove any of those pieces

Same five Lego pieces, same five subdirectories, same CLI commands. The structural changes are: one new shared backbone file for the π-family policies, the Unitree G1 driver split into smaller modules, and a single new training-config field for deterministic cuDNN. If you're an SO-101 user, none of that touches your workflow.

Optional: under the hood
Show the 18 subpackages and what each owns

18 subpackages, identical roster in v0.4.4 and v0.5.0. Vendor means code copied from an upstream model repo (HF Transformers / OpenPI / Florence-2) and patched in-tree.

Package Kind Owns / responsibility Δ 0.4.4 → 0.5.0
async_inference/ first-party gRPC async-inference pipeline: policy_server.py, robot_client.py, helpers (make_lerobot_observation at src/lerobot/async_inference/helpers.py:135). 5-line import block in robot_client.py
cameras/ first-party Camera ABC at src/lerobot/cameras/camera.py:26; backends opencv/, realsense/, reachy2_camera/, zmq/; factory make_cameras_from_configs at src/lerobot/cameras/utils.py:25. zmq/image_server.py adds CaptureThread
configs/ first-party Top-level dataclass configs (TrainPipelineConfig, EvalPipelineConfig, PreTrainedConfig), the draccus-based CLI parser, shared enums. +1 field (cudnn_deterministic)
data_processing/ first-party Single helper module sarm_annotations/subtask_annotation.py for SARM (Subtask-Annotation Reward Model) labels. unchanged
datasets/ first-party LeRobotDataset + MultiLeRobotDataset (src/lerobot/datasets/lerobot_dataset.py:566,1722); metadata at :86; v2.1→v3.0 migration in v30/; streaming, online buffer, stats, transforms. cosmetic (PEP 695, pd.Index name)
envs/ first-party Gym env config + factory: make_env at src/lerobot/envs/factory.py:102; Libero + Metaworld wrappers; EnvHub support. unchanged
model/ first-party Currently a single file: kinematics.py. Used by Unitree G1 and so_follower. unchanged
motors/ first-party Motor-bus abstractions (motors_bus.py), backends dynamixel/, feetech/, damiao/, robstride/, calibration_gui.py. cosmetic (TypeAliastype)
optim/ first-party Optimizer + LR-scheduler dataclass configs and factory make_optimizer_and_scheduler at src/lerobot/optim/factory.py:25. byte-identical
policies/ mixed Per-policy folders (act/, diffusion/, tdmpc/, vqbet/, sac/, smolvla/, pi0/, pi05/, pi0_fast/, groot/, xvla/, wall_x/, sarm/, rtc/); base PreTrainedPolicy at src/lerobot/policies/pretrained.py:44; central factory.py. + pi_gemma.py
processor/ first-party Pipeline ABC + step registry. The thing that makes training policy-agnostic: each policy folder ships a processor_<name>.py that builds a pre/post pair from these steps. cosmetic (PEP 695)
rl/ first-party HIL-SERL / online-RL stack: actor.py, learner.py, buffer.py, gym_manipulator.py, learner_service.py (gRPC), wandb_utils.py. unchanged
robots/ first-party Robot ABC at src/lerobot/robots/robot.py:30; per-platform packages (koch_follower/, so_follower/, bi_so_follower/, lekiwi/, reachy2/, unitree_g1/, hope_jr/, omx_follower/, openarm_follower/, bi_openarm_follower/, earthrover_mini_plus/); factory make_robot_from_config at src/lerobot/robots/utils.py:25. G1 driver split
scripts/ first-party 16 CLI entry-points (lerobot-train, lerobot-eval, lerobot-record, …). body-level edits only
teleoperators/ first-party Teleop ABC at src/lerobot/teleoperators/teleoperator.py:29; backends include keyboard/, gamepad/, phone/, homunculus/, leader arms (koch_leader/, so_leader/, omx_leader/, openarm_leader/, bi_*_leader/), unitree_g1/, reachy2_teleoperator/; factory make_teleoperator_from_config at src/lerobot/teleoperators/utils.py:36. G1 RemoteController grew (dual-source joystick)
templates/ first-party Single Jinja-style file lerobot_modelcard_template.md for HF Hub model cards. unchanged
transport/ first-party gRPC plumbing — services.proto, generated services_pb2.py, services_pb2_grpc.py, helpers in utils.py. byte-identical
utils/ first-party Cross-cutting helpers — constants.py, hub.py (HubMixin), import_utils.py, logging_utils.py, random_utils.py, robot_utils.py, rotation.py, train_utils.py, transition.py, visualization_utils.py, errors.py. small content edits in import_utils.py, io_utils.py

src/lerobot/__init__.py is byte-identical between the two tags — the top-level module is intentionally lightweight and exposes only string registries plus __version__. There is no make_* symbol exposed from the top of the package; factories must be imported from their submodules.

Stale constant in both tags

available_policies at src/lerobot/__init__.py:160 still lists only ["act", "diffusion", "tdmpc", "vqbet"], even though both tags ship 14 policy folders including pi0, pi05, pi0_fast, smolvla, groot, xvla, wall_x, sac, sarm, rtc. The real registry is the draccus ChoiceRegistry on PreTrainedConfig (src/lerobot/configs/policies.py:41) — so import lerobot; lerobot.available_policies is a misleading artifact. Don't trust it programmatically.

Show the public factory functions (make_policy, make_dataset, …)

Every Lego piece is built by a small make_* factory. These have to be imported from their submodules — nothing is re-exported at the top level of the package.

Factory Location
make_policy(...) src/lerobot/policies/factory.py:405 (v0.4.4: :406)
make_policy_config(policy_type, **kwargs) src/lerobot/policies/factory.py:140
make_pre_post_processors(...) src/lerobot/policies/factory.py:213
get_policy_class(name) src/lerobot/policies/factory.py:60
make_dataset(cfg) src/lerobot/datasets/factory.py:71
make_env(...) src/lerobot/envs/factory.py:102
make_optimizer_and_scheduler(...) src/lerobot/optim/factory.py:25
make_default_processors() src/lerobot/processor/factory.py:58
make_robot_from_config(config) src/lerobot/robots/utils.py:25
make_teleoperator_from_config(config) src/lerobot/teleoperators/utils.py:36
make_cameras_from_configs(camera_configs) src/lerobot/cameras/utils.py:25
make_robot_env(cfg) (HIL-SERL gym wrapper) src/lerobot/rl/gym_manipulator.py:303
make_lerobot_observation(...) (async client) src/lerobot/async_inference/helpers.py:135
make_locomotion_controller(name) (G1) src/lerobot/robots/unitree_g1/g1_utils.py:66 (v0.5.0 only)
Per-policy make_<name>_pre_post_processors(...) One per policy folder (act, diffusion, tdmpc, vqbet, sac, smolvla, groot, xvla, wall_x, pi0, pi05, pi0_fast, sarm)
Naming asymmetry that will bite wrappers

Device builders are named make_<X>_from_config(config) and take an already-instantiated config object (cameras, robots, teleoperators). Pipeline builders are named make_<X>(cfg) and take the full pipeline config (make_dataset(cfg), make_env(...), make_policy(...)). There is no make_robot("ur20", **kwargs) sugar. Anyone building a wrapper that expects that shape will rediscover this every time.

Configuration is dataclass + draccus, not Hydra

TrainPipelineConfig at src/lerobot/configs/train.py:37 is a single root dataclass that holds nested configs as typed fields (no defaults: list, no merge logic). Polymorphic dispatch is through draccus ChoiceRegistry: PreTrainedConfig at src/lerobot/configs/policies.py:41 is the registry every per-policy *Config subclasses, which is how --policy.type=act selects the right one.

Show the v0.5.0 anatomy delta (what moved, what was added)

diff -rq across src/lerobot/ shows ~40 files differ. Almost all are body-level edits inside policies and per-robot drivers. The structural changes:

v0.4.4

Removed in v0.5.0

  • src/lerobot/robots/unitree_g1/robot_kinematic_processor.py (313 lines) — per-frame WeightedMovingFilter + IK pre-processor.
  • examples/unitree_g1/gr00t_locomotion.py — promoted into the library.
  • examples/unitree_g1/holosoma_locomotion.py — promoted into the library.

Constraints

  • requires-python = ">=3.10" (pyproject.toml:32)
  • transformers>=4.57.1,<5.0.0 (pyproject.toml:102)
  • cudnn.benchmark = True unconditional in scripts/lerobot_train.py:212-216
v0.5.0

Added (structural)

  • src/lerobot/policies/pi_gemma.py (363 lines) — shared Gemma / PaliGemma backbone module; first-class AdaRMS via PiGemmaRMSNorm.
  • src/lerobot/robots/unitree_g1/g1_kinematics.py (287 lines) — kinematics promoted to its own module.
  • src/lerobot/robots/unitree_g1/gr00t_locomotion.py (205 lines) — NVIDIA GR00T-WBC ONNX adapter.
  • src/lerobot/robots/unitree_g1/holosoma_locomotion.py (214 lines) — Amazon FAR Holosoma ONNX adapter.
  • tests/robots/test_unitree_g1.py + teleoperator tests.

Constraints

  • requires-python = ">=3.12" (pyproject.toml:32)
  • transformers>=5.3.0,<6.0.0 (pyproject.toml:102)
  • huggingface_hub>=1.0.0,<2.0.0 — major bump, extras dropped
  • New cudnn_deterministic field gates cuDNN at scripts/lerobot_train.py:212-216
  • New dep: unitree-sdk2==1.0.1 (pyproject.toml:122)
Key invariants of the upgrade
  • No subpackages added or removed. Same 18.
  • No top-level public symbols added or removed in lerobot/__init__.py. Byte-identical file.
  • No new entry-point scripts. Same 16.
  • No restructuring of configs/, processor/, transport/, datasets/, policies/factory.py, envs/factory.py, or optim/factory.py.
  • No dataset schema change — both tags emit CODEBASE_VERSION = "v3.0" (src/lerobot/datasets/lerobot_dataset.py:83).

For a UR20 + ACT/Diffusion stack, the only one of these changes that actually matters is the Python 3.12 floor. Everything else is internal to the π-family policies or the Unitree G1 driver.

Where to go next →

Robots & how you control them — the three teleop styles (leader arm, keyboard/gamepad, phone), what hardware LeRobot ships drivers for, and the v0.5.0 Unitree G1 split in detail.