#4626·lerobot

[Bug/Feature Req/Fix?] DAgger rollout strategy cannot record full task-attempt episodes with HIL corrections

Author: grahamstelzerCreated Sep 13, 2026Updated Sep 15, 2026
Labelsbugdocumentationpoliciesdatasetconfigurationteleoperatorssensorsexamples

tldr: DAggerStrategyConfig.record_autonomous=True currently offers no way to record a complete task attempt (autonomous + HIL corrections) as a single bounded episode. Only unbounded/filesize-gated continuous recording, or corrections-only recording, are supported. This is confusing and seemingly off-methodology.

(Also i should mention im just 1 guy doing all the research and the coding so probably made mistakes here and there, please feel free to lmk (and double check). If it matters, claude was used for ~50% of the planning and code design and like 3 comments, everything else i wrote by hand.)

background/important note: This bug was discovered during my lab's research effort into DAgger/HIL for VLAs, and the coded fix is my own solution that I spent extra time making sure to fit into the LeRobot ecosystem if you guys want to use it but importantly, does not have to be a PR or addition, the current code is still (mostly) valid in my opinion, just confusing! I think at the least, i would just leave this as a forked version of Lerobot.

essentially: the research papers from the documentation and the code seem to be on different pages. The code allows two (mostly) valid recreations of existing methods for HIL rollouts but the flags are confusing and a rework of the logic would both solve the confusion and allow for my lab's methodology to work correctly.

my lab's data-collection protocol requires each recorded episode to correspond to one full task attempt, from task start to completion or failure, regardless of whether that attempt was fully autonomous, fully human-driven, or a mix, with human interventions tagged inline. This mirrors the DAgger-family approaches: DAgger (Ross et al.), HG-DAgger (Kelly et al., human-gated variant), and RaC (Recovery and Correction, cited in this module's own docstring).

comparatively, there exists continuous/unbounded collection paradigms like π0.6's long-horizon sessions (robot making coffee for 18 hours or something). This seems to be what the code I intend to address is mimicking, though importantly, it bounds episodes by file-size.

Current behavior in code:

Dispatch point: line ~291 in dagger.py :: DAggerStrategy.run()

if self.config.record_autonomous:
	self._run_continuous(ctx)
else:
	self._run_corrections_only(ctx)

if record_autonomous is False:

  • _run_corrections_only() runs
  • each correction window (aka, each time a human triggers teleoperation) = one episode
  • otherwise, the policy runs autonomously, unrecorded
  • this is valid for any research methodology where their dataset aggregation on takes into account HIL corrections

True (confusing branch that I intend to remedy):

  • _run_continuous() runs
  • an estimation is run based on the input duration and camera views to determine a filesize target
  • meanwhile, the policy runs and records everything; both the autonomous rollout and the human in the loop takeovers
  • this is seemingly valid for in the case of someone wanting to run a long rollout session and record portions of what happens (like the pi0.6 example from before, long session, record episodes of x filesize)
  • this might need a couple logic reworks and checks, because i never was able to record more than a single episode BUT this could be user error…
  • …and regardless, what if the user does not care about filesize parameters, and instead wants to record episodes by task attempt with autonomous parts and not autonomous parts? (me)

Furthermore, the record_autonomous=True and resulting _run_continuous() logic actually seems to be taken from the sentry rollout strategy, just with HIL functionality added (as DAggerPhases). Again to reiterate, this doest seem invalid, it just doesnt make sense.

Main relevant failure cases: A user trying to "record the autonomous" sections of a DAgger rollout AND the HIL may suddenly have their episode cut off. This is fine because the code will not save during the CORRECTING phase but what about cases where the user may need to correct multiple times? Or if they are only doing a brief, trajectory nudge before the robot fully completes the task autonomously?

Suggested fix:

In order to not clutter this post too much, I'll paste the entire rewritten dagger.py file for inspection in the comments and only describe changes with a couple relevant code snippets here. I'll only submit a PR if you guys are interested in this approach. lerobot/src/lerobot/rollout/configs.py is also changed to add some fields, I'll paste that as well.

1. Add a flag, change default behavior. (use_sentry_rotation bool to DAggerStrategyConfig in src/lerobot/rollout/configs.py, defaulting False, and change the dispatch logic in src/lerobot/rollout/strategies/dagger.py) Essentially just moving the current behavior from record_autonomous=true to a different sub-flag. record_autonomous=false stays the same. The new flag I just added to configs.py and is "--strategy.use_sentry_rotation". This wont work if strategy.record_autonomous=False and --strategy.use_sentry_rotation=True, just added a CLI warning if this happens.

truth table of --strategy.record_autonomous and --strategy.use_sentry_rotation: T and T = _run_continuous() T and F = (default) _run_episodic() F and T = _run_corrections() and logger.warning F and F = _run_corrections()

dispatch code:

if self.config.record_autonomous:
    if self.config.use_sentry_rotation:
        logger.info("Running DAgger + sentry")
        self._run_continuous(ctx)
    else:
        logger.info("Running DAgger + episodic")
        self._run_episodic(ctx)
else:
    if self.config.use_sentry_rotation:
        logger.warning("--strategy.use_sentry_rotation=True does nothing when --strategy.record_autonomous=False")
    self._run_corrections_only(ctx)

2. Add _run_episodic() (new method) to class DAggerStategy() in dagger.py

tldr: Basically just implement the functionality from episodic.py and the original lerobot-record loop with DAggerPhases and transitions inserted. This is done with a double loop on episodes and episode recording time. The important logic to consider is when to record frames, when to allow users to exit early or rerecord episodes, and how phases interact.

list of things _run_episodic() deals with:

  • Outer loop (episode count, reset-phase trigger, re-record branch) adapted from episodic.py :: EpisodicStrategy.run(), converting its plain-dict events["..."] access to DAggerEvents attribute/Event() calls.
  • Inner per-tick loop (phase dispatch, intervention-tagged frame construction) adapted from _run_continuous, with the filesize-based rotation block removed and replaced by a timestamp < episode_time_s bound (sourced from cfg.dataset.episode_time_s, matching EpisodicStrategyConfig's existing field of the same name/purpose).
  • Reset-between-episodes logic reworked from episodic.py's optional-teleop branch (if teleop: ... elif reset_to_initial_position: ...) into an unconditional sequence, since DAgger strategy always requires a teleop (RolloutConfig.post_init raises ValueError if self.teleop is None and strategy is DAgger), the "no teleop" branch was dead code in this context.
  • New sequence: unconditionally home the follower via self._return_to_initial_position, then sync the leader to the follower's home pose (teleop_smooth_move_to, guarded by teleop_supports_feedback) and open the existing _reset_loop window.
  • _reset_loop copied from episodic.py, retargeted from events: dict to events: DAggerEvents (attribute access). Functionally unchanged otherwise. Does not run on the last episode.
  • self._needs_push.set() added in the finally block (unconditionally, alongside the best-effort final save) so teardown()'s hub-push branch actually fires. This is still gated by the CLI arg though.

3. events changes tldr: instead of messing with existing events that looked at DAggerEvents(), just added fields to DAggerEvents() itself to allow for the early exiting and re-recording from lerobot-record and episodic.py

  • added exit_early() flag and check to DAggerEvents()
  • added rerecord() flag and check to DAggerEvents()
  • changed the events checks from episodic.py that were previously string-based to attributes

**4. Everything else is added to just make the first stuff work **

  • sort of running out of energy for this post but allowed early exiting and rerecording in the PAUSE phase and the AUTONOMOUS phase, NOT in the CORRECTING phase.
  • added a bunch of logger.info()'s
  • added a bunch of comments
  • reworked logic at the end of the recording loop itself to unconditionally move the teleop. the problem with episodic loop is that it checks for a teleop to preform certain resetting functionality but in a DAgger session, HIL teleop is unconditionally true.

Testing:

  • HIL works without error
  • dataset frame creations work without error
  • early exit and re-record arrow key functionality works, checked in all phases of HIL
  • 3-episode dataset, shows pausing not recorded, episode 1 was exited early and re-recorded: huggingface link

Ok I ran out of energy to write this, sorry. Happy to answer any questions. Please feel free to give criticism, personally just trying to help :)