Skip to content

refactor(imitation): isolate LeRobot policy runtime - #3315

Open
TomCC7 wants to merge 8 commits into
fix/openyam-learning-baselinefrom
feat/experimental-lerobot-policy
Open

refactor(imitation): isolate LeRobot policy runtime#3315
TomCC7 wants to merge 8 commits into
fix/openyam-learning-baselinefrom
feat/experimental-lerobot-policy

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jul 31, 2026

Copy link
Copy Markdown
Member

This is layer 2 of the OpenYAM learning slice in the original stack.

Problem

LeRobot requires an environment that conflicts with the main DimOS perception and development dependency set. The policy module still needs normal typed streams and RPC control from the host stack.

Solution

  • Define an RPC-only LeRobotPolicyModule host contract using IsolatedPythonModule.
  • Keep LeRobot, Transformers, Torch, runtime tests, and their lockfile in a sibling Python 3.12 project.
  • Preserve lazy checkpoint loading, live observation validation, ordered joint targets, concurrency control, status reporting, and clean shutdown.
  • Keep LeRobot dependencies out of the root extras and root lockfile.

Learning slice

  1. feat: add isolated Python module runtime #3478 — external Python native-module runtime
  2. fix(openyam): align learning stack with canonical model #3853 — OpenYAM learning baseline
  3. refactor(imitation): isolate LeRobot policy runtime #3315 — isolated LeRobot policy runtime
  4. feat(imitation): add native OpenYAM collection #3854 — native OpenYAM collection
  5. feat(imitation): add controlled OpenYAM policy rollout #3855 — controlled OpenYAM policy rollout
  6. feat(a1z): a1z learning workflow #3318 — A1Z learning workflow

Validation: 21 host-side tests and 14 isolated-runtime tests passed.

Checklist

  • I have read and approved the CLA.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
4687 1 4686 180
View the top 1 failed test(s) by shortest run time
dimos.robot.manipulators.openyam.test_openyam::test_make_openyam_model_config_uses_canonical_arm_joints
Stack Traces | 3.23s run time
def test_make_openyam_model_config_uses_canonical_arm_joints() -> None:
        config = make_openyam_model_config()
    
>       assert OPENYAM_MODEL_PATH.parts[-2:] == ("i2rt", "yam.urdf")

config     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xff1d30df7b60>

.../manipulators/openyam/test_openyam.py:63: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/data.py:370: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = 'parts'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...cription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff1da12c0d50>
dimos/utils/data.py:353: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'yam_description/i2rt/yam.urdf'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...cription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff1da12c0d50>
dimos/utils/data.py:310: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'yam_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/yam_description/i2rt/yam.urdf')
        name       = 'yam_description/i2rt/yam.urdf'
        nested_path = PosixPath('i2rt/yam.urdf')
        path_parts = ('yam_description', 'i2rt', 'yam.urdf')
dimos/utils/data.py:254: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/yam_description.tar.gz')
        filename   = 'yam_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/data/.lfs/yam_description.tar.gz')
repo_root = PosixPath('.../work/dimos/dimos')

    def _lfs_pull(file_path: Path, repo_root: Path, *, retries: int = 2) -> None:
        relative_path = file_path.relative_to(repo_root)
    
        env = os.environ.copy()
        env["GIT_LFS_FORCE_PROGRESS"] = "1"
    
        last_err: subprocess.CalledProcessError | None = None
        for attempt in range(1, retries + 2):  # retries + 1 total attempts
            try:
                subprocess.run(
                    # --exclude= overrides lfs.fetchexclude from .lfsconfig, which
                    # otherwise silently skips data/.lfs/* even when --include matches.
                    ["git", "lfs", "pull", "--include", str(relative_path), "--exclude="],
                    cwd=repo_root,
                    check=True,
                    env=env,
                )
                return
            except subprocess.CalledProcessError as e:
                last_err = e
                if attempt <= retries:
                    time.sleep(attempt)  # 1s, 2s backoff
    
>       raise RuntimeError(
            f"Failed to pull LFS file {file_path} after {retries + 1} attempts: {last_err}"
        )
E       RuntimeError: Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/yam_description.tar.gz', '--exclude=']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '7da26a0d-a906-4b2b-9dae-d13f44e84b20.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/yam_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/yam_description.tar.gz', '--exclude='])
relative_path = PosixPath('data/.lfs/yam_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:224: RuntimeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Comment thread dimos/experimental/robot_policy/lerobot.py Outdated
Comment thread dimos/experimental/robot_policy/lerobot.py Outdated
Comment thread dimos/experimental/robot_policy/lerobot.py Outdated
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from a8ab2d8 to 486d959 Compare July 31, 2026 23:35
@TomCC7
TomCC7 changed the base branch from main to feat/galaxea-a1z-hardware July 31, 2026 23:35
@TomCC7
TomCC7 marked this pull request as ready for review July 31, 2026 23:58
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Aug 1, 2026
@TomCC7 TomCC7 mentioned this pull request Aug 1, 2026
3 tasks
Base automatically changed from feat/galaxea-a1z-hardware to main August 4, 2026 08:05
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 01aa5d0 to 18ffcc7 Compare August 14, 2026 20:03
@mintlify

mintlify Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Aug 14, 2026, 8:04 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Aug 14, 2026
@TomCC7
TomCC7 changed the base branch from main to cc/feat/openyam-macos-support August 14, 2026 20:05
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 18ffcc7 to cda4f1f Compare August 14, 2026 21:37
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from f6a2f76 to 2a46dfb Compare August 14, 2026 21:40
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 2a46dfb to 884a430 Compare August 14, 2026 21:56
@TomCC7
TomCC7 marked this pull request as draft August 14, 2026 21:57
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 884a430 to 2ffca94 Compare August 18, 2026 00:07
@TomCC7

TomCC7 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

The combined change has now been split into narrow dependent PRs:

  1. fix(openyam): align learning stack with canonical model #3853 — OpenYAM learning baseline
  2. refactor(imitation): isolate LeRobot policy runtime #3856 — isolated LeRobot policy runtime
  3. feat(imitation): add native OpenYAM collection #3854 — native OpenYAM collection
  4. feat(imitation): add controlled OpenYAM policy rollout #3855 — controlled OpenYAM policy rollout

GitHub does not allow changing the base of a PR already contained in a Stack, and #3315 has unrelated A1Z work above it. This PR remains open as the review-history and A1Z integration anchor; the new PRs carry the decision-clean diffs. Existing threads here are intentionally preserved, including threads that now reference files extracted into later layers.

@TomCC7
TomCC7 changed the base branch from cc/feat/external-python-native-module to fix/openyam-learning-baseline September 1, 2026 03:08
@TomCC7 TomCC7 changed the title feat: LeRobot rollout on openyam refactor(imitation): isolate LeRobot policy runtime Sep 1, 2026
Comment thread dimos/codebase_checks/test_no_init_files.py Outdated
Comment thread dimos/core/python_native_environment.py Outdated
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 34ff638 to 89cc9f6 Compare September 1, 2026 05:15
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 89cc9f6 to b8a7241 Compare September 1, 2026 17:00
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from b8a7241 to 0c8996a Compare September 1, 2026 17:16
@TomCC7
TomCC7 force-pushed the feat/experimental-lerobot-policy branch from 0c8996a to 564d788 Compare September 1, 2026 17:17
Comment thread dimos/imitation/policy/lerobot/python/dimos_lerobot/runtime.py Outdated
Comment thread dimos/imitation/policy/lerobot/python/dimos_lerobot/runtime.py Outdated
@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds a host-side LeRobot policy integration backed by an isolated Python runtime, including rollout lifecycle handling and joint-command publication.

Two failures were reproduced. Standard wheel installations omit the isolated runtime manifest required to start LeRobot, so rollout startup raises FileNotFoundError. Separately, stopping a rollout can still emit one arm command and one gripper command after the stop request. These issues must be fixed before merge.

Confidence Score: 2/5

Not safe to merge because wheel-installed LeRobot rollouts cannot start and stopped rollouts can still publish robot commands.

The reproduced packaging failure blocks the feature in standard deployments, while the reproduced stop race can issue commands after a stop request.

Files Needing Attention: MANIFEST.in and dimos/imitation/policy/lerobot/python/dimos_lerobot/runtime.py

T-Rex T-Rex Logs

What T-Rex did

  • Executed the installed-wheel runtime-project execution script and captured its output to document the runtime path for the P1 finding.
  • Executed the deterministic stop-to-publication window test and captured the output showing commands after the stop.
  • Produced and documented a third finding-proof for a posted P1 finding.
  • Validated the general-contract scenario by running a controlled sequence where stop_requested_before_arm_publish=True and recording the subsequent one arm and one gripper command after the stop.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Stop request can be followed by arm and gripper command publication

    • Bug
      • A deterministic controlled-hook execution reached the post-predict check with stop unset, issued _stop_policy immediately afterward, and observed an arm command plus gripper command publish while the stop event was already set.
    • Cause
      • _run_rollout checks _stop_event at lines 333-336 but performs publication later at lines 347-354 without a synchronization mechanism or a second stop check that closes the intervening window.
    • Fix
      • Serialize stop and publication with a shared lock/protocol, or perform a final stop-state check immediately before each publication while ensuring a concurrent stop cannot occur between that check and publish.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(imitation): tighten LeRobot runtime ..." | Re-trigger Greptile

Comment thread MANIFEST.in
Comment on lines +12 to +13
include dimos/imitation/policy/lerobot/python/pyproject.toml
include dimos/imitation/policy/lerobot/python/uv.lock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Wheel omits isolated runtime

The wheel does not contain the isolated LeRobot runtime manifest. These MANIFEST.in entries preserve the files for source distributions, but the installed wheel omits dimos/imitation/policy/lerobot/python/pyproject.toml. The host module resolves this manifest from its installed package directory before starting the isolated runtime, so wheel installations fail with FileNotFoundError before a rollout can start. Package the isolated runtime project files in the wheel, not only the source distribution.

Artifacts

Installed-wheel runtime-project execution script

  • This script builds and installs the wheel outside the checkout, then executes the installed runtime-project lookup; takeaway: it directly reproduces the missing-manifest failure.

Installed-wheel runtime-project execution output

  • This captured command output shows the wheel and installed site-packages omit the required manifest and the actual installed lookup raises FileNotFoundError; takeaway: the claimed defect is verified.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment on lines +333 to +354
if self._stop_event.is_set() or (
deadline is not None and time.monotonic() >= deadline
):
break

gripper_name = self.config.gripper_joint_name
gripper_index = (
self.config.joint_names.index(gripper_name)
if gripper_name is not None
else None
)
arm_indices = [
index for index in range(len(self.config.joint_names)) if index != gripper_index
]
self.joint_command.publish(
JointState(
name=[self.config.joint_names[index] for index in arm_indices],
position=[float(action[index]) for index in arm_indices],
)
)
if gripper_index is not None:
self.gripper_command.publish(Float32(data=float(action[gripper_index])))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stop races with command publication

A stop request can arrive immediately after the stop-state check and before either publisher is called. In that interval, the rollout emits the already-computed arm command and, when configured, the gripper command despite the stop request having completed. Serialize stopping with publication, or otherwise close this window so no command can be published after stop_rollout() returns.

Artifacts

Deterministic stop-to-publication window test source

  • Authored controlled-hook test imports and executes the runtime implementation, holds at the line-333 check, requests stop, and asserts that subsequent publications occur; the takeaway is that the exact race is reproducible.

Controlled stop-window test output showing commands after stop

  • Captured output from `python trex-artifacts/stop-publication-window-test.py` in `/home/user/repo` exits successfully and records stop before one arm and one gripper publication; the takeaway is that the claimed post-stop publication path occurs.

View artifacts

T-Rex Ran code and verified through T-Rex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant