Skip to content

feat(manipulation): add Dual OpenYAM teleoperation - #3463

Open
TomCC7 wants to merge 22 commits into
pim/feat/g1-quest-teleop-stackedfrom
cc/feat/dual-yam
Open

feat(manipulation): add Dual OpenYAM teleoperation#3463
TomCC7 wants to merge 22 commits into
pim/feat/g1-quest-teleop-stackedfrom
cc/feat/dual-yam

Conversation

@TomCC7

@TomCC7 TomCC7 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Summary

  • add a Dual OpenYAM entity with two verified OpenYAM arm models at the ABC Box 620 mm spacing
  • add mock and dual-CAN hardware support, coupled planning groups, and Quest teleoperation
  • align teleop velocity limits and Pink tuning with A1Z
  • document fake-hardware and physical-hardware startup

Testing

  • 83 focused manipulation, Pink IK, G1 Quest, and Dual OpenYAM tests pass after rebasing
  • Ruff passes
  • mypy passes
  • uv run dimos run teleop-quest-dual-openyam starts successfully with mock hardware

Stack

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

❌ 5 Tests Failed:

Tests completed Failed Passed Skipped
4658 5 4653 208
View the top 3 failed test(s) by shortest run time
dimos.hardware.whole_body.dual_openyam_damiao.test_adapter::test_adapter_connects_complete_dual_yam_topology
Stack Traces | 0.002s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff22bdb65940>

    @pytest.fixture
    def adapter(mocker: MockerFixture) -> Iterator[DualOpenYamDamiaoAdapter]:
        mocker.patch.object(can_motor_control, "SocketCanBus", can_motor_control.MockCanBus)
>       result = DualOpenYamDamiaoAdapter(
            runtime_config=DamiaoRuntimeConfig(
                bus_addresses={"left": "can8", "right": "can9"},
                gravity_comp=False,
            )
        )

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff22bdb65940>

.../whole_body/dual_openyam_damiao/test_adapter.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.whole_body.dual_openyam_damiao.adapter.DualOpenYamDamiaoAdapter object at 0xff22bdb644d0>
address = None

    def __init__(
        self,
        address: str | Path | None = None,
        *,
        runtime_config: DamiaoRuntimeConfig | Mapping[str, Any] | None = None,
        dof: int | None = None,
        hardware_id: str = "whole_body",
        domain_id: int = 0,
    ) -> None:
        """Initialize runtime settings for a subclass-declared Damiao topology.
    
        ``address`` is accepted for the coordinator's common adapter factory
        convention, but one scalar cannot represent a multi-bus whole body.
        Configure physical CAN interfaces by logical bus name through
        ``runtime_config.bus_addresses`` instead.
        """
        del domain_id
        if address is not None:
            raise ValueError("configure Damiao CAN buses through runtime_config.bus_addresses")
        if runtime_config is None:
            config = DamiaoRuntimeConfig()
        elif isinstance(runtime_config, DamiaoRuntimeConfig):
            config = runtime_config
        else:
            config = DamiaoRuntimeConfig(**runtime_config)
        unknown_buses = config.bus_addresses.keys() - self.bus_defaults.keys()
        if unknown_buses:
>           raise ValueError(f"unknown CAN bus overrides: {sorted(unknown_buses)}")
E           ValueError: unknown CAN bus overrides: ['left', 'right']

address    = None
config     = DamiaoRuntimeConfig(bus_addresses={'left': 'can8', 'right': 'can9'}, gravity_comp=False, tick_deadline_us=1000)
dof        = None
hardware_id = 'whole_body'
runtime_config = DamiaoRuntimeConfig(bus_addresses={'left': 'can8', 'right': 'can9'}, gravity_comp=False, tick_deadline_us=1000)
self       = <dimos.hardware.whole_body.dual_openyam_damiao.adapter.DualOpenYamDamiaoAdapter object at 0xff22bdb644d0>
unknown_buses = {'left', 'right'}

.../whole_body/damiao/adapter.py:77: ValueError
dimos.codebase_checks.test_blueprint_kwargs::test_blueprint_atom_kwargs_match_module_config[teleop-quest-dual-openyam]
Stack Traces | 0.003s run time
blueprint_name = 'teleop-quest-dual-openyam'

    @pytest.mark.parametrize("blueprint_name", _blueprint_params())
    def test_blueprint_atom_kwargs_match_module_config(blueprint_name: str) -> None:
        """Fail when blueprint kwargs cannot be consumed by their target module."""
        blueprint = _get_blueprint_or_skip(blueprint_name)
    
        violations: list[str] = []
        for atom in blueprint.blueprints:
            unknown_kwargs = sorted(set(atom.kwargs) - _allowed_kwarg_names(atom.module))
            if unknown_kwargs:
                violations.append(
                    f"{atom.module.__module__}.{atom.module.__name__}: unknown kwargs {unknown_kwargs}"
                )
    
        if violations:
            listing = "\n".join(f"  - {violation}" for violation in violations)
>           raise AssertionError(
                f"Blueprint {blueprint_name!r} passes unknown module kwargs:\n{listing}\n\n"
                "Blueprint kwargs are forwarded into the module constructor. For modules "
                "with an explicit `config` annotation, use fields from that config model; "
                "for legacy modules with direct constructor parameters, use the declared "
                "`__init__` keyword names."
            )
E           AssertionError: Blueprint 'teleop-quest-dual-openyam' passes unknown module kwargs:
E             - dimos.teleop.quest.quest_extensions.ArmTeleopModule: unknown kwargs ['task_names']
E           
E           Blueprint kwargs are forwarded into the module constructor. For modules with an explicit `config` annotation, use fields from that config model; for legacy modules with direct constructor parameters, use the declared `__init__` keyword names.

atom       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] BlueprintAtom object at 0xffb7f9974920>
blueprint  = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] Blueprint object at 0xffb7f996fe00>
blueprint_name = 'teleop-quest-dual-openyam'
listing    = "  - dimos.teleop.quest.quest_extensions.ArmTeleopModule: unknown kwargs ['task_names']"
unknown_kwargs = []
violations = ["dimos.teleop.quest.quest_extensions.ArmTeleopModule: unknown kwargs ['task_names']"]

dimos/codebase_checks/test_blueprint_kwargs.py:103: AssertionError
dimos.robot.manipulators.dual_openyam.test_teleop_ik::test_quest_solver_matches_a1z_target_tracking_speed
Stack Traces | 0.003s run time
self = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

    def __hash__(self):
        try:
>           return self._hash

self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

....../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/pathlib.py:526: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
name = '_hash'

    def __getattribute__(self, name: str) -> object:
        # During Path.__new__(), _lfs_filename hasn't been set yet.
        # Fall through to normal Path behavior until construction is complete.
        try:
            object.__getattribute__(self, "_lfs_filename")
        except AttributeError:
            return object.__getattribute__(self, name)
    
        # After construction, allow access to our internal attributes directly
        if name in ("_lfs_filename", "_lfs_resolved_cache", "_ensure_downloaded"):
            return object.__getattribute__(self, name)
    
        # For all other attributes, ensure download first then delegate to resolved path
        resolved = object.__getattribute__(self, "_ensure_downloaded")()
>       return getattr(resolved, name)
E       AttributeError: 'PosixPath' object has no attribute '_hash'

name       = '_hash'
resolved   = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

dimos/utils/data.py:371: AttributeError

During handling of the above exception, another exception occurred:

    @pytest.mark.self_hosted
    def test_quest_solver_matches_a1z_target_tracking_speed() -> None:
>       solver = _solver()


.../manipulators/dual_openyam/test_teleop_ik.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/dual_openyam/test_teleop_ik.py:47: in _solver
    return DualOpenYamPinkPoseTargetSolver(config)
        config     = PoseTargetIKTaskConfig(joint_names=('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_...ilter_cutoff_hz=30.0, max_command_tracking_error_deg=10.0, feedback_limit_tolerance=0.001, command_limit_margin=0.0001)
        task       = TaskConfig(name='teleop_dual_openyam', type='teleop_ik', joint_names=['left_arm/joint1', 'left_arm/joint2', 'left_arm/...nd_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, 'joint_command_filter_cutoff_hz': 30.0}, stream_bind={})
.../control/tasks/pose_target_ik.py:277: in __init__
    self._validate_frame_targets(
        __class__  = <class 'dimos.control.tasks.pose_target_ik.PinkPoseTargetSolver'>
        config     = PoseTargetIKTaskConfig(joint_names=('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_...ilter_cutoff_hz=30.0, max_command_tracking_error_deg=10.0, feedback_limit_tolerance=0.001, command_limit_margin=0.0001)
        self       = <dimos.robot.manipulators.dual_openyam.teleop_ik.DualOpenYamPinkPoseTargetSolver object at 0x73bcc522bcb0>
.../control/tasks/pose_target_ik.py:491: in _validate_frame_targets
    context = self._get_control_context(robot_model, frames, joints)
        command_limit_margin = 0.0001
        controlled_joints = ('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...)
        frame_names = ('left_grasp_frame', 'right_grasp_frame')
        frames     = ('left_grasp_frame', 'right_grasp_frame')
        joints     = ('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...)
        robot_model = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...extra_links=[], home_joints=[0.0, 1.047, 1.047, 0.0, 0.0, 0.0, 0.0, 1.047, 1.047, 0.0, 0.0, 0.0], pre_grasp_offset=0.1)
        self       = <dimos.robot.manipulators.dual_openyam.teleop_ik.DualOpenYamPinkPoseTargetSolver object at 0x73bcc522bcb0>
.../control/tasks/pose_target_ik.py:536: in _get_control_context
    if cache_key not in self._control_contexts:
        cache_key  = (RobotModel(_source_path=PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf'), _package_paths=...'left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...))
        config     = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...extra_links=[], home_joints=[0.0, 1.047, 1.047, 0.0, 0.0, 0.0, 0.0, 1.047, 1.047, 0.0, 0.0, 0.0], pre_grasp_offset=0.1)
        controlled_joints = ('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...)
        frame_names = ('left_grasp_frame', 'right_grasp_frame')
        frames     = ('left_grasp_frame', 'right_grasp_frame')
        self       = <dimos.robot.manipulators.dual_openyam.teleop_ik.DualOpenYamPinkPoseTargetSolver object at 0x73bcc522bcb0>
<string>:3: in __hash__
    ???
        self       = RobotModel(_source_path=PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf'), _package_paths=(...dimos/data/dual_openyam_abc_box_v2')),), _xacro_args=(), _fixed_frames=(), _fixed_joints=(), _joint_position_limits=())
....../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/pathlib.py:529: in __hash__
    return self._hash
        self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
name = '_hash'

    def __getattribute__(self, name: str) -> object:
        # During Path.__new__(), _lfs_filename hasn't been set yet.
        # Fall through to normal Path behavior until construction is complete.
        try:
            object.__getattribute__(self, "_lfs_filename")
        except AttributeError:
            return object.__getattribute__(self, name)
    
        # After construction, allow access to our internal attributes directly
        if name in ("_lfs_filename", "_lfs_resolved_cache", "_ensure_downloaded"):
            return object.__getattribute__(self, name)
    
        # For all other attributes, ensure download first then delegate to resolved path
        resolved = object.__getattribute__(self, "_ensure_downloaded")()
>       return getattr(resolved, name)
E       AttributeError: 'PosixPath' object has no attribute '_hash'

name       = '_hash'
resolved   = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

dimos/utils/data.py:371: AttributeError
dimos.robot.manipulators.dual_openyam.test_teleop_ik::test_solver_uses_nominal_posture_without_manipulability
Stack Traces | 0.003s run time
self = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

    def __hash__(self):
        try:
>           return self._hash

self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

....../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/pathlib.py:526: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
name = '_hash'

    def __getattribute__(self, name: str) -> object:
        # During Path.__new__(), _lfs_filename hasn't been set yet.
        # Fall through to normal Path behavior until construction is complete.
        try:
            object.__getattribute__(self, "_lfs_filename")
        except AttributeError:
            return object.__getattribute__(self, name)
    
        # After construction, allow access to our internal attributes directly
        if name in ("_lfs_filename", "_lfs_resolved_cache", "_ensure_downloaded"):
            return object.__getattribute__(self, name)
    
        # For all other attributes, ensure download first then delegate to resolved path
        resolved = object.__getattribute__(self, "_ensure_downloaded")()
>       return getattr(resolved, name)
E       AttributeError: 'PosixPath' object has no attribute '_hash'

name       = '_hash'
resolved   = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

dimos/utils/data.py:371: AttributeError

During handling of the above exception, another exception occurred:

    @pytest.mark.self_hosted
    def test_solver_uses_nominal_posture_without_manipulability() -> None:
>       solver = _solver()


.../manipulators/dual_openyam/test_teleop_ik.py:52: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/dual_openyam/test_teleop_ik.py:47: in _solver
    return DualOpenYamPinkPoseTargetSolver(config)
        config     = PoseTargetIKTaskConfig(joint_names=('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_...ilter_cutoff_hz=30.0, max_command_tracking_error_deg=10.0, feedback_limit_tolerance=0.001, command_limit_margin=0.0001)
        task       = TaskConfig(name='teleop_dual_openyam', type='teleop_ik', joint_names=['left_arm/joint1', 'left_arm/joint2', 'left_arm/...nd_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, 'joint_command_filter_cutoff_hz': 30.0}, stream_bind={})
.../control/tasks/pose_target_ik.py:277: in __init__
    self._validate_frame_targets(
        __class__  = <class 'dimos.control.tasks.pose_target_ik.PinkPoseTargetSolver'>
        config     = PoseTargetIKTaskConfig(joint_names=('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_...ilter_cutoff_hz=30.0, max_command_tracking_error_deg=10.0, feedback_limit_tolerance=0.001, command_limit_margin=0.0001)
        self       = <dimos.robot.manipulators.dual_openyam.teleop_ik.DualOpenYamPinkPoseTargetSolver object at 0x73bcc487e2a0>
.../control/tasks/pose_target_ik.py:491: in _validate_frame_targets
    context = self._get_control_context(robot_model, frames, joints)
        command_limit_margin = 0.0001
        controlled_joints = ('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...)
        frame_names = ('left_grasp_frame', 'right_grasp_frame')
        frames     = ('left_grasp_frame', 'right_grasp_frame')
        joints     = ('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...)
        robot_model = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...extra_links=[], home_joints=[0.0, 1.047, 1.047, 0.0, 0.0, 0.0, 0.0, 1.047, 1.047, 0.0, 0.0, 0.0], pre_grasp_offset=0.1)
        self       = <dimos.robot.manipulators.dual_openyam.teleop_ik.DualOpenYamPinkPoseTargetSolver object at 0x73bcc487e2a0>
.../control/tasks/pose_target_ik.py:536: in _get_control_context
    if cache_key not in self._control_contexts:
        cache_key  = (RobotModel(_source_path=PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf'), _package_paths=...'left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...))
        config     = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...extra_links=[], home_joints=[0.0, 1.047, 1.047, 0.0, 0.0, 0.0, 0.0, 1.047, 1.047, 0.0, 0.0, 0.0], pre_grasp_offset=0.1)
        controlled_joints = ('left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...)
        frame_names = ('left_grasp_frame', 'right_grasp_frame')
        frames     = ('left_grasp_frame', 'right_grasp_frame')
        self       = <dimos.robot.manipulators.dual_openyam.teleop_ik.DualOpenYamPinkPoseTargetSolver object at 0x73bcc487e2a0>
<string>:3: in __hash__
    ???
        self       = RobotModel(_source_path=PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf'), _package_paths=(...dimos/data/dual_openyam_abc_box_v2')),), _xacro_args=(), _fixed_frames=(), _fixed_joints=(), _joint_position_limits=())
....../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/pathlib.py:529: in __hash__
    return self._hash
        self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
name = '_hash'

    def __getattribute__(self, name: str) -> object:
        # During Path.__new__(), _lfs_filename hasn't been set yet.
        # Fall through to normal Path behavior until construction is complete.
        try:
            object.__getattribute__(self, "_lfs_filename")
        except AttributeError:
            return object.__getattribute__(self, name)
    
        # After construction, allow access to our internal attributes directly
        if name in ("_lfs_filename", "_lfs_resolved_cache", "_ensure_downloaded"):
            return object.__getattribute__(self, name)
    
        # For all other attributes, ensure download first then delegate to resolved path
        resolved = object.__getattribute__(self, "_ensure_downloaded")()
>       return getattr(resolved, name)
E       AttributeError: 'PosixPath' object has no attribute '_hash'

name       = '_hash'
resolved   = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')
self       = PosixPath('.../data/dual_openyam_abc_box_v2/dual_openyam.urdf')

dimos/utils/data.py:371: AttributeError
dimos.robot.manipulators.dual_openyam.test_blueprints::test_quest_blueprint_selects_physical_hardware_from_both_can_ports
Stack Traces | 3.23s run time
value = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffb7f996ea80>

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

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

.../coordination/blueprint_config/values.py:148: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
........................................../usr/lib/python3.12/copy.py:162: in deepcopy
    y = _reconstruct(x, memo, *rv)
        _nil       = []
        cls        = <class 'dimos.control.coordinator.TaskConfig'>
        copier     = None
        d          = 281165631515264
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        reductor   = <built-in method __reduce_ex__ of TaskConfig object at 0xffb7f996ea80>
        rv         = (<function __newobj__ at 0xffb8ec4b05e0>, (<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/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffb7f996ea80>
        y          = []
........................................../usr/lib/python3.12/copy.py:259: in _reconstruct
    state = deepcopy(state, memo)
        args       = <generator object _reconstruct.<locals>.<genexpr> at 0xffb787fd3f10>
        deep       = True
        deepcopy   = <function deepcopy at 0xffb8ebe72020>
        dictiter   = None
        func       = <function __newobj__ at 0xffb8ec4b05e0>
        listiter   = None
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        state      = {'auto_start': False, 'joint_names': ['left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'lef...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/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffb7f996ea80>
        y          = <[AttributeError("'TaskConfig' object has no attribute 'name'") raised in repr()] TaskConfig object at 0xffb7847592b0>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffb8ebca0ae0>
        d          = 281167239149248
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'auto_start': False, 'joint_names': ['left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'lef...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 0xffb8ebe72020>
        key        = 'params'
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = {'bindings': [{'hand': 'left', 'target_frame': 'left_grasp_frame'}, {'hand': 'right', 'target_frame': 'right_grasp_fra..., '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': ['left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'lef...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': ['left_arm/joint1', 'left_arm/joint2', 'left_arm/joint3', 'left_arm/joint4', 'left_arm/joint5', 'left_arm/joint6', ...], 'name': 'teleop_dual_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 0xffb8ebca0ae0>
        d          = 281165620354048
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'bindings': [{'hand': 'left', 'target_frame': 'left_grasp_frame'}, {'hand': 'right', 'target_frame': 'right_grasp_fra..., '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 0xffb8ebe72020>
        key        = 'robot_model'
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xffb7f955e940>
        x          = {'bindings': [{'hand': 'left', 'target_frame': 'left_grasp_frame'}, {'hand': 'right', 'target_frame': 'right_grasp_fra..., '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/dual_openyam_abc_box_v2.tar.gz after 3...bc_box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] method object at 0xffb8596f4b80>
        d          = 281165627255104
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xffb7f955e940>
        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       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xffb7f955e940>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffb8ebca0ae0>
        d          = 281165620351104
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'auto_convert_meshes': True, 'base_link': 'dual_openyam_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 0xffb8ebe72020>
        key        = 'model'
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xffb867957d70>
        x          = {'auto_convert_meshes': True, 'base_link': 'dual_openyam_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...ess=True, local_relay=False, relay_url=None, dimos_cloud_url='https://login.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          = 281167476915568
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        reductor   = <built-in method __reduce_ex__ of RobotModel object at 0xffb867957d70>
        rv         = (<function __newobj__ at 0xffb8ec4b05e0>, (<class 'dimos.robot.assets.model.RobotModel'>,), {'_fixed_frames': (), '_fi...xclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516850>),), ...}, None, None)
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xffb867957d70>
        y          = []
........................................../usr/lib/python3.12/copy.py:259: in _reconstruct
    state = deepcopy(state, memo)
        args       = <generator object _reconstruct.<locals>.<genexpr> at 0xffb858ab5d50>
        deep       = True
        deepcopy   = <function deepcopy at 0xffb8ebe72020>
        dictiter   = None
        func       = <function __newobj__ at 0xffb8ec4b05e0>
        listiter   = None
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        state      = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('dual_openyam_abc_box', <...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516850>),), ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xffb867957d70>
        y          = <[AttributeError("'RobotModel' object has no attribute '_source_path'") raised in repr()] RobotModel object at 0xffb78475a5d0>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffb8ebca0ae0>
        d          = 281165615248640
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('dual_openyam_abc_box', <...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516850>),), ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xffb8ebe72020>
        key        = '_source_path'
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...c_box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516ad0>
        x          = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('dual_openyam_abc_box', <...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516850>),), ...}
        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          = 281167472454352
        memo       = {281165615248640: {}, 281165620351104: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': ...sional.org', dimos_api_key=None), ...}, 281165620354048: {}, 281165620358080: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...c_box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516ad0>
        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/dual_openyam_abc_box_v2.tar.gz after 3...c_box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516ad0>
dimos/utils/data.py:353: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'dual_openyam_abc_box_v2/dual_openyam.urdf'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3...c_box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffb867516ad0>
dimos/utils/data.py:310: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'dual_openyam_abc_box_v2'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/dual_openyam_abc_box_v2/dual_openyam.urdf')
        name       = 'dual_openyam_abc_box_v2/dual_openyam.urdf'
        nested_path = PosixPath('dual_openyam.urdf')
        path_parts = ('dual_openyam_abc_box_v2', 'dual_openyam.urdf')
dimos/utils/data.py:254: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz')
        filename   = 'dual_openyam_abc_box_v2'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/data/.lfs/dual_openyam_abc_box_v2.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/dual_openyam_abc_box_v2.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/dual_openyam_abc_box_v2.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': '4024f162-5146-470b-a509-e3856f031b5f.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/dual_openyam_abc_box_v2.tar.gz', '--exclude='])
relative_path = PosixPath('data/.lfs/dual_openyam_abc_box_v2.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_quest_blueprint_selects_physical_hardware_from_both_can_ports() -> None:
>       parsed = BlueprintConfigParser(teleop_quest_dual_openyam).parse(
            [
                "--left-can-port",
                "follower_l",
                "--right-can-port",
                "follower_r",
                "--manipulationmodule.visualization.host=0.0.0.0",
            ],
            environ={},
        )


.../manipulators/dual_openyam/test_blueprints.py:49: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../coordination/blueprint_config/parser.py:169: in parse
    key: plain(value)
        cli_tokens = ['--left-can-port', 'follower_l', '--right-can-port', 'follower_r', '--manipulationmodule.visualization.host=0.0.0.0']
        config_path = None
        environ    = {}
        global_overrides = None
        overrides  = None
        schema     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/dual_openyam_abc_box_v2.tar.gz after 3..._v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] ParserSchema object at 0xffb784b86800>
        self       = <dimos.core.coordination.blueprint_config.parser.BlueprintConfigParser object at 0xffb7858e89e0>
.../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/dual_openyam_abc_box_v2.tar.gz after ...oint5', 'right_arm/joint6'], 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/dual_openyam_abc_box_v2.tar.gz after 3...ox_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffb7f996ea80>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

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

    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/dual_openyam_abc_box_v2.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/dual_openyam_abc_box_v2.tar.gz', '--exclude=']' returned non-zero exit status 1.

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

.../coordination/blueprint_config/values.py:150: BlueprintConfigError

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 changed the base branch from cc/feat/dual-arm-teleop to pim/feat/g1-quest-teleop-stacked August 14, 2026 01:45
Comment thread dimos/control/tasks/teleop_ik_task/teleop_ik_task.py Outdated
Comment thread dimos/control/tasks/trajectory_task/trajectory_task.py Outdated
Comment thread dimos/hardware/whole_body/damiao/adapter.py Outdated
Comment thread dimos/hardware/whole_body/dual_openyam_damiao/adapter.py Outdated
Comment thread dimos/hardware/whole_body/dual_openyam_damiao/adapter.py Outdated
Comment thread dimos/hardware/whole_body/dual_openyam_damiao/adapter.py Outdated
Comment thread dimos/manipulation/planning/kinematics/pink_solver.py Outdated
Comment thread dimos/manipulation/planning/kinematics/pink_solver.py Outdated
Comment thread dimos/manipulation/planning/utils/mesh_utils.py Outdated
Comment thread dimos/manipulation/planning/utils/model_reduction.py Outdated
Comment thread dimos/manipulation/planning/world/roboplan_model.py
Comment thread dimos/manipulation/planning/world/drake_world.py
Comment thread dimos/manipulation/visualization/viser/test_visualizer_lifecycle.py
Comment thread dimos/manipulation/visualization/viser/scene.py
Comment thread dimos/robot/manipulators/dual_openyam/blueprints/teleop.py Outdated
Comment thread dimos/robot/manipulators/dual_openyam/blueprints/teleop.py Outdated
Comment thread dimos/robot/manipulators/dual_openyam/blueprints/teleop.py
Comment thread docs/capabilities/manipulation/dual_openyam_abc_box.md Outdated
Comment thread dimos/robot/manipulators/dual_openyam/test_blueprints.py Outdated
Comment thread dimos/robot/manipulators/dual_openyam/test_blueprints.py Outdated
Comment thread dimos/robot/manipulators/dual_openyam/test_blueprints.py
@TomCC7
TomCC7 marked this pull request as ready for review August 14, 2026 20:29
@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds Dual OpenYAM model loading, dual-CAN hardware configuration, coordinated planning, and Quest teleoperation support.

The integration test cannot load because it imports a removed mesh utility. The default model test selection also requires a large LFS-backed model that the regular job rejects, and the physical two-CAN configuration passes a runtime field that the Damiao adapter configuration rejects.

T-Rex validation blocked

Full execution of the model test path is blocked by missing Python packages dotenv and pinocchio, and resolving the model archive also requires the unavailable git-lfs tool.

Confidence Score: 2/5

The change is not ready to merge because integration testing cannot load, the regular test job cannot retrieve its required model asset, and physical CAN configuration cannot create its adapter.

Three independent non-security failures remain: the invalid mesh utility import, the unmarked guarded LFS model test, and the rejected physical-adapter configuration field.

Files Needing Attention: dimos/robot/manipulators/dual_openyam/test_integration.py, dimos/robot/manipulators/dual_openyam/test_model.py, dimos/robot/manipulators/dual_openyam/config.py

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proof for a posted P1 finding, including a minimal exact-import reproduction script and runtime outputs showing the import path and current source lines.
  • T-Rex documented the Damiao runtime configuration probe, including its source probe and the corresponding runtime output.
  • T-Rex attempted the regular model-test path and recorded collection blockers due to missing dotenv and missing pinocchio, with an oversized LFS archive affecting default selection.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. General comment

    P1 Dual OpenYAM integration test imports a nonexistent mesh utility

    • Bug
      • dimos.robot.manipulators.dual_openyam.test_integration cannot import because it requests prepare_urdf, which current dimos.manipulation.planning.utils.mesh_utils does not export. This prevents the integration test module from loading, so its test body cannot run.
    • Cause
      • The test uses the obsolete/nonexistent prepare_urdf API and a filesystem-path calling convention, whereas the current mesh utility exposes prepare_urdf_for_drake(description: LoadedRobotModel, convert_meshes: bool = False) for in-memory models.
    • Fix
      • Update the integration test to use the supported current model-loading and preparation API, or restore a compatible prepare_urdf public API if that path-based interface is intentionally required.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Regular CI selects an unmarked test that resolves a guarded oversized LFS model

    • Bug
      • The Dual OpenYAM model tests are selected by default because they live below the configured dimos test path and carry no self_hosted mark. Their first test calls DUAL_OPENYAM_MODEL_PATH.is_file(), which forces LfsPath resolution. The current archive is an LFS pointer declaring 2,488,468 bytes, above the regular CI guard's 1,048,576-byte cap, so a clean regular job will have its required LFS pull rejected before the test can inspect the model.
    • Cause
      • test_model.py exercises an LFS-backed asset without being marked self_hosted or otherwise excluded from the regular test matrix.
    • Fix
      • Mark the module or all LFS-resolving tests with pytest.mark.self_hosted, or change the regular job/fixture strategy so this model is available without an LFS pull under the guard.

    T-Rex Ran code and verified through T-Rex

Reviews (2): Last reviewed commit: "fix(manipulation): load dual OpenYAM rob..." | Re-trigger Greptile

Comment thread dimos/robot/manipulators/dual_openyam/test_integration.py
Comment thread dimos/robot/manipulators/dual_openyam/test_model.py
Comment thread dimos/robot/manipulators/dual_openyam/test_integration.py
Comment thread dimos/robot/manipulators/dual_openyam/test_model.py
Comment thread dimos/robot/manipulators/dual_openyam/config.py
@TomCC7
TomCC7 force-pushed the cc/feat/dual-yam branch 2 times, most recently from ff35402 to 1bb53dd Compare August 25, 2026 22:53
@TomCC7
TomCC7 force-pushed the cc/feat/dual-yam branch 3 times, most recently from 0c6883b to 883606d Compare August 25, 2026 23:50
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.

2 participants