From 6e196eea0edb75429f364ce9aefa2da6e89b521d Mon Sep 17 00:00:00 2001 From: Maxime Ellerbach Date: Fri, 31 Jul 2026 14:48:43 +0200 Subject: [PATCH] feat(rollout): add smooth_handover flag to DAgger strategy config (#4160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(rollout): add smooth_handover flag to DAgger strategy config The DAgger phase transitions run blocking smooth handovers: on pause the leader is driven to the follower (~2 s), and on correction start the follower is slid to the teleop pose (~1 s), both inside the record loop. For clutch-style teleoperators (e.g. VR controllers) that re-reference their command frame at the current robot pose on engage, the handover is already continuous — the interpolation only delays the start of the correction and eats its first frames. Add --strategy.smooth_handover (default true, existing behavior unchanged) to let such setups skip it, mirroring the episodic strategy's smooth_leader_to_follower_handover flag. Co-Authored-By: Claude Fable 5 * fix: pre-commit auto-fix (prettier markdown table formatting) --------- Co-authored-by: griffinaddison Co-authored-by: Claude Fable 5 --- docs/source/inference.mdx | 15 ++++++++------- src/lerobot/rollout/configs.py | 8 ++++++++ src/lerobot/rollout/strategies/dagger.py | 14 +++++++++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/source/inference.mdx b/docs/source/inference.mdx index aa1716015..f7b194a26 100644 --- a/docs/source/inference.mdx +++ b/docs/source/inference.mdx @@ -149,13 +149,14 @@ lerobot-rollout \ Foot pedal input is also supported via `--strategy.input_device=pedal`. Configure pedal codes with `--strategy.pedal.*` flags. -| Flag | Description | -| ------------------------------------ | ------------------------------------------------------- | -| `--strategy.num_episodes` | Number of correction episodes to record (default: 10) | -| `--strategy.record_autonomous` | Record autonomous frames too (default: false) | -| `--strategy.upload_every_n_episodes` | Push to Hub every N episodes (default: 5) | -| `--strategy.input_device` | Input device: `keyboard` or `pedal` (default: keyboard) | -| `--teleop.type` | **Required.** Teleoperator type | +| Flag | Description | +| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--strategy.num_episodes` | Number of correction episodes to record (default: 10) | +| `--strategy.record_autonomous` | Record autonomous frames too (default: false) | +| `--strategy.upload_every_n_episodes` | Push to Hub every N episodes (default: 5) | +| `--strategy.input_device` | Input device: `keyboard` or `pedal` (default: keyboard) | +| `--strategy.smooth_handover` | Smoothly hand control over at pause / correction start (default: true). Disable for clutch-style teleops that re-reference at the current robot pose on engage | +| `--teleop.type` | **Required.** Teleoperator type | ### Episodic (`--strategy.type=episodic`) diff --git a/src/lerobot/rollout/configs.py b/src/lerobot/rollout/configs.py index 2890668eb..ad2c12524 100644 --- a/src/lerobot/rollout/configs.py +++ b/src/lerobot/rollout/configs.py @@ -189,6 +189,14 @@ class DAggerStrategyConfig(RolloutStrategyConfig): # Target video file size in MB for episode rotation (record_autonomous # mode only). Defaults to DEFAULT_VIDEO_FILE_SIZE_IN_MB when None. target_video_file_size_mb: int | None = None + # Whether to turn on or off the smooth handover behavior at phase transitions: + # the leader is driven to the follower position on pause (teleops with + # `send_feedback` capability), and the follower is slid to the teleop pose when + # a correction starts (non-actuated teleops). Disable for clutch-style + # teleoperators (e.g. VR controllers) that re-reference at the current robot + # pose on engage: the handover is already continuous there, and the blocking + # interpolation only delays the start of the correction. + smooth_handover: bool = True input_device: str = "keyboard" keyboard: DAggerKeyboardConfig = field(default_factory=DAggerKeyboardConfig) pedal: DAggerPedalConfig = field(default_factory=DAggerPedalConfig) diff --git a/src/lerobot/rollout/strategies/dagger.py b/src/lerobot/rollout/strategies/dagger.py index 21d1e8e98..dec403ea9 100644 --- a/src/lerobot/rollout/strategies/dagger.py +++ b/src/lerobot/rollout/strategies/dagger.py @@ -623,8 +623,8 @@ class DAggerStrategy(RolloutStrategy): # State-machine transition side-effects # ------------------------------------------------------------------ - @staticmethod def _apply_transition( + self, old_phase: DAggerPhase, new_phase: DAggerPhase, engine, @@ -634,6 +634,10 @@ class DAggerStrategy(RolloutStrategy): ) -> None: """Execute side-effects for a validated phase transition, including smooth handovers. + The smooth handovers below can be disabled with + ``--strategy.smooth_handover=false`` (useful for clutch-style teleops + that re-reference at the current robot pose on engage). + AUTONOMOUS -> PAUSED (actuated teleop): Pause the engine, then drive the leader arm to the follower's last commanded position so the operator takes over without a jerk. @@ -657,7 +661,7 @@ class DAggerStrategy(RolloutStrategy): logger.info("Pausing engine - robot holds position") engine.pause() - if teleop_supports_feedback(teleop) and prev_action is not None: + if self.config.smooth_handover and teleop_supports_feedback(teleop) and prev_action is not None: # TODO(Maxime): prev_action is in robot action key space (output of robot_action_processor). # send_feedback expects teleop feedback key space. For homogeneous setups (e.g. SO-101 # leader + SO-101 follower) the keys are identical so this works. If the processor pipeline @@ -668,7 +672,11 @@ class DAggerStrategy(RolloutStrategy): elif old_phase == DAggerPhase.PAUSED and new_phase == DAggerPhase.CORRECTING: logger.info("Entering correction mode - human teleop control") - if not teleop_supports_feedback(teleop) and prev_action is not None: + if ( + self.config.smooth_handover + and not teleop_supports_feedback(teleop) + and prev_action is not None + ): logger.info("Smooth handover: sliding follower to teleop position") obs = robot.get_observation() teleop_action = teleop.get_action()