Skip to content

WIP: feat: add manipulation planning groups#2489

Draft
TomCC7 wants to merge 34 commits into
mainfrom
cc/spec/movegroup
Draft

WIP: feat: add manipulation planning groups#2489
TomCC7 wants to merge 34 commits into
mainfrom
cc/spec/movegroup

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jun 13, 2026

Copy link
Copy Markdown
Member

Summary

  • add first-class manipulation planning group models, SRDF/fallback discovery, resolved joint naming, and world group resolution
  • add group-scoped kinematics/planner APIs plus GeneratedPlan preview/execution projection through coordinator trajectory tasks
  • add OpenArm mock planner/coordinator large E2E coverage for single-arm and dual-arm planning flows
  • add OpenSpec artifacts and user/contributor docs for planning groups
image

Verification

  • uv run pytest dimos/manipulation -q
  • uv run pytest dimos/e2e_tests/test_manipulation_planning_groups.py -m self_hosted_large -q
  • uv run pytest --collect-only dimos/e2e_tests/test_manipulation_planning_groups.py -q
  • openspec validate add-planning-groups
  • pre-commit hooks via git commit

Notes

  • PR remains draft.
  • OpenSpec manual QA tasks 8.12 and 8.13 remain unchecked because no SRDF-backed chain fixture or auxiliary pose-planning fixture was available in this environment.

@TomCC7 TomCC7 changed the title WIP spec: movegroup concept and bimanual/multi-target motion planning WIP spec: planning group and bimanual/multi-target motion planning Jun 13, 2026
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

❌ 6 Tests Failed:

Tests completed Failed Passed Skipped
2113 6 2107 214
View the top 3 failed test(s) by shortest run time
::dimos.control.blueprints.test_dual
Stack Traces | 0s run time
.../control/blueprints/test_dual.py:19: in <module>
    from dimos.manipulation.blueprints import dual_xarm6_planner_coordinator
        ControlCoordinator = <class 'dimos.control.coordinator.ControlCoordinator'>
        __builtins__ = <builtins>
        __cached__ = '.../blueprints/__pycache__/test_dual.cpython-312.pyc'
        __doc__    = 'Tests for dual-arm control blueprints.'
        __file__   = '.../work/dimos/dimos/.../control/blueprints/test_dual.py'
        __loader__ = <_pytest.assertion.rewrite.AssertionRewritingHook object at 0xff06e8b892b0>
        __name__   = 'dimos.control.blueprints.test_dual'
        __package__ = 'dimos.control.blueprints'
        __spec__   = ModuleSpec(name='dimos.control.blueprints.test_dual', loader=<_pytest.assertion.rewrite.AssertionRewritingHook object at 0xff06e8b892b0>, origin='.../work/dimos/dimos/.../control/blueprints/test_dual.py')
        coordinator_dual_xarm = Blueprint(blueprints=(BlueprintAtom(kwargs={'hardware': [HardwareComponent(hardware_id='left_arm', hardware_type=<Hard...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
dimos/manipulation/blueprints.py:60: in <module>
    robots=[_xarm6_planner_cfg.to_robot_model_config()],
        ControlCoordinator = <class 'dimos.control.coordinator.ControlCoordinator'>
        JointState = <class 'dimos.msgs.sensor_msgs.JointState.JointState'>
        LCMTransport = <class 'dimos.core.transport.LCMTransport'>
        ManipulationModule = <class 'dimos.manipulation.manipulation_module.ManipulationModule'>
        McpClient  = <class 'dimos.agents.mcp.mcp_client.McpClient'>
        McpServer  = <class 'dimos.agents.mcp.mcp_server.McpServer'>
        ObjectSceneRegistrationModule = <class 'dimos.perception.object_scene_registration.ObjectSceneRegistrationModule'>
        PickAndPlaceModule = <class 'dimos.manipulation.pick_and_place_module.PickAndPlaceModule'>
        Quaternion = <class 'dimos.msgs.geometry_msgs.Quaternion.Quaternion'>
        RealSenseCamera = <class 'dimos.hardware.sensors.camera.realsense.camera.RealSenseCamera'>
        Transform  = <class 'dimos.msgs.geometry_msgs.Transform.Transform'>
        Vector3    = <class 'dimos.msgs.geometry_msgs.Vector3.Vector3'>
        __builtins__ = <builtins>
        __cached__ = '.../manipulation/__pycache__/blueprints.cpython-312.pyc'
        __doc__    = '\nManipulation blueprints.\n\nQuick start:\n    # 1. Verify manipulation deps load correctly (standalone, no hardware... dimos run dual-xarm7-planner-coordinator\n    python -i -m dimos.manipulation.planning.examples.manipulation_client\n'
        __file__   = '.../dimos/manipulation/blueprints.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xff064915e6f0>
        __name__   = 'dimos.manipulation.blueprints'
        __package__ = 'dimos.manipulation'
        __spec__   = ModuleSpec(name='dimos.manipulation.blueprints', loader=<_frozen_importlib_external.SourceFileLoader object at 0xff064915e6f0>, origin='.../dimos/manipulation/blueprints.py')
        _catalog_xarm6 = <function xarm6 at 0xff061ef24180>
        _catalog_xarm7 = <function xarm7 at 0xff061ef17f60>
        _xarm6_planner_cfg = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff061f06a080>
        autoconnect = <function autoconnect at 0xff06b702f380>
        global_config = GlobalConfig(robot_ip=None, robot_ips=None, unitree_aes_128_key=None, xarm7_ip=None, xarm6_ip=None, can_port=None, dev...e, obstacle_avoidance=True, detection_model='moondream', listen_host='127.0.0.1', dimsim_scene='apt', dimsim_port=8090)
        math       = <module 'math' (built-in)>
dimos/robot/config.py:206: in to_robot_model_config
    model=self.model_description,
        base_link  = 'link_base'
        base_pose  = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000))
        bp         = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...]
        exclusions = [('right_inner_knuckle', 'right_outer_knuckle'), ('left_inner_knuckle', 'left_outer_knuckle'), ('right_inner_knuckle',...ft_inner_knuckle', 'left_finger'), ('left_finger', 'right_finger'), ('left_outer_knuckle', 'right_outer_knuckle'), ...]
        joint_names = ['joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6']
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff061f06a080>
dimos/robot/config.py:153: in model_description
    return self._ensure_parsed()
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff061f06a080>
dimos/robot/config.py:125: in _ensure_parsed
    self._parsed = parse_model(self.model_path, self.package_paths, self.xacro_args)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff061f06a080>
dimos/robot/model_parser.py:71: in parse_model
    path = Path(path)
        package_paths = {'xarm_description': <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.....lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33c50>}
        path       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33cd0>
        xacro_args = {'add_gripper': 'true', 'attach_rpy': '0 0 0', 'attach_xyz': '0 0 0', 'dof': '6', ...}
....../usr/lib/python3.12/pathlib.py:1164: in __init__
    super().__init__(*args)
        __class__  = <class 'pathlib.Path'>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33cd0>,)
        kwargs     = {}
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff06b9c736f0>
....../usr/lib/python3.12/pathlib.py:362: in __init__
    if arg._flavour is ntpath and self._flavour is posixpath:
        arg        = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33cd0>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33cd0>,)
        ntpath     = <module 'ntpath' (frozen)>
        paths      = []
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff06b9c736f0>
dimos/utils/data.py:364: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '_flavour'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33cd0>
dimos/utils/data.py:347: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'xarm_description/urdf/xarm_device.urdf.xacro'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff061ef33cd0>
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'xarm_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/xarm_description/urdf/xarm_device.urdf.xacro')
        name       = 'xarm_description/urdf/xarm_device.urdf.xacro'
        nested_path = PosixPath('urdf/xarm_device.urdf.xacro')
        path_parts = ('xarm_description', 'urdf', 'xarm_device.urdf.xacro')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
        filename   = 'xarm_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
dimos/utils/data.py:216: in _lfs_pull
    raise RuntimeError(
E   RuntimeError: Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.
        attempt    = 3
        env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '10bd23aa-c98a-4f1a-abc0-35319bdbe5fa.tests.ubuntu-24_04-arm_3_14_fal', ...}
        file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
        last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz'])
        relative_path = PosixPath('data/.lfs/xarm_description.tar.gz')
        repo_root  = PosixPath('.../work/dimos/dimos')
        retries    = 2
dimos.robot.catalog.test_ufactory::test_xarm_instance_offsets_are_encoded_only_in_base_pose[xarm7]
Stack Traces | 3.09s run time
factory = <function xarm7 at 0xff061ef17f60>

    @pytest.mark.parametrize("factory", [xarm6, xarm7])
    def test_xarm_instance_offsets_are_encoded_only_in_base_pose(
        factory: Callable[..., RobotConfig],
    ) -> None:
        config = factory(name="arm", y_offset=0.5, pitch=0.25)
>       model_config = config.to_robot_model_config()

config     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff0605878a00>
factory    = <function xarm7 at 0xff061ef17f60>

.../robot/catalog/test_ufactory.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/config.py:206: in to_robot_model_config
    model=self.model_description,
        base_link  = 'link_base'
        base_pose  = Pose(position=Vector([          0         0.5           0]), orientation=Quaternion(0.000000, 0.124675, 0.000000, 0.992198))
        bp         = [0.0, 0.5, 0.0, 0.0, 0.12467473338522769, 0.0, ...]
        exclusions = []
        joint_names = ['joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6', ...]
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff0605878a00>
dimos/robot/config.py:153: in model_description
    return self._ensure_parsed()
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff0605878a00>
dimos/robot/config.py:125: in _ensure_parsed
    self._parsed = parse_model(self.model_path, self.package_paths, self.xacro_args)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff0605878a00>
dimos/robot/model_parser.py:71: in parse_model
    path = Path(path)
        package_paths = {'xarm_description': <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.....lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605930c50>}
        path       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605d7ac50>
        xacro_args = {'attach_rpy': '0 0 0', 'attach_xyz': '0 0 0', 'dof': '7', 'limited': 'true'}
....../usr/lib/python3.12/pathlib.py:1164: in __init__
    super().__init__(*args)
        __class__  = <class 'pathlib.Path'>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605d7ac50>,)
        kwargs     = {}
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff06058d9af0>
....../usr/lib/python3.12/pathlib.py:362: in __init__
    if arg._flavour is ntpath and self._flavour is posixpath:
        arg        = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605d7ac50>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605d7ac50>,)
        ntpath     = <module 'ntpath' (frozen)>
        paths      = []
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff06058d9af0>
dimos/utils/data.py:364: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '_flavour'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605d7ac50>
dimos/utils/data.py:347: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'xarm_description/urdf/xarm_device.urdf.xacro'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0605d7ac50>
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'xarm_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/xarm_description/urdf/xarm_device.urdf.xacro')
        name       = 'xarm_description/urdf/xarm_device.urdf.xacro'
        nested_path = PosixPath('urdf/xarm_device.urdf.xacro')
        path_parts = ('xarm_description', 'urdf', 'xarm_device.urdf.xacro')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
        filename   = 'xarm_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/data/.lfs/xarm_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(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    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/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '10bd23aa-c98a-4f1a-abc0-35319bdbe5fa.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz'])
relative_path = PosixPath('data/.lfs/xarm_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.robot.catalog.test_ufactory::test_xarm_instance_offsets_are_encoded_only_in_base_pose[xarm6]
Stack Traces | 3.11s run time
factory = <function xarm6 at 0xff061ef24180>

    @pytest.mark.parametrize("factory", [xarm6, xarm7])
    def test_xarm_instance_offsets_are_encoded_only_in_base_pose(
        factory: Callable[..., RobotConfig],
    ) -> None:
        config = factory(name="arm", y_offset=0.5, pitch=0.25)
>       model_config = config.to_robot_model_config()

config     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff060760b9d0>
factory    = <function xarm6 at 0xff061ef24180>

.../robot/catalog/test_ufactory.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/config.py:206: in to_robot_model_config
    model=self.model_description,
        base_link  = 'link_base'
        base_pose  = Pose(position=Vector([          0         0.5           0]), orientation=Quaternion(0.000000, 0.124675, 0.000000, 0.992198))
        bp         = [0.0, 0.5, 0.0, 0.0, 0.12467473338522769, 0.0, ...]
        exclusions = [('right_inner_knuckle', 'right_outer_knuckle'), ('left_inner_knuckle', 'left_outer_knuckle'), ('right_inner_knuckle',...ft_inner_knuckle', 'left_finger'), ('left_finger', 'right_finger'), ('left_outer_knuckle', 'right_outer_knuckle'), ...]
        joint_names = ['joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6']
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff060760b9d0>
dimos/robot/config.py:153: in model_description
    return self._ensure_parsed()
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff060760b9d0>
dimos/robot/config.py:125: in _ensure_parsed
    self._parsed = parse_model(self.model_path, self.package_paths, self.xacro_args)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff060760b9d0>
dimos/robot/model_parser.py:71: in parse_model
    path = Path(path)
        package_paths = {'xarm_description': <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.....lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0607971ad0>}
        path       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0648fb92d0>
        xacro_args = {'add_gripper': 'true', 'attach_rpy': '0 0 0', 'attach_xyz': '0 0 0', 'dof': '6', ...}
....../usr/lib/python3.12/pathlib.py:1164: in __init__
    super().__init__(*args)
        __class__  = <class 'pathlib.Path'>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0648fb92d0>,)
        kwargs     = {}
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff0618934430>
....../usr/lib/python3.12/pathlib.py:362: in __init__
    if arg._flavour is ntpath and self._flavour is posixpath:
        arg        = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0648fb92d0>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0648fb92d0>,)
        ntpath     = <module 'ntpath' (frozen)>
        paths      = []
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff0618934430>
dimos/utils/data.py:364: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '_flavour'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0648fb92d0>
dimos/utils/data.py:347: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'xarm_description/urdf/xarm_device.urdf.xacro'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff0648fb92d0>
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'xarm_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/xarm_description/urdf/xarm_device.urdf.xacro')
        name       = 'xarm_description/urdf/xarm_device.urdf.xacro'
        nested_path = PosixPath('urdf/xarm_device.urdf.xacro')
        path_parts = ('xarm_description', 'urdf', 'xarm_device.urdf.xacro')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
        filename   = 'xarm_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/data/.lfs/xarm_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(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    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/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '10bd23aa-c98a-4f1a-abc0-35319bdbe5fa.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz'])
relative_path = PosixPath('data/.lfs/xarm_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.robot.test_all_blueprints::test_blueprint_is_valid[keyboard-teleop-xarm7]
Stack Traces | 3.11s run time
blueprint_name = 'keyboard-teleop-xarm7'

    @pytest.mark.parametrize("blueprint_name", UBUNTU_BLUEPRINTS)
    def test_blueprint_is_valid(blueprint_name: str) -> None:
        """Validate blueprints that should import on the ubuntu-latest runner."""
>       _check_blueprint(blueprint_name)

blueprint_name = 'keyboard-teleop-xarm7'

dimos/robot/test_all_blueprints.py:104: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/test_all_blueprints.py:80: in _check_blueprint
    blueprint = get_blueprint_by_name(blueprint_name)
        blueprint_name = 'keyboard-teleop-xarm7'
        message    = "Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1."
dimos/robot/get_all_blueprints.py:47: in get_blueprint_by_name
    module = __import__(module_path, fromlist=[attr])
        attr       = 'keyboard_teleop_xarm7'
        module_path = 'dimos.robot.manipulators.xarm.blueprints'
        name       = 'keyboard-teleop-xarm7'
.../manipulators/xarm/blueprints.py:56: in <module>
    joint_names=_xarm6_cfg.global_joint_names,
        ControlCoordinator = <class 'dimos.control.coordinator.ControlCoordinator'>
        KeyboardTeleopModule = <class 'dimos.teleop.keyboard.keyboard_teleop_module.KeyboardTeleopModule'>
        ManipulationModule = <class 'dimos.manipulation.manipulation_module.ManipulationModule'>
        XARM6_FK_MODEL = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5cb76fd5d0>
        XARM7_FK_MODEL = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5cb76fd650>
        __builtins__ = <builtins>
        __cached__ = '.../xarm/__pycache__/blueprints.cpython-312.pyc'
        __doc__    = 'Keyboard teleop blueprints for XArm6 and XArm7.\n\nLaunches the ControlCoordinator (mock adapter + CartesianIK), the\... wired together via autoconnect.\n\nUsage:\n    dimos run keyboard-teleop-xarm6\n    dimos run keyboard-teleop-xarm7\n'
        __file__   = '.../work/dimos/dimos/.../manipulators/xarm/blueprints.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xff5ca1cfcbf0>
        __name__   = 'dimos.robot.manipulators.xarm.blueprints'
        __package__ = 'dimos.robot.manipulators.xarm'
        __spec__   = ModuleSpec(name='dimos.robot.manipulators.xarm.blueprints', loader=<_frozen_importlib_external.SourceFileLoader object at 0xff5ca1cfcbf0>, origin='.../work/dimos/dimos/.../manipulators/xarm/blueprints.py')
        _catalog_xarm6 = <function xarm6 at 0xff5cb76e8fe0>
        _catalog_xarm7 = <function xarm7 at 0xff5cb76e91c0>
        _xarm6_cfg = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca01aefd0>
        _xarm7_cfg = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca01af2a0>
        autoconnect = <function autoconnect at 0xff5d4f442ca0>
        global_config = GlobalConfig(robot_ip=None, robot_ips=None, unitree_aes_128_key=None, xarm7_ip=None, xarm6_ip=None, can_port=None, dev...e, obstacle_avoidance=True, detection_model='moondream', listen_host='127.0.0.1', dimsim_scene='apt', dimsim_port=8090)
dimos/robot/config.py:163: in global_joint_names
    return make_global_joint_names(self.name, self.local_joint_names)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca01aefd0>
dimos/robot/config.py:157: in local_joint_names
    self._ensure_parsed()
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca01aefd0>
dimos/robot/config.py:125: in _ensure_parsed
    self._parsed = parse_model(self.model_path, self.package_paths, self.xacro_args)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca01aefd0>
dimos/robot/model_parser.py:71: in parse_model
    path = Path(path)
        package_paths = {'xarm_description': <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.....lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7bd0>}
        path       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7c50>
        xacro_args = {'attach_rpy': '0 0 0', 'attach_xyz': '0 0 0', 'dof': '6', 'limited': 'true'}
....../usr/lib/python3.12/pathlib.py:1164: in __init__
    super().__init__(*args)
        __class__  = <class 'pathlib.Path'>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7c50>,)
        kwargs     = {}
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff5ca0123990>
....../usr/lib/python3.12/pathlib.py:362: in __init__
    if arg._flavour is ntpath and self._flavour is posixpath:
        arg        = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7c50>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7c50>,)
        ntpath     = <module 'ntpath' (frozen)>
        paths      = []
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff5ca0123990>
dimos/utils/data.py:364: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '_flavour'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7c50>
dimos/utils/data.py:347: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'xarm_description/urdf/xarm_device.urdf.xacro'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5ca00a7c50>
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'xarm_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/xarm_description/urdf/xarm_device.urdf.xacro')
        name       = 'xarm_description/urdf/xarm_device.urdf.xacro'
        nested_path = PosixPath('urdf/xarm_device.urdf.xacro')
        path_parts = ('xarm_description', 'urdf', 'xarm_device.urdf.xacro')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
        filename   = 'xarm_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/data/.lfs/xarm_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(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    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/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '10bd23aa-c98a-4f1a-abc0-35319bdbe5fa.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz'])
relative_path = PosixPath('data/.lfs/xarm_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.robot.test_all_blueprints::test_blueprint_is_valid[keyboard-teleop-xarm6]
Stack Traces | 3.12s run time
blueprint_name = 'keyboard-teleop-xarm6'

    @pytest.mark.parametrize("blueprint_name", UBUNTU_BLUEPRINTS)
    def test_blueprint_is_valid(blueprint_name: str) -> None:
        """Validate blueprints that should import on the ubuntu-latest runner."""
>       _check_blueprint(blueprint_name)

blueprint_name = 'keyboard-teleop-xarm6'

dimos/robot/test_all_blueprints.py:104: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/test_all_blueprints.py:80: in _check_blueprint
    blueprint = get_blueprint_by_name(blueprint_name)
        blueprint_name = 'keyboard-teleop-xarm6'
        message    = "Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1."
dimos/robot/get_all_blueprints.py:47: in get_blueprint_by_name
    module = __import__(module_path, fromlist=[attr])
        attr       = 'keyboard_teleop_xarm6'
        module_path = 'dimos.robot.manipulators.xarm.blueprints'
        name       = 'keyboard-teleop-xarm6'
.../manipulators/xarm/blueprints.py:56: in <module>
    joint_names=_xarm6_cfg.global_joint_names,
        ControlCoordinator = <class 'dimos.control.coordinator.ControlCoordinator'>
        KeyboardTeleopModule = <class 'dimos.teleop.keyboard.keyboard_teleop_module.KeyboardTeleopModule'>
        ManipulationModule = <class 'dimos.manipulation.manipulation_module.ManipulationModule'>
        XARM6_FK_MODEL = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5cb76fd5d0>
        XARM7_FK_MODEL = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5cb76fd650>
        __builtins__ = <builtins>
        __cached__ = '.../xarm/__pycache__/blueprints.cpython-312.pyc'
        __doc__    = 'Keyboard teleop blueprints for XArm6 and XArm7.\n\nLaunches the ControlCoordinator (mock adapter + CartesianIK), the\... wired together via autoconnect.\n\nUsage:\n    dimos run keyboard-teleop-xarm6\n    dimos run keyboard-teleop-xarm7\n'
        __file__   = '.../work/dimos/dimos/.../manipulators/xarm/blueprints.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xff5ca24e10a0>
        __name__   = 'dimos.robot.manipulators.xarm.blueprints'
        __package__ = 'dimos.robot.manipulators.xarm'
        __spec__   = ModuleSpec(name='dimos.robot.manipulators.xarm.blueprints', loader=<_frozen_importlib_external.SourceFileLoader object at 0xff5ca24e10a0>, origin='.../work/dimos/dimos/.../manipulators/xarm/blueprints.py')
        _catalog_xarm6 = <function xarm6 at 0xff5cb76e8fe0>
        _catalog_xarm7 = <function xarm7 at 0xff5cb76e91c0>
        _xarm6_cfg = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca26b3cf0>
        _xarm7_cfg = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca26b3d90>
        autoconnect = <function autoconnect at 0xff5d4f442ca0>
        global_config = GlobalConfig(robot_ip=None, robot_ips=None, unitree_aes_128_key=None, xarm7_ip=None, xarm6_ip=None, can_port=None, dev...e, obstacle_avoidance=True, detection_model='moondream', listen_host='127.0.0.1', dimsim_scene='apt', dimsim_port=8090)
dimos/robot/config.py:163: in global_joint_names
    return make_global_joint_names(self.name, self.local_joint_names)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca26b3cf0>
dimos/robot/config.py:157: in local_joint_names
    self._ensure_parsed()
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca26b3cf0>
dimos/robot/config.py:125: in _ensure_parsed
    self._parsed = parse_model(self.model_path, self.package_paths, self.xacro_args)
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp...s/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] RobotConfig object at 0xff5ca26b3cf0>
dimos/robot/model_parser.py:71: in parse_model
    path = Path(path)
        package_paths = {'xarm_description': <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.....lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbd0d0>}
        path       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbfcd0>
        xacro_args = {'attach_rpy': '0 0 0', 'attach_xyz': '0 0 0', 'dof': '6', 'limited': 'true'}
....../usr/lib/python3.12/pathlib.py:1164: in __init__
    super().__init__(*args)
        __class__  = <class 'pathlib.Path'>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbfcd0>,)
        kwargs     = {}
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff5ca2698c10>
....../usr/lib/python3.12/pathlib.py:362: in __init__
    if arg._flavour is ntpath and self._flavour is posixpath:
        arg        = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbfcd0>
        args       = (<[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attem...lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbfcd0>,)
        ntpath     = <module 'ntpath' (frozen)>
        paths      = []
        self       = <[AttributeError("'PosixPath' object has no attribute '_raw_paths'") raised in repr()] PosixPath object at 0xff5ca2698c10>
dimos/utils/data.py:364: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '_flavour'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbfcd0>
dimos/utils/data.py:347: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'xarm_description/urdf/xarm_device.urdf.xacro'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/xarm_description.tar.gz after 3 attemp.../.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xff5c9fdbfcd0>
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'xarm_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/xarm_description/urdf/xarm_device.urdf.xacro')
        name       = 'xarm_description/urdf/xarm_device.urdf.xacro'
        nested_path = PosixPath('urdf/xarm_device.urdf.xacro')
        path_parts = ('xarm_description', 'urdf', 'xarm_device.urdf.xacro')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
        filename   = 'xarm_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/data/.lfs/xarm_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(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    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/xarm_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '10bd23aa-c98a-4f1a-abc0-35319bdbe5fa.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/xarm_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/xarm_description.tar.gz'])
relative_path = PosixPath('data/.lfs/xarm_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.codebase_checks.test_no_all::test_no_all
Stack Traces | 4.5s run time
def test_no_all():
        """Fail if any file defines `__all__`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_all_definitions()
        if hits:
            listing = "\n".join(f"  - {p.relative_to(dimos_dir)}:{lineno}" for p, lineno in hits)
>           raise AssertionError(
                f"Found __all__ definition(s) in dimos/:\n{listing}\n\n"
                "__all__ is not allowed. We don't use `from x import *`, so __all__ "
                "lists serve no purpose and are tedious to maintain. Remove them. For "
                "an import that exists purely to be re-exported, use `# noqa: F401`."
            )
E           AssertionError: Found __all__ definition(s) in dimos/:
E             - control/blueprints/dual.py:103
E             - manipulation/blueprints.py:382
E             - .../planning/kinematics/pink_ik.py:883
E             - manipulation/planning/planning_identifiers.py:115
E             - robot/catalog/openarm.py:116
E             - robot/catalog/ufactory.py:168
E             - robot/config.py:290
E             - .../manipulators/xarm/blueprints.py:105
E           
E           __all__ is not allowed. We don't use `from x import *`, so __all__ lists serve no purpose and are tedious to maintain. Remove them. For an import that exists purely to be re-exported, use `# noqa: F401`.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/control/blueprints/dual.py'), 103), (PosixPath('.../runner/work/dim...bot/catalog/openarm.py'), 116), (PosixPath('.../dimos/dimos/dimos/robot/catalog/ufactory.py'), 168), ...]
listing    = '  - control/blueprints/dual.py:103\n  - manipulation/blueprints.py:382\n  - .../planning/kinematics/pink_ik....narm.py:116\n  - robot/catalog/ufactory.py:168\n  - robot/config.py:290\n  - .../manipulators/xarm/blueprints.py:105'

dimos/codebase_checks/test_no_all.py:51: 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 changed the title WIP spec: planning group and bimanual/multi-target motion planning feat: add manipulation planning groups Jun 17, 2026
@TomCC7 TomCC7 changed the title feat: add manipulation planning groups WIP: feat: add manipulation planning groups Jun 17, 2026
Comment thread dimos/e2e_tests/test_manipulation_planning_groups.py Outdated
Comment thread dimos/manipulation/planning/kinematics/jacobian_ik.py Outdated
Comment thread dimos/manipulation/planning/kinematics/jacobian_ik.py
Comment thread dimos/manipulation/planning/kinematics/jacobian_ik.py Outdated
Comment thread dimos/manipulation/manipulation_module.py
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py
@TomCC7 TomCC7 force-pushed the cc/spec/movegroup branch from f157926 to 31731b1 Compare June 19, 2026 02:31
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py
Comment thread dimos/manipulation/manipulation_module.py
Comment thread dimos/manipulation/manipulation_module.py Outdated
return result

@rpc
def plan_to_pose(self, pose: Pose, robot_name: RobotName | None = None) -> bool:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

plan_to_pose and plan_to_joints should be just wrapper of the more generic functions, no duplication. plan_to_poses and plan_to_joints

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in d370174.

Comment on lines +909 to +917
if self._world_monitor is None or self._kinematics is None:
return False
if not pose_targets:
return self._fail("At least one pose target is required")
with self._lock:
if self._state not in (ManipulationState.IDLE, ManipulationState.COMPLETED):
logger.warning(f"Cannot plan: state is {self._state.name}")
return False
self._state = ManipulationState.PLANNING

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

should reuse begin_planning here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in d370174.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

why not?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 288fca5.

Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/planning/world/drake_world.py Outdated
Comment thread dimos/manipulation/planning/planning_group_utils.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment on lines +909 to +917
if self._world_monitor is None or self._kinematics is None:
return False
if not pose_targets:
return self._fail("At least one pose target is required")
with self._lock:
if self._state not in (ManipulationState.IDLE, ManipulationState.COMPLETED):
logger.warning(f"Cannot plan: state is {self._state.name}")
return False
self._state = ManipulationState.PLANNING

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

why not?

Comment thread dimos/manipulation/manipulation_module.py Outdated
self._world_monitor.world.list_planning_groups(), robot_name
)

def _current_positions_by_name(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this function is basically a noop check, remove

) -> bool:
"""Preview planned path in Meshcat."""
return _client.preview_path(duration, robot_name, target_fps)
"""Preview the last generated plan in Meshcat."""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
"""Preview the last generated plan in Meshcat."""
"""Preview the last generated plan in Visualizer."""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 10e4d31.

"""Dual-arm coordinator blueprints with trajectory control.

Usage:
dimos run coordinator-dual-mock # Mock 7+6 DOF arms

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok we shouldn't remove this... bring it back

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 10e4d31.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

no init

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 10e4d31.

return fallback_result
return _failure(IKStatus.NO_SOLUTION, f"Pink IK failed after {max_attempts} attempts")

def _solve_single(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

again, solve single should be thin wrapper of the more general case (solve pose target)

planning_groups: list[PlanningGroupInfo]


class PlanningGroupInfo(TypedDict):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is just duplication... why not use original type?

Comment on lines +548 to +586
def _selected_joint_state(self, group_ids: tuple[PlanningGroupID, ...]) -> JointState | None:
"""Collect current state for exactly the selected global joints."""
assert self._world_monitor is not None
selection = self._world_monitor.planning_groups.select(group_ids)

robot_ids_by_name: dict[RobotName, WorldRobotID] = {}
for robot_name in selection.robot_names:
try:
robot_ids_by_name[robot_name] = self._robots[robot_name][0]
except KeyError:
logger.error("Robot '%s' is not registered", robot_name)
return None

current_by_robot: dict[RobotName, dict[str, float]] = {}
for robot_name, robot_id in robot_ids_by_name.items():
current = self._world_monitor.get_current_joint_state(robot_id)
if current is None:
logger.error("No joint state for robot '%s'", robot_name)
return None
indexed_current = self._current_positions_by_name(robot_name, current)
if indexed_current is None:
return None
current_by_robot[robot_name] = indexed_current

names: list[str] = []
positions: list[float] = []
for group in selection.groups:
robot_state = current_by_robot[group.robot_name]
for resolved_name, local_name in zip(
group.joint_names, group.local_joint_names, strict=True
):
if local_name not in robot_state:
logger.error("Current state missing selected joint '%s'", resolved_name)
return None
position = robot_state[local_name]
names.append(resolved_name)
positions.append(position)

return JointState(name=names, position=positions)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

really can't simplify this?

}

def evaluate_pose_target(self, pose: Pose, robot_name: RobotName) -> TargetEvaluation:
def evaluate_pose_target(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

maybe we should just remove this and use IK + collision checking function instead of the all-in-one function

logger.error(f"No coordinator_task_name for '{robot_name}'")
@rpc
def execute_plan(
self, plan: GeneratedPlan | None = None, robot_name: RobotName | None = None

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

execute a plan with only one robot makes zero sense. a plan is a whole and should always be executed together, just remove the robot_name variable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 10e4d31.

def execute_plan(
self, plan: GeneratedPlan | None = None, robot_name: RobotName | None = None
) -> bool:
"""Project and execute a generated plan through affected trajectory tasks."""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
"""Project and execute a generated plan through affected trajectory tasks."""
"""Project and execute a generated plan through affected trajectory tasks. TODO: proper time parametrization """

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 10e4d31.

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