Skip to content

feat(imitation): add native OpenYAM collection - #3854

Open
TomCC7 wants to merge 14 commits into
feat/experimental-lerobot-policyfrom
feat/openyam-native-collection
Open

feat(imitation): add native OpenYAM collection#3854
TomCC7 wants to merge 14 commits into
feat/experimental-lerobot-policyfrom
feat/openyam-native-collection

Conversation

@TomCC7

@TomCC7 TomCC7 commented Sep 1, 2026

Copy link
Copy Markdown
Member

This is layer 3 of the OpenYAM learning slice in the original stack. It adds native MCAP collection, OpenYAM collection composition, dataset validation, and isolated LeRobot data preparation.

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: 52 host-side and 18 isolated-runtime tests passed.

@TomCC7
TomCC7 changed the base branch from feat/experimental-lerobot-policy to feat/openyam-lerobot-policy-runtime September 1, 2026 02:57
@TomCC7
TomCC7 changed the base branch from feat/openyam-lerobot-policy-runtime to feat/experimental-lerobot-policy September 1, 2026 03:06
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

❌ 5 Tests Failed:

Tests completed Failed Passed Skipped
4692 5 4687 178
View the top 3 failed test(s) by shortest run time
dimos.core.test_isolated_python_end_to_end::test_isolated_python_rpc_refs_and_restart
Stack Traces | 2.33s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12........./site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12........./site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 92, in pytest_runtest_call
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 95, in pytest_runtest_call
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 846, in pytest_runtest_call
  |     yield from self._runtest_for(item, "call")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 898, in pytest_runtest_call
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/skipping.py", line 257, in pytest_runtest_call
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12........./site-packages/_pytest/runner.py", line 174, in pytest_runtest_call
  |     item.runtest()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/python.py", line 1627, in runtest
  |     self.ihook.pytest_pyfunc_call(pyfuncitem=self)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.............................................................../site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/python.py", line 159, in pytest_pyfunc_call
  |     result = testfunction(**testargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_isolated_python_end_to_end.py", line 59, in test_isolated_python_rpc_refs_and_restart
  |     coordinator = ModuleCoordinator.build(
  |                   ^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 377, in build
  |     coordinator.build_all_modules()
  |   File ".../core/coordination/module_coordinator.py", line 249, in build_all_modules
  |     safe_thread_map(modules, lambda m: m.build())
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (1 sub-exception)
  +-+---------------- 1 ----------------
    | dimos.protocol.rpc.rpc_utils.RemoteError: [Remote builtins.RuntimeError] Isolated Python environment preparation failed (exit 1): Using CPython 3.12.3 interpreter at: ........./usr/bin/python3.12
    | Creating virtual environment at: ........./home/runner/.cache........./dimos/python-native/external_python_module-c5c794f5b247
    | Resolved 195 packages in 593ms
    | error: The lockfile at `uv.lock` needs to be updated, but `--locked` was
    |        provided.
    | 
    | hint: To update the lockfile, run `uv lock`.
    | 
    | Remote traceback:
    | Traceback (most recent call last):
    |   File ".../protocol/rpc/zenohrpc.py", line 199, in _execute_rpc
    |     response = f(*args[0], **args[1])
    |                ^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../protocol/rpc/spec.py", line 116, in override_f
    |     return getattr(module, fname)(*args, **kwargs)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../dimos/core/isolated_python_module.py", line 211, in build
    |     super().build()
    |   File ".../dimos/core/native_module.py", line 236, in build
    |     self._maybe_build()
    |   File ".../dimos/core/isolated_python_module.py", line 207, in _maybe_build
    |     self._run_prepare()
    |   File ".../dimos/core/isolated_python_module.py", line 127, in _run_prepare
    |     raise RuntimeError(
    | RuntimeError: Isolated Python environment preparation failed (exit 1): Using CPython 3.12.3 interpreter at: ........./usr/bin/python3.12
    | Creating virtual environment at: ........./home/runner/.cache........./dimos/python-native/external_python_module-c5c794f5b247
    | Resolved 195 packages in 593ms
    | error: The lockfile at `uv.lock` needs to be updated, but `--locked` was
    |        provided.
    | 
    | hint: To update the lockfile, run `uv lock`.
    | 
    | 
    | The above exception was the direct cause of the following exception:
    | 
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "........./usr/lib/python3.12....../concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "........./usr/lib/python3.12....../concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "........./usr/lib/python3.12.../concurrent/futures/thread.py", line 58, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 249, in <lambda>
    |     safe_thread_map(modules, lambda m: m.build())
    |                                        ^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 93, in __call__
    |     result, unsub_fn = self._rpc.call_sync(
    |                        ^^^^^^^^^^^^^^^^^^^^
    |   File ".../protocol/rpc/spec.py", line 88, in call_sync
    |     raise result
    | RuntimeError: Isolated Python environment preparation failed (exit 1): Using CPython 3.12.3 interpreter at: ........./usr/bin/python3.12
    | Creating virtual environment at: ........./home/runner/.cache........./dimos/python-native/external_python_module-c5c794f5b247
    | Resolved 195 packages in 593ms
    | error: The lockfile at `uv.lock` needs to be updated, but `--locked` was
    |        provided.
    | 
    | hint: To update the lockfile, run `uv lock`.
    +------------------------------------
dimos.robot.manipulators.openyam.test_openyam::test_make_openyam_model_config_uses_canonical_arm_joints
Stack Traces | 3.16s 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 0xff181c2f2350>

.../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 0xff188bf36cd0>
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 0xff188bf36cd0>
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': '7237980b-89d9-4817-a0e4-c581631a1c12.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
dimos.robot.manipulators.openyam.blueprints.test_learning_collection::test_native_openyam_paths_are_configurable_from_cli
Stack Traces | 3.31s run time
value = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xff181e012990>

    def _copy_opaque(value: Any) -> Any:
        try:
>           return copy.deepcopy(value)

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

.../coordination/blueprint_config/values.py:113: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
........................................../usr/lib/python3.12/copy.py:162: in deepcopy
    y = _reconstruct(x, memo, *rv)
        _nil       = []
        cls        = <class 'dimos.control.coordinator.TaskConfig'>
        copier     = None
        d          = 280479047690640
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        reductor   = <built-in method __reduce_ex__ of TaskConfig object at 0xff181e012990>
        rv         = (<function __newobj__ at 0xff19111fc5e0>, (<class 'dimos.control.coordinator.TaskConfig'>,), {'auto_start': False, 'jo...lter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}, None, None)
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xff181e012990>
        y          = []
........................................../usr/lib/python3.12/copy.py:259: in _reconstruct
    state = deepcopy(state, memo)
        args       = <generator object _reconstruct.<locals>.<genexpr> at 0xff17fa366110>
        deep       = True
        deepcopy   = <function deepcopy at 0xff1910b9e020>
        dictiter   = None
        func       = <function __newobj__ at 0xff19111fc5e0>
        listiter   = None
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        state      = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint...nt_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xff181e012990>
        y          = <[AttributeError("'TaskConfig' object has no attribute 'name'") raised in repr()] TaskConfig object at 0xff17fa4a1fa0>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xff191099cae0>
        d          = 280478444981568
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        x          = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint...nt_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xff1910b9e020>
        key        = 'params'
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        value      = {'bindings': [{'hand': 'right', 'target_frame': 'gripper_tip'}], 'joint_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}
        x          = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint...nt_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}
        y          = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], 'name': 'teleop_openyam', 'priority': 10, ...}
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xff191099cae0>
        d          = 280479044271104
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        x          = {'bindings': [{'hand': 'right', 'target_frame': 'gripper_tip'}], 'joint_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xff1910b9e020>
        key        = 'robot_model'
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        value      = <[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 0xff181e261f40>
        x          = {'bindings': [{'hand': 'right', 'target_frame': 'gripper_tip'}], 'joint_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}
        y          = {}
........................................../usr/lib/python3.12/copy.py:143: in deepcopy
    y = copier(memo)
        _nil       = []
        cls        = <class 'dimos.manipulation.planning.spec.config.RobotModelConfig'>
        copier     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...scription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] method object at 0xff180debc540>
        d          = 280479050112832
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        x          = <[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 0xff181e261f40>
        y          = []
.venv/lib/python3.12.../site-packages/pydantic/main.py:978: in __deepcopy__
    _object_setattr(m, '__dict__', deepcopy(self.__dict__, memo=memo))
        cls        = <class 'dimos.manipulation.planning.spec.config.RobotModelConfig'>
        m          = RobotModelConfig()
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        self       = <[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 0xff181e261f40>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xff191099cae0>
        d          = 280479043920448
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        x          = {'auto_convert_meshes': True, 'base_link': 'base', 'base_pose': Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), 'collision_exclusion_pairs': [], ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xff1910b9e020>
        key        = 'model'
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xff181e0135c0>
        x          = {'auto_convert_meshes': True, 'base_link': 'base', 'base_pose': Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), 'collision_exclusion_pairs': [], ...}
        y          = {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': GlobalConfig(robot_ip=None, robot_ips=N...dless=True, local_relay=False, relay_url=None, dimos_cloud_url='https://api.dimensional.org', dimos_api_key=None), ...}
........................................../usr/lib/python3.12/copy.py:162: in deepcopy
    y = _reconstruct(x, memo, *rv)
        _nil       = []
        cls        = <class 'dimos.robot.assets.model.RobotModel'>
        copier     = None
        d          = 280479047693760
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        reductor   = <built-in method __reduce_ex__ of RobotModel object at 0xff181e0135c0>
        rv         = (<function __newobj__ at 0xff19111fc5e0>, (<class 'dimos.robot.assets.model.RobotModel'>,), {'_fixed_frames': (), '_fi...xclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff188bf36bd0>),), ...}, None, None)
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xff181e0135c0>
        y          = []
........................................../usr/lib/python3.12/copy.py:259: in _reconstruct
    state = deepcopy(state, memo)
        args       = <generator object _reconstruct.<locals>.<genexpr> at 0xff17fa366980>
        deep       = True
        deepcopy   = <function deepcopy at 0xff1910b9e020>
        dictiter   = None
        func       = <function __newobj__ at 0xff19111fc5e0>
        listiter   = None
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        state      = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('yam_description', <[Runt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff188bf36bd0>),), ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xff181e0135c0>
        y          = <[AttributeError("'RobotModel' object has no attribute '_source_path'") raised in repr()] RobotModel object at 0xff1880b4b290>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xff191099cae0>
        d          = 280480930614656
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        x          = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('yam_description', <[Runt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff188bf36bd0>),), ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xff1910b9e020>
        key        = '_source_path'
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        value      = <[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 0xff188bf36cd0>
        x          = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('yam_description', <[Runt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff188bf36bd0>),), ...}
        y          = {}
........................................../usr/lib/python3.12/copy.py:141: in deepcopy
    copier = getattr(x, "__deepcopy__", None)
        _nil       = []
        cls        = <class 'dimos.utils.data.LfsPath'>
        copier     = None
        d          = 280480892284112
        memo       = {280478444959232: [['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], {'build': 864...one), ...}, 280479044256064: ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], ...}
        x          = <[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 0xff188bf36cd0>
        y          = []
dimos/utils/data.py:370: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '__deepcopy__'
        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 0xff188bf36cd0>
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 0xff188bf36cd0>
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': '7237980b-89d9-4817-a0e4-c581631a1c12.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

The above exception was the direct cause of the following exception:

    def test_native_openyam_paths_are_configurable_from_cli() -> None:
>       parsed = BlueprintConfigParser(learning_collect_quest_openyam).parse(
            [
                "--nativecollectionrecorder.store.path",
                "/tmp/native-openyam.mcap",
                "--WristCamera.webcam.camera-index",
                ".../v4l/by-id/usb-wrist-camera",
                "--task",
                "pick up the red block",
            ],
            environ={},
        )


.../openyam/blueprints/test_learning_collection.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../coordination/blueprint_config/parser.py:168: in parse
    key: plain(value)
        cli_tokens = ['--nativecollectionrecorder.store.path', '/tmp/native-openyam.mcap', '--WristCamera.webcam.camera-index', '.../v4l/by-id/usb-wrist-camera', '--task', 'pick up the red block']
        config_path = None
        environ    = {}
        global_overrides = None
        overrides  = None
        schema     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ion.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] ParserSchema object at 0xff17fa149300>
        self       = <dimos.core.coordination.blueprint_config.parser.BlueprintConfigParser object at 0xff17fa335190>
.../coordination/blueprint_config/values.py:50: in plain
    return [plain(item) for item in value]
        value      = [<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attemp...yam_joint6', 'arm/gripper'], priority=20, auto_start=False, params={'start_position_tolerance': 0.05}, stream_bind={})]
.../coordination/blueprint_config/values.py:55: in plain
    return _copy_opaque(value)
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xff181e012990>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

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

    def _copy_opaque(value: Any) -> Any:
        try:
            return copy.deepcopy(value)
        except Exception as error:
>           raise BlueprintConfigError(
                f"Configuration value of type {type(value).__name__} cannot be copied safely: {error}"
            ) from error
E           dimos.core.coordination.blueprint_config.errors.BlueprintConfigError: Configuration value of type TaskConfig cannot be copied safely: 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.

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

.../coordination/blueprint_config/values.py:115: BlueprintConfigError
dimos.core.test_isolated_python_end_to_end::test_example_script_exits_after_printing_results
Stack Traces | 4.34s run time
def test_example_script_exits_after_printing_results() -> None:
        repository = Path(__file__).parents[2]
    
        result = subprocess.run(
            [sys.executable, "examples/external_python_module/run.py"],
            cwd=repository,
            capture_output=True,
            text=True,
            timeout=30,
        )
    
>       assert result.returncode == 0, result.stderr
E       AssertionError:   + Exception Group Traceback (most recent call last):
E           |   File ".../examples/external_python_module/run.py", line 38, in <module>
E           |     run_example()
E           |   File ".../examples/external_python_module/run.py", line 22, in run_example
E           |     coordinator = ModuleCoordinator.build(
E           |                   ^^^^^^^^^^^^^^^^^^^^^^^^
E           |   File ".../core/coordination/module_coordinator.py", line 377, in build
E           |     coordinator.build_all_modules()
E           |   File ".../core/coordination/module_coordinator.py", line 249, in build_all_modules
E           |     safe_thread_map(modules, lambda m: m.build())
E           |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
E           |     raise ExceptionGroup("safe_thread_map failed", errors)
E           | ExceptionGroup: safe_thread_map failed (1 sub-exception)
E           +-+---------------- 1 ----------------
E             | dimos.protocol.rpc.rpc_utils.RemoteError: [Remote builtins.RuntimeError] Isolated Python environment preparation failed (exit 1): Resolved 195 packages in 47ms
E         error: The lockfile at `uv.lock` needs to be updated, but `--locked` was
E                provided.
E         
E         hint: To update the lockfile, run `uv lock`.
E         
E         Remote traceback:
E         Traceback (most recent call last):
E           File ".../protocol/rpc/zenohrpc.py", line 199, in _execute_rpc
E             response = f(*args[0], **args[1])
E                        ^^^^^^^^^^^^^^^^^^^^^^
E           File ".../protocol/rpc/spec.py", line 116, in override_f
E             return getattr(module, fname)(*args, **kwargs)
E                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           File ".../dimos/core/isolated_python_module.py", line 211, in build
E             super().build()
E           File ".../dimos/core/native_module.py", line 236, in build
E             self._maybe_build()
E           File ".../dimos/core/isolated_python_module.py", line 207, in _maybe_build
E             self._run_prepare()
E           File ".../dimos/core/isolated_python_module.py", line 127, in _run_prepare
E             raise RuntimeError(
E         RuntimeError: Isolated Python environment preparation failed (exit 1): Resolved 195 packages in 47ms
E         error: The lockfile at `uv.lock` needs to be updated, but `--locked` was
E                provided.
E         
E         hint: To update the lockfile, run `uv lock`.
E         
E             | 
E             | The above exception was the direct cause of the following exception:
E             | 
E             | Traceback (most recent call last):
E             |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
E             |     outcomes[idx] = fut.result()
E             |                     ^^^^^^^^^^^^
E             |   File ".............../usr/lib/python3.12........./concurrent/futures/_base.py", line 449, in result
E             |     return self.__get_result()
E             |            ^^^^^^^^^^^^^^^^^^^
E             |   File ".............../usr/lib/python3.12........./concurrent/futures/_base.py", line 401, in __get_result
E             |     raise self._exception
E             |   File ".............../usr/lib/python3.12....../concurrent/futures/thread.py", line 58, in run
E             |     result = self.fn(*self.args, **self.kwargs)
E             |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E             |   File ".../core/coordination/module_coordinator.py", line 249, in <lambda>
E             |     safe_thread_map(modules, lambda m: m.build())
E             |                                        ^^^^^^^^^
E             |   File ".../dimos/core/rpc_client.py", line 93, in __call__
E             |     result, unsub_fn = self._rpc.call_sync(
E             |                        ^^^^^^^^^^^^^^^^^^^^
E             |   File ".../protocol/rpc/spec.py", line 88, in call_sync
E             |     raise result
E             | RuntimeError: Isolated Python environment preparation failed (exit 1): Resolved 195 packages in 47ms
E         error: The lockfile at `uv.lock` needs to be updated, but `--locked` was
E                provided.
E         
E         hint: To update the lockfile, run `uv lock`.
E             +------------------------------------
E         
E       assert 1 == 0
E        +  where 1 = CompletedProcess(args=['.../dimos/dimos/.venv/bin/python', 'examples/external_python_module/run.py'], returncode=1, stdout="05:22:42.826 [inf][dination/module_coordinator.py] Building the blueprint\n05:22:42.828 [inf][ce/system_configurator/base.py] Pytest run detected: skipping system configuration.\n05:22:42.828 [inf][dination/module_coordinator.py] Starting the modules\n05:22:44.135 [inf][ation/worker_manager_python.py] Worker pool started. n_workers=2\n05:22:45.457 [inf][otocol/service/zenohservice.py] Zenoh session opened connect=[] gossip=True listen=['tcp/127.0.0.1:0'] mode=peer multicast_interface=lo\n05:22:45.460 [inf][otocol/service/zenohservice.py] Zenoh session opened connect=[] gossip=True listen=['tcp/127.0.0.1:0'] mode=peer multicast_interface=lo\n05:22:45.463 [inf][/coordination/python_worker.py] Deployed module. module=ExampleExternal module_id=0 worker_id=0\n05:22:45.466 [inf][/coordination/python_worker.py] Deployed module. module=Offset module_id=1 worker_id=1\n05:22:45.482 [inf][otocol/service/zenohservice.py] Zenoh session opened connect=[] gossip=True listen=['tcp/127.0.0.1:0'] mode=peer multicast_interface=lo\n05:22:45.597 [err][dimos/protoco...esult()\n    |            ^^^^^^^^^^^^^^^^^^^\n    |   File ".............../usr/lib/python3.12........./concurrent/futures/_base.py", line 401, in __get_result\n    |     raise self._exception\n    |   File ".............../usr/lib/python3.12....../concurrent/futures/thread.py", line 58, in run\n    |     result = self.fn(*self.args, **self.kwargs)\n    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    |   File ".../core/coordination/module_coordinator.py", line 249, in <lambda>\n    |     safe_thread_map(modules, lambda m: m.build())\n    |                                        ^^^^^^^^^\n    |   File ".../dimos/core/rpc_client.py", line 93, in __call__\n    |     result, unsub_fn = self._rpc.call_sync(\n    |                        ^^^^^^^^^^^^^^^^^^^^\n    |   File ".../protocol/rpc/spec.py", line 88, in call_sync\n    |     raise result\n    | RuntimeError: Isolated Python environment preparation failed (exit 1): Resolved 195 packages in 47ms\nerror: The lockfile at `uv.lock` needs to be updated, but `--locked` was\n       provided.\n\nhint: To update the lockfile, run `uv lock`.\n    +------------------------------------\n').returncode

repository = PosixPath('.../work/dimos/dimos')
result     = CompletedProcess(args=['.../dimos/dimos/.venv/bin/python', 'examples/external_python_module/run.py'], re...d` was\n       provided.\n\nhint: To update the lockfile, run `uv lock`.\n    +------------------------------------\n')

dimos/core/test_isolated_python_end_to_end.py:98: AssertionError
dimos.codebase_checks.test_import_from_source::test_import_from_source
Stack Traces | 8.65s run time
def test_import_from_source() -> None:
        """Fail if any name is imported from a module that only re-imported it."""
        violations = find_reexport_imports()
        if violations:
            listing = "\n".join(
                f"  - {p.relative_to(DIMOS_PROJECT_ROOT)}:{line}: `{name}` imported from "
                f"{src}, but defined in {origin}"
                for p, line, name, src, origin in sorted(violations)
            )
>           raise AssertionError(
                f"Found import(s) that pull a name from a re-exporter:\n{listing}\n\n"
                "Import each name straight from the module that defines it (shown above). "
                "If a module re-exports a name on purpose, mark its import with "
                "`# noqa: F401` or the `from x import Y as Y` form, and that re-export "
                "will be allowed."
            )
E           AssertionError: Found import(s) that pull a name from a re-exporter:
E             - .../teleop/quest/quest_teleop_module.py:45: `EpisodeStatus` imported from dimos.imitation.collection.episode_monitor, but defined in dimos.msgs.imitation_msgs.EpisodeStatus
E             - .../teleop/quest/test_quest_teleop_module.py:24: `EpisodeStatus` imported from dimos.imitation.collection.episode_monitor, but defined in dimos.msgs.imitation_msgs.EpisodeStatus
E           
E           Import each name straight from the module that defines it (shown above). If a module re-exports a name on purpose, mark its import with `# noqa: F401` or the `from x import Y as Y` form, and that re-export will be allowed.

listing    = '  - .../teleop/quest/quest_teleop_module.py:45: `EpisodeStatus` imported from dimos.imitation.collection.episode_mo...atus` imported from dimos.imitation.collection.episode_monitor, but defined in dimos.msgs.imitation_msgs.EpisodeStatus'
violations = [(PosixPath('/home/runner/work/dimos/dimos/.../teleop/quest/quest_teleop_module.py'), 45, 'EpisodeStatus', 'dimos.im...le.py'), 24, 'EpisodeStatus', 'dimos.imitation.collection.episode_monitor', 'dimos.msgs.imitation_msgs.EpisodeStatus')]

dimos/codebase_checks/test_import_from_source.py:148: AssertionError

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

@TomCC7
TomCC7 force-pushed the feat/openyam-native-collection branch from 17256b0 to 933facf Compare September 1, 2026 04:46
@TomCC7
TomCC7 force-pushed the feat/openyam-native-collection branch from 933facf to 0307ebb Compare September 1, 2026 05:15
@TomCC7
TomCC7 force-pushed the feat/openyam-native-collection branch 2 times, most recently from 13940ff to 275e358 Compare September 1, 2026 17:16
@TomCC7
TomCC7 force-pushed the feat/openyam-native-collection branch from 275e358 to 31dd683 Compare September 1, 2026 17:17
Comment thread dimos/experimental/memory/README.md Outdated
Comment thread dimos/robot/manipulators/openyam/blueprints/learning_collection.py Outdated
@TomCC7
TomCC7 force-pushed the feat/openyam-native-collection branch from 31dd683 to 2ca3643 Compare September 1, 2026 17:56
@TomCC7
TomCC7 force-pushed the feat/openyam-native-collection branch from 2ca3643 to c871ce7 Compare September 1, 2026 18:54
Comment thread dimos/imitation/dataprep/cli.py Outdated
Comment thread dimos/imitation/dataprep/formats/lerobot/reader.py Outdated
Comment thread dimos/hardware/sensors/camera/module.py Outdated
Comment thread dimos/imitation/dataprep/formats/lerobot/reader.py Outdated
@TomCC7
TomCC7 marked this pull request as ready for review September 2, 2026 00:50
@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds OpenYAM-native MCAP collection, records post-arbitration position commands, and adds quality validation for episode conversion.

Two recorded-data integrity failures were reproduced:

  • A partial OpenYAM position update is expanded to held targets for hardware, but the recorded applied-command message contains only the newly supplied joint. DataPrep then rejects the action because required joints are missing.
  • Fill-mode conversion accepts a required stream held for at least nine seconds, allowing long runs of stale observations or actions into an otherwise accepted episode.

The affected command recording and fill-mode validation behavior should be corrected before using this path for training-data collection.

Confidence Score: 3/5

Not safe to merge for OpenYAM data collection until recorded applied commands include held targets and fill mode limits stale required streams.

Focused executable reproductions confirmed two independent data-integrity failures in the collection and conversion paths.

Files Needing Attention: dimos/control/tick_loop.py needs complete accepted position targets published for recording; dimos/imitation/dataprep/core.py needs a bounded forward-fill policy for required streams.

T-Rex T-Rex Logs

What T-Rex did

  • A focused OpenYAM partial-command repro was prepared for a posted P1 finding, and its source and output artifacts were created.
  • A separate executable repro source for a stopped required stream in fill mode was prepared, along with its successful reproduction output.
  • A third P1 finding proof was produced, but no additional artifacts were attached.
  • A general contract validation run was executed, recording the exact command and observed results for the fill-stale-stream test.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Fill mode accepts indefinitely stale required stream samples

    • Bug
      • A required non-anchor stream with its last and only sample at t=0.0 is emitted unchanged for anchor targets through t=9.0. The quality inspector reports the ten-frame episode valid despite nine forward-filled frames and a 9,000 ms final alignment age.
    • Cause
      • In fill mode, _alignment_plan chooses the most recent causal sample whenever no nearby sample exists (previous = bisect.bisect_right(values, target) - 1) and imposes no maximum age before accepting it. inspect_episode_quality then reuses that plan and only rejects incompleteness in strict mode.
    • Fix
      • Add and enforce a maximum forward-fill age for each required stream (or reuse a documented alignment-age limit) in _alignment_plan; make inspect_episode_quality reject fill-mode episodes whose held sample exceeds that bound.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "spec: remove" | Re-trigger Greptile

Comment on lines +444 to +448
for commands, mode in accepted_commands.values():
if mode not in (ControlMode.POSITION, ControlMode.SERVO_POSITION):
continue
names.extend(commands)
positions.extend(commands.values())

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 Applied-command events omit held joint targets

A partial OpenYAM position write is expanded by the whole-body hardware interface using its previously commanded values, but this callback publishes only the partial commands dictionary. Consequently, a gripper-only or single-joint update records an action missing the other configured OpenYAM joints, and DataPrep rejects the episode with JointState is missing configured joints. Publish the complete position target that hardware accepted, including held joints.

Artifacts

Focused OpenYAM partial-command repro source

  • This review-authored script performs two accepted partial OpenYAM whole-body writes, publishes the second applied command, and submits it to the configured DataPrep action validator.

Focused OpenYAM partial-command repro output

  • This executed-command log shows the hardware held and wrote all seven targets while the callback published only yam_joint2, which DataPrep rejected for six missing configured joints.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment on lines +384 to +389
previous = bisect.bisect_right(values, target) - 1
if quality.mode == "fill" and previous >= 0:
indices[key] = previous
max_error_s = max(max_error_s, abs(values[previous] - target))
filled = True
continue

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 Fill mode accepts indefinitely stale required data

When a required stream stops publishing, fill mode continually selects its last causal sample without any age limit. The quality check still accepts the episode, so a dataset can contain arbitrarily long runs of stale observations or actions. Enforce a maximum forward-fill age and reject frames or episodes once the held value exceeds it.

Artifacts

Executable repro source for a stopped required stream in fill mode

  • Review-authored source constructs ten anchor frames and one required-stream frame, then executes the real sampler and quality inspector to assert stale emission and acceptance.

Successful stopped-stream fill-mode reproduction output

  • Captured execution output shows ten frames, nine forward fills, a nine-second stale required value, and a valid quality report, confirming the failure.

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