lerobot.async_inference): a PolicyServer child process runs locally unless --server_host selects a remote inference-server. --policy_type custom connects to an existing endpoint through the custom policy interface, using compressed observations and accepted-plan continuation. The Rust core exclusively owns arm CAN during execution.
Type s on stdin to save the rollout and end the episode, r to discard it — the arms drop limp for hand-repositioning (a failed rollout usually leaves them somewhere they shouldn’t be); clear them, then press Enter to return to rest and re-record — or q to discard and quit. --episode_time_s is a safety cap that falls back to the same [Enter]=save / r / q prompt when no key has been pressed.
This command is configured via draccus. The robot subsystem is exposed as the nested robot_config (cameras, per-joint gains) — nest into it with dots or pass a whole-config file. See Command configuration for the syntax and Run Policy Config Parameters for every field with its default.
Every policy observation is assembled around image exposure time: all cameras must provide a fresh timestamped frame, their median exposure selects the nearest sample from the Rust core’s 240 Hz telemetry history, and only then are joint/Cartesian features built. A stale camera, missing state bracket, or excessive timestamp skew stops the rollout instead of silently feeding a train/inference-misaligned observation. The default SDK backend derives exposure time from
TIME_REFERENCE.IMAGE; the optional GStreamer backend is accepted only when the exact timestamp-patched plugin binary installed by gst.build-zed is verified.
--repo_id is supplied, each saved episode is appended to a LeRobot-format dataset using the same resume/repair/wipe semantics as collect-data (resume a complete dataset, auto-repair a crash-inconsistent one by truncating to the intact episodes — see the collect-data crash-consistency check — refuse an incomplete one, wipe a leftover empty directory), and with the same per-episode durability: each episode’s parquet footers are flushed to disk the moment it saves, so a killed process costs at most the episode in flight. Between episodes the arms return to the rest pose via a collision-aware IK trajectory planned in a worker subprocess, mirroring the reset path used by collect-data.
Stopping parks the arms. Ending a run — Ctrl+C, q, or Stop in the control panel — plays that same return to rest and only then torques the motors off, so raised arms no longer drop under gravity on the way out. The teardown move is capped at 6 s; if it runs out of time or hits something, the arms are torqued off where they are. It is skipped when there is nothing to park or no safe way to park it: when the arms are already at rest, when a limp gravity-comp hold has left them in the operator’s hands, when the IK worker that plans the path never came up, when the CAN bus has stopped reporting positions, or when an episode worker has not proved it left the robot. A second Ctrl+C cannot interrupt it.
LeRobot action chunk aggregation defaults to ACT’s Algorithm 2 (
temporal_ensemble): every future timestep covered by buffered chunks is the exponentially-weighted average across those chunks, with gripper indices snapped to the newest contributing chunk. Custom policies publish their final plan directly and bypass this aggregation. Both paths keep observation/network work outside the periodic control thread.Custom policy interface
--policy_type custom selects compressed observations, accepted-plan
continuation and confirmed dispatch references. The robot executes one
unchanged plan suffix while inference runs remotely; it skips elapsed rows
of an arriving plan instead of retiming row zero. No protocol-selection flag
is needed. The handshake uses version: 2 to reject endpoints using the
original custom-policy protocol, with no automatic fallback.
almond_axol.policy SDK (Policy + serve()) and test it without the robot using policy.check. See the custom policy interface documentation for setup,
timing, continuity, recovery and DAgger semantics.