Repository navigation
feat(datasets): replay an episode on the follower arm - #129
Open
nicolas-rabault wants to merge 2 commits into
Open
nicolas-rabault wants to merge 2 commits into
nicolas-rabault wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Cross-mode starts remain racy, and several paths can remove the Stop control while replay still owns the arm.
Review effort: Balanced
Findings: 2
Open (4)
What changed in this PR
Adds episode replay on the SO-101 follower arm from the dataset viewer.
Changes:
- Adds replay backend lifecycle, API routes, and cross-mode checks.
- Adds replay controls with synchronized footage playback.
- Adds tests, documentation, and rebuilt frontend assets.
| File | Description |
|---|---|
lelab/replay.py |
Implements replay lifecycle and hardware control. |
lelab/server.py |
Exposes replay endpoints. |
lelab/record.py |
Checks replay state before recording. |
lelab/teleoperate.py |
Checks replay state before teleoperation. |
lelab/rollout.py |
Checks replay state before inference. |
tests/test_replay.py |
Tests schemas, exclusion, and failure paths. |
frontend/src/components/dataset/ReplayButton.tsx |
Adds replay UI and status polling. |
frontend/src/components/dataset/EpisodeViewer.tsx |
Integrates replay with footage playback. |
frontend/src/components/dataset/EpisodeViewer.test.tsx |
Mocks the new replay control. |
frontend/dist/index.html |
References rebuilt assets. |
frontend/dist/assets/index-BhoUX80z.css |
Removes the previous stylesheet. |
frontend/dist/assets/index-Bg74I3Au.css |
Adds the rebuilt stylesheet. |
CLAUDE.md |
Documents replay and mutual exclusion. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+67
to
+70
| with _state_lock: | ||
| if replay_active: | ||
| return {"success": False, "message": "A replay is already running"} | ||
| if _record.recording_active: |
Comment on lines
+133
to
+137
| worker.join(timeout=5.0) | ||
| if worker.is_alive(): | ||
| logger.warning("Replay worker did not exit within 5s") | ||
|
|
||
| return {"success": True, "message": "Replay stopped"} |
| return () => clearInterval(timer); | ||
| }, [phase, baseUrl, fetchWithHeaders, toast]); | ||
|
|
||
| if (!robot?.is_clean) return null; |
Comment on lines
+42
to
+45
| useEffect(() => { | ||
| apiRequest<ReplayStatus>(baseUrl, fetchWithHeaders, "/replay-status") | ||
| .then((status) => { | ||
| if (status.replay_active) setPhase("playing"); |
The episode viewer gets a "Play on robot" button, shown only once a calibrated robot is selected. It sends the episode's recorded actions to the follower at the recording's rate and starts the footage from the first frame at 1x so both can be compared; while the arm moves the button becomes "Stop robot", and it picks a running replay back up when the viewer is opened again. replay.py is an in-process take on lerobot-replay, built like teleoperation: the follower connects synchronously so a failure reaches the user, the actions are read from the episode's data files, and the worker owns the disconnect and the active flag. Recording, teleoperation, inference and replay all drive the follower, so each refuses to start while another runs.
…lease Recording, teleoperation, inference and replay each checked the others' flags under their own lock, so two concurrent starts could both claim the follower. They now check and claim under a shared follower_lock. Stopping a replay reports a failure when the worker has not released the arm within 5s, and the button only turns back to Play once the status poll sees the arm released. A running replay keeps its Stop button even when no calibrated robot is selected anymore. ReplayButton gets component tests for restoring a running replay, starting, stopping, a failed poll and a refused start.
nicolas-rabault
force-pushed
the
feat/replay-on-robot
branch
from
October 9, 2026 08:03
045879d to
287a5da
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
lelab/replay.py: an in-process take onlerobot-replay, built like teleoperation. The follower connects synchronously so a failure reaches the user, the worker owns the disconnect and the active flag, and athreading.Eventstops it. Actions are read from the episode's data files throughepisode_media.POST /start-replay,POST /stop-replay,GET /replay-status.follower_lockand refuses to start while another runs. The CLAUDE.md state model says so.Behaviour to know
Same as
lerobot-replay: the arm moves to the episode's first pose at full speed (no ramp), and torque is released when the replay ends or is stopped.Testing
tscshows only the 5 errors already on main, eslint clean on the changed files.ReplayButtontests cover restoring a running replay without a selected robot, starting, Stop holding until the status shows the arm released, a failed poll and a refused start. Reintroducing either regression from the review fails them.