Skip to content

feat(imitation): add direct OpenYAM teach collection - #3921

Draft
TomCC7 wants to merge 8 commits into
feat/openyam-lerobot-rolloutfrom
cc/feat/openyam-teach-data-collection
Draft

feat(imitation): add direct OpenYAM teach collection#3921
TomCC7 wants to merge 8 commits into
feat/openyam-lerobot-rolloutfrom
cc/feat/openyam-teach-data-collection

Conversation

@TomCC7

@TomCC7 TomCC7 commented Sep 3, 2026

Copy link
Copy Markdown
Member

This is layer 6 of the OpenYAM learning slice. It replaces the closed A1Z learning-workflow PR #3318 as the top layer above #3855.

Contribution path

Problem

OpenYAM data collection currently requires a Quest teleoperator. Operators need a simpler scheme where they can put the arm in gravity-compensation mode, move it and the gripper by hand, and collect demonstrations without a second control device.

Solution

  • Reuse the existing trajectory task in idle-hold mode to keep the coordinator-to-hardware command path running at 100 Hz. The OpenYAM teach blueprint hardcodes zero position stiffness, so the latched position target is inert while each write applies gravity feed-forward and configured damping.
  • Configure OpenYAM teaching with gravity compensation, zero position stiffness, and lighter arm damping: (2, 2, 2, 0.5, 0.5, 0.5).
  • Add Damiao passive-gripper configuration. After normal opening calibration, teach mode switches the gripper to MIT mode, explicitly enables it, and continuously commands zero stiffness, zero damping, and zero feed-forward torque while reading its measured opening.
  • Record the hand-moved gripper position in the canonical seven-joint ordering.
  • Add learning-collect-teach-openyam with the native MCAP recorder, episode monitor, control coordinator, and wrist camera; keep Quest collection unchanged.
  • Add dimos collect, a Textual operator dashboard with a recording timer, saved/discarded counters, contextual guidance, keyboard shortcuts, and clickable Start/Save, Discard, and Detach controls. It refuses to detach during an active take and never stops the daemon.
  • Route Quest buttons and terminal commands through the same episode state machine.
  • Add OPENYAM_TEACH_LEARNING_PROFILE, which uses continuous measured joint state for both observations and kinesthetic actions. Quest collection keeps using accepted position commands.
  • Resolve recording and dataset paths before entering the isolated LeRobot runtime, and accept its final typed result even when initialized log handlers write progress to stdout.

Learning slice

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

How to Test

dimos --can-port follower_l run learning-collect-teach-openyam --daemon \
  --task "pick up the red block" \
  --WristCamera.hardware.camera-index 0 \
  --nativecollectionrecorder.store.path data/recordings/openyam-teach.mcap
dimos collect

dimos dataprep build \
  --source data/recordings/openyam-teach.mcap \
  --profile dimos.robot.manipulators.openyam.learning:OPENYAM_TEACH_LEARNING_PROFILE \
  --output data/datasets/openyam-teach
  • Converted a real 25-episode OpenYAM teach MCAP into a LeRobot v3 dataset with 5,813 frames at 30 FPS, 7D state/action, wrist video, uniform feature shapes, and computed statistics.
  • All 25 episodes passed strict validation; worst observed alignment error was 8.1 ms against the 20 ms limit.
  • 54 focused dashboard, OpenYAM, task-registry, trajectory-hold, episode-monitor, learning-profile, and DataPrep tests passed. Ruff, formatting, focused mypy, diff checks, and commit hooks passed.
  • The dashboard layout was checked at 80x24; all status, counter, guidance, and button regions remain visible.
  • Real hardware still requires a smoke test confirming that the gripper is freely hand-movable, the lower damping feels appropriate, and a converted take contains meaningful measured joint motion.

AI assistance

OpenAI Codex with GPT-5 assisted with implementation, tests, documentation, and PR preparation. The change was validated with the focused suites listed above.

Checklist

  • I have read and approved the CLA.
  • Tests added or updated.
  • Documentation added or updated.

@TomCC7 TomCC7 changed the title cc/feat/openyam teach data collection feat(imitation): add direct OpenYAM teach collection Sep 3, 2026
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

❌ 11 Tests Failed:

Tests completed Failed Passed Skipped
4748 11 4737 178
View the top 3 failed test(s) by shortest run time
dimos.experimental.memory.test_rust_recorder_e2e::test_rust_artifact_is_readable_by_python_memory2[mcap]
Stack Traces | 0.001s run time
@pytest.fixture(scope="module")
    def rust_recorder_executable() -> Path:
>       subprocess.run(
            [
                "nix",
                "--extra-experimental-features",
                "nix-command flakes",
                "build",
                "-L",
                ".#dimos-memory-recorder",
                "--no-write-lock-file",
            ],
            cwd=_RUST_WORKSPACE,
            check=True,
        )


.../experimental/memory/test_rust_recorder_e2e.py:76: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

input = None, capture_output = False, timeout = None, check = True
popenargs = (['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', ...],)
kwargs = {'cwd': PosixPath('.../dimos/native/rust')}
process = <Popen: returncode: 1 args: ['nix', '--extra-experimental-features', 'nix-co...>
stdout = None, stderr = None, retcode = 1

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', '--no-write-lock-file']' returned non-zero exit status 1.

capture_output = False
check      = True
input      = None
kwargs     = {'cwd': PosixPath('.../dimos/native/rust')}
popenargs  = (['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', ...],)
process    = <Popen: returncode: 1 args: ['nix', '--extra-experimental-features', 'nix-co...>
retcode    = 1
stderr     = None
stdout     = None
timeout    = None

../../../../..../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/subprocess.py:571: CalledProcessError
dimos.experimental.memory.test_rust_recorder_e2e::test_tf_records_over_zenoh_and_replays_through_python
Stack Traces | 0.001s run time
@pytest.fixture(scope="module")
    def rust_recorder_executable() -> Path:
>       subprocess.run(
            [
                "nix",
                "--extra-experimental-features",
                "nix-command flakes",
                "build",
                "-L",
                ".#dimos-memory-recorder",
                "--no-write-lock-file",
            ],
            cwd=_RUST_WORKSPACE,
            check=True,
        )


.../experimental/memory/test_rust_recorder_e2e.py:76: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

input = None, capture_output = False, timeout = None, check = True
popenargs = (['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', ...],)
kwargs = {'cwd': PosixPath('.../dimos/native/rust')}
process = <Popen: returncode: 1 args: ['nix', '--extra-experimental-features', 'nix-co...>
stdout = None, stderr = None, retcode = 1

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', '--no-write-lock-file']' returned non-zero exit status 1.

capture_output = False
check      = True
input      = None
kwargs     = {'cwd': PosixPath('.../dimos/native/rust')}
popenargs  = (['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', ...],)
process    = <Popen: returncode: 1 args: ['nix', '--extra-experimental-features', 'nix-co...>
retcode    = 1
stderr     = None
stdout     = None
timeout    = None

../../../../..../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/subprocess.py:571: CalledProcessError
dimos.robot.manipulators.openyam.blueprints.test_learning_collection_e2e::test_native_collection_records_typed_zenoh_streams
Stack Traces | 0.003s run time
@pytest.fixture(scope="module")
    def native_recorder_executable() -> Path:
>       subprocess.run(
            ["cargo", "build", "-p", "dimos-memory-recorder"],
            cwd=_RUST_WORKSPACE,
            check=True,
        )


.../openyam/blueprints/test_learning_collection_e2e.py:55: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../../..../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/subprocess.py:548: in run
    with Popen(*popenargs, **kwargs) as process:
        capture_output = False
        check      = True
        input      = None
        kwargs     = {'cwd': PosixPath('.../dimos/native/rust')}
        popenargs  = (['cargo', 'build', '-p', 'dimos-memory-recorder'],)
        timeout    = None
../../../../..../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/subprocess.py:1026: in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
        args       = ['cargo', 'build', '-p', 'dimos-memory-recorder']
        bufsize    = -1
        c2pread    = -1
        c2pwrite   = -1
        close_fds  = True
        creationflags = 0
        cwd        = PosixPath('.../dimos/native/rust')
        encoding   = None
        env        = None
        errors     = None
        errread    = -1
        errwrite   = -1
        executable = None
        extra_groups = None
        gid        = None
        gids       = None
        group      = None
        p2cread    = -1
        p2cwrite   = -1
        pass_fds   = ()
        pipesize   = -1
        preexec_fn = None
        process_group = -1
        restore_signals = True
        self       = <Popen: returncode: 255 args: ['cargo', 'build', '-p', 'dimos-memory-recorder']>
        shell      = False
        start_new_session = False
        startupinfo = None
        stderr     = None
        stdin      = None
        stdout     = None
        text       = None
        uid        = None
        umask      = -1
        universal_newlines = None
        user       = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Popen: returncode: 255 args: ['cargo', 'build', '-p', 'dimos-memory-recorder']>
args = ['cargo', 'build', '-p', 'dimos-memory-recorder'], executable = b'cargo'
preexec_fn = None, close_fds = True, pass_fds = ()
cwd = PosixPath('.../dimos/native/rust')
env = None, startupinfo = None, creationflags = 0, shell = False, p2cread = -1
p2cwrite = -1, c2pread = -1, c2pwrite = -1, errread = -1, errwrite = -1
restore_signals = True, gid = None, gids = None, uid = None, umask = -1
start_new_session = False, process_group = -1

    def _execute_child(self, args, executable, preexec_fn, close_fds,
                       pass_fds, cwd, env,
                       startupinfo, creationflags, shell,
                       p2cread, p2cwrite,
                       c2pread, c2pwrite,
                       errread, errwrite,
                       restore_signals,
                       gid, gids, uid, umask,
                       start_new_session, process_group):
        """Execute program (POSIX version)"""
    
        if isinstance(args, (str, bytes)):
            args = [args]
        elif isinstance(args, os.PathLike):
            if shell:
                raise TypeError('path-like args is not allowed when '
                                'shell is true')
            args = [args]
        else:
            args = list(args)
    
        if shell:
            # On Android the default shell is at '....../system/bin/sh'.
            unix_shell = ('....../system/bin/sh' if
                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')
            args = [unix_shell, "-c"] + args
            if executable:
                args[0] = executable
    
        if executable is None:
            executable = args[0]
    
        sys.audit("subprocess.Popen", executable, args, cwd, env)
    
        if (_USE_POSIX_SPAWN
                and os.path.dirname(executable)
                and preexec_fn is None
                and not close_fds
                and not pass_fds
                and cwd is None
                and (p2cread == -1 or p2cread > 2)
                and (c2pwrite == -1 or c2pwrite > 2)
                and (errwrite == -1 or errwrite > 2)
                and not start_new_session
                and process_group == -1
                and gid is None
                and gids is None
                and uid is None
                and umask < 0):
            self._posix_spawn(args, executable, env, restore_signals,
                              p2cread, p2cwrite,
                              c2pread, c2pwrite,
                              errread, errwrite)
            return
    
        orig_executable = executable
    
        # For transferring possible exec failure from child to parent.
        # Data format: "exception name:hex errno:description"
        # Pickle is not used; it is complex and involves memory allocation.
        errpipe_read, errpipe_write = os.pipe()
        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
        low_fds_to_close = []
        while errpipe_write < 3:
            low_fds_to_close.append(errpipe_write)
            errpipe_write = os.dup(errpipe_write)
        for low_fd in low_fds_to_close:
            os.close(low_fd)
        try:
            try:
                # We must avoid complex work that could involve
                # malloc or free in the child process to avoid
                # potential deadlocks, thus we do all this here.
                # and pass it to fork_exec()
    
                if env is not None:
                    env_list = []
                    for k, v in env.items():
                        k = os.fsencode(k)
                        if b'=' in k:
                            raise ValueError("illegal environment variable name")
                        env_list.append(k + b'=' + os.fsencode(v))
                else:
                    env_list = None  # Use execv instead of execve.
                executable = os.fsencode(executable)
                if os.path.dirname(executable):
                    executable_list = (executable,)
                else:
                    # This matches the behavior of os._execvpe().
                    executable_list = tuple(
                        os.path.join(os.fsencode(dir), executable)
                        for dir in os.get_exec_path(env))
                fds_to_keep = set(pass_fds)
                fds_to_keep.add(errpipe_write)
                self.pid = _fork_exec(
                        args, executable_list,
                        close_fds, tuple(sorted(map(int, fds_to_keep))),
                        cwd, env_list,
                        p2cread, p2cwrite, c2pread, c2pwrite,
                        errread, errwrite,
                        errpipe_read, errpipe_write,
                        restore_signals, start_new_session,
                        process_group, gid, gids, uid, umask,
                        preexec_fn, _USE_VFORK)
                self._child_created = True
            finally:
                # be sure the FD is closed no matter what
                os.close(errpipe_write)
    
            self._close_pipe_fds(p2cread, p2cwrite,
                                 c2pread, c2pwrite,
                                 errread, errwrite)
    
            # Wait for exec to fail or succeed; possibly raising an
            # exception (limited in size)
            errpipe_data = bytearray()
            while True:
                part = os.read(errpipe_read, 50000)
                errpipe_data += part
                if not part or len(errpipe_data) > 50000:
                    break
        finally:
            # be sure the FD is closed no matter what
            os.close(errpipe_read)
    
        if errpipe_data:
            try:
                pid, sts = os.waitpid(self.pid, 0)
                if pid == self.pid:
                    self._handle_exitstatus(sts)
                else:
                    self.returncode = sys.maxsize
            except ChildProcessError:
                pass
    
            try:
                exception_name, hex_errno, err_msg = (
                        errpipe_data.split(b':', 2))
                # The encoding here should match the encoding
                # written in by the subprocess implementations
                # like _posixsubprocess
                err_msg = err_msg.decode()
            except ValueError:
                exception_name = b'SubprocessError'
                hex_errno = b'0'
                err_msg = 'Bad exception data from child: {!r}'.format(
                              bytes(errpipe_data))
            child_exception_type = getattr(
                    builtins, exception_name.decode('ascii'),
                    SubprocessError)
            if issubclass(child_exception_type, OSError) and hex_errno:
                errno_num = int(hex_errno, 16)
                if err_msg == "noexec:chdir":
                    err_msg = ""
                    # The error must be from chdir(cwd).
                    err_filename = cwd
                elif err_msg == "noexec":
                    err_msg = ""
                    err_filename = None
                else:
                    err_filename = orig_executable
                if errno_num != 0:
                    err_msg = os.strerror(errno_num)
                if err_filename is not None:
>                   raise child_exception_type(errno_num, err_msg, err_filename)
E                   FileNotFoundError: [Errno 2] No such file or directory: 'cargo'

args       = ['cargo', 'build', '-p', 'dimos-memory-recorder']
c2pread    = -1
c2pwrite   = -1
child_exception_type = <class 'OSError'>
close_fds  = True
creationflags = 0
cwd        = PosixPath('.../dimos/native/rust')
env        = None
env_list   = None
err_filename = 'cargo'
err_msg    = 'No such file or directory'
errno_num  = 2
errpipe_data = bytearray(b'OSError:2:')
errpipe_read = 295
errpipe_write = 296
errread    = -1
errwrite   = -1
exception_name = bytearray(b'OSError')
executable = b'cargo'
executable_list = (b'.../dimos/dimos/.venv/bin/cargo', b'.../default/bin/cargo', b'/home....../_tool/uv/0.12.9/x86_64/cargo', b'.../home/ubuntu/.local/bin/cargo', b'.../local/sbin/cargo', ...)
fds_to_keep = {296}
gid        = None
gids       = None
hex_errno  = bytearray(b'2')
low_fds_to_close = []
orig_executable = 'cargo'
p2cread    = -1
p2cwrite   = -1
part       = b''
pass_fds   = ()
pid        = 3702057
preexec_fn = None
process_group = -1
restore_signals = True
self       = <Popen: returncode: 255 args: ['cargo', 'build', '-p', 'dimos-memory-recorder']>
shell      = False
start_new_session = False
startupinfo = None
sts        = 65280
uid        = None
umask      = -1

../../../../..../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/subprocess.py:1955: FileNotFoundError
dimos.cli.commands.test_dataprep::test_dataprep_inspect_auto_detects_format
Stack Traces | 0.015s run time
def test_dataprep_inspect_auto_detects_format() -> None:
        result = CliRunner().invoke(dataprep_app, ["inspect", "--help"])
    
        assert result.exit_code == 0
>       assert "--profile" in result.output
E       AssertionError: assert '--profile' in '\x1b[1m                                                                                \x1b[0m\n\x1b[1m \x1b[0m\x1b[1;33mUsage: \x1b[0m\x1b[1mroot inspect [OPTIONS] DATASET\x1b[0m\x1b[1m                                         \x1b[0m\x1b[1m \x1b[0m\n\x1b[1m                                                                                \x1b[0m\n Summarize a recording or built dataset, including incomplete episodes.         \n                                                                                \n\x1b[2m╭─\x1b[0m\x1b[2m Arguments \x1b[0m\x1b[2m─────────────────────────────────────────────────────────────────\x1b[0m\x1b[2m─╮\x1b[0m\n\x1b[2m│\x1b[0m \x1b[31m*\x1b[0m    dataset      \x1b[1;33mPATH\x1b[0m  Recording .db/.mcap, built .hdf5 file, or lerobot    \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                         directory                                            \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                         \x1b[2;31m[required]                                          \x1b[0m \x1b[2m│\x1b[0m\n\x1b[2m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\x1b[2m╭─\x1b[0m\x1b[2m Options \x1b[0m\x1b[2m───────────────────────────────────────────────────────────────────\x1b[0m\x1b[2m─╮\x1b[0m\n\x1b[2m│\x1b[0m \x1b[1;36m-\x1b[0m\x1b[1;36m-profile\x1b[0m             \x1b[1;33mTEXT         \x1b[0m  Validate a recording with this          \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                      module:attribute profile                \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m \x1b[1;36m-\x1b[0m\x1b[1;36m-quality\x1b[0m\x1b[1;36m-mode\x1b[0m        \x1b[1;2;33m[\x1b[0m\x1b[1;33mstrict\x1b[0m\x1b[1;2;33m|\x1b[0m\x1b[1;33mfill\x1b[0m\x1b[1;2;33m]\x1b[0m  Override episode validation: strict |   \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                      fill                                    \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m \x1b[1;36m-\x1b[0m\x1b[1;36m-help\x1b[0m                \x1b[1;33m             \x1b[0m  Show this message and exit.             \x1b[2m│\x1b[0m\n\x1b[2m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\n'
E        +  where '\x1b[1m                                                                                \x1b[0m\n\x1b[1m \x1b[0m\x1b[1;33mUsage: \x1b[0m\x1b[1mroot inspect [OPTIONS] DATASET\x1b[0m\x1b[1m                                         \x1b[0m\x1b[1m \x1b[0m\n\x1b[1m                                                                                \x1b[0m\n Summarize a recording or built dataset, including incomplete episodes.         \n                                                                                \n\x1b[2m╭─\x1b[0m\x1b[2m Arguments \x1b[0m\x1b[2m─────────────────────────────────────────────────────────────────\x1b[0m\x1b[2m─╮\x1b[0m\n\x1b[2m│\x1b[0m \x1b[31m*\x1b[0m    dataset      \x1b[1;33mPATH\x1b[0m  Recording .db/.mcap, built .hdf5 file, or lerobot    \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                         directory                                            \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                         \x1b[2;31m[required]                                          \x1b[0m \x1b[2m│\x1b[0m\n\x1b[2m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\x1b[2m╭─\x1b[0m\x1b[2m Options \x1b[0m\x1b[2m───────────────────────────────────────────────────────────────────\x1b[0m\x1b[2m─╮\x1b[0m\n\x1b[2m│\x1b[0m \x1b[1;36m-\x1b[0m\x1b[1;36m-profile\x1b[0m             \x1b[1;33mTEXT         \x1b[0m  Validate a recording with this          \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                      module:attribute profile                \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m \x1b[1;36m-\x1b[0m\x1b[1;36m-quality\x1b[0m\x1b[1;36m-mode\x1b[0m        \x1b[1;2;33m[\x1b[0m\x1b[1;33mstrict\x1b[0m\x1b[1;2;33m|\x1b[0m\x1b[1;33mfill\x1b[0m\x1b[1;2;33m]\x1b[0m  Override episode validation: strict |   \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                      fill                                    \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m \x1b[1;36m-\x1b[0m\x1b[1;36m-help\x1b[0m                \x1b[1;33m             \x1b[0m  Show this message and exit.             \x1b[2m│\x1b[0m\n\x1b[2m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\n' = <Result okay>.output

result     = <Result okay>

.../cli/commands/test_dataprep.py:34: AssertionError
dimos.cli.commands.test_dataprep::test_dataprep_build_exposes_only_profile_based_configuration
Stack Traces | 0.033s run time
def test_dataprep_build_exposes_only_profile_based_configuration() -> None:
        result = CliRunner().invoke(dataprep_app, ["build", "--help"])
    
        assert result.exit_code == 0
>       assert "--profile" in result.output
E       AssertionError: assert '--profile' in '\x1b[1m                                                                                \x1b[0m\n\x1b[1m \x1b[0m\x1b[1;33mUsage: \x1b[0m\x1b[1mroot build [OPTIONS]\x1b[0m\x1b[1m                                                   \x1b[0m\x1b[1m \x1b[0m\n\x1b[1m                                                                                \x1b[0m\n Build a dataset from a recording (lerobot/hdf5 + dimos_meta.json).             \n                                                                                \n\x1b[2m╭─\x1b[0m\x1b[2m Options \x1b[0m\x1b[2m───────────────────────────────────────────────────────────────────\x1b[0m\x1b[2m─╮\x1b[0m\n\x1b[2m│\x1b[0m \x1b[31m*\x1b[0m  \x1b[1;36m-\x1b[0m\x1b[1;36m-profile\x1b[0m               \x1b[1;33mTEXT         \x1b[0m  DataPrep profile as                \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           module:attribute                   \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           \x1b[2;31m[required]                        \x1b[0m \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m \x1b[31m*\x1b[0m  \x1b[1;36m-\x1b[0m\x1b[1;36m-source\x1b[0m        \x1b[1;32m-s\x1b[0m      \x1b[1;33mPATH         \x1b[0m  Recording .db or .mcap to read     \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           \x1b[2;31m[required]                    \x1b[0m     \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m    \x1b[1;36m-\x1b[0m\x1b[1;36m-output\x1b[0m                \x1b[1;33mPATH         \x1b[0m  Dataset output directory           \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m    \x1b[1;36m-\x1b[0m\x1b[1;36m-quality\x1b[0m\x1b[1;36m-mode\x1b[0m          \x1b[1;2;33m[\x1b[0m\x1b[1;33mstrict\x1b[0m\x1b[1;2;33m|\x1b[0m\x1b[1;33mfill\x1b[0m\x1b[1;2;33m]\x1b[0m  Override episode validation:       \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           strict | fill                      \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m    \x1b[1;36m-\x1b[0m\x1b[1;36m-help\x1b[0m                  \x1b[1;33m             \x1b[0m  Show this message and exit.        \x1b[2m│\x1b[0m\n\x1b[2m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\n'
E        +  where '\x1b[1m                                                                                \x1b[0m\n\x1b[1m \x1b[0m\x1b[1;33mUsage: \x1b[0m\x1b[1mroot build [OPTIONS]\x1b[0m\x1b[1m                                                   \x1b[0m\x1b[1m \x1b[0m\n\x1b[1m                                                                                \x1b[0m\n Build a dataset from a recording (lerobot/hdf5 + dimos_meta.json).             \n                                                                                \n\x1b[2m╭─\x1b[0m\x1b[2m Options \x1b[0m\x1b[2m───────────────────────────────────────────────────────────────────\x1b[0m\x1b[2m─╮\x1b[0m\n\x1b[2m│\x1b[0m \x1b[31m*\x1b[0m  \x1b[1;36m-\x1b[0m\x1b[1;36m-profile\x1b[0m               \x1b[1;33mTEXT         \x1b[0m  DataPrep profile as                \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           module:attribute                   \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           \x1b[2;31m[required]                        \x1b[0m \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m \x1b[31m*\x1b[0m  \x1b[1;36m-\x1b[0m\x1b[1;36m-source\x1b[0m        \x1b[1;32m-s\x1b[0m      \x1b[1;33mPATH         \x1b[0m  Recording .db or .mcap to read     \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           \x1b[2;31m[required]                    \x1b[0m     \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m    \x1b[1;36m-\x1b[0m\x1b[1;36m-output\x1b[0m                \x1b[1;33mPATH         \x1b[0m  Dataset output directory           \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m    \x1b[1;36m-\x1b[0m\x1b[1;36m-quality\x1b[0m\x1b[1;36m-mode\x1b[0m          \x1b[1;2;33m[\x1b[0m\x1b[1;33mstrict\x1b[0m\x1b[1;2;33m|\x1b[0m\x1b[1;33mfill\x1b[0m\x1b[1;2;33m]\x1b[0m  Override episode validation:       \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m                                           strict | fill                      \x1b[2m│\x1b[0m\n\x1b[2m│\x1b[0m    \x1b[1;36m-\x1b[0m\x1b[1;36m-help\x1b[0m                  \x1b[1;33m             \x1b[0m  Show this message and exit.        \x1b[2m│\x1b[0m\n\x1b[2m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\n' = <Result okay>.output

result     = <Result okay>

.../cli/commands/test_dataprep.py:24: AssertionError
dimos.codebase_checks.test_no_dunder_new::test_no_dunder_new
Stack Traces | 2.12s run time
def test_no_dunder_new() -> None:
        """Fail if any test file calls `__new__` to bypass `__init__`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_dunder_new_calls()
        if hits:
            listing = "\n".join(
                f"  - {p.relative_to(dimos_dir)}:{lineno}: {line.strip()}" for p, lineno, line in hits
            )
>           raise AssertionError(
                f"Found __new__ call(s) in test files:\n{listing}\n\n"
                "Tests must construct objects with the real constructor: __init__ is "
                "code under test too, and an object assembled by hand silently rots "
                "when the constructor changes. If __init__ does heavy work, mock the "
                "collaborators it needs instead of skipping it. Only if that is truly "
                "impossible, add the call to the WHITELIST in "
                "dimos/codebase_checks/test_no_dunder_new.py."
            )
E           AssertionError: Found __new__ call(s) in test files:
E             - .../tasks/teach_task/test_teach_task.py:115: winners, _ = TickLoop._arbitrate(object.__new__(TickLoop), commands)
E           
E           Tests must construct objects with the real constructor: __init__ is code under test too, and an object assembled by hand silently rots when the constructor changes. If __init__ does heavy work, mock the collaborators it needs instead of skipping it. Only if that is truly impossible, add the call to the WHITELIST in dimos/codebase_checks/test_no_dunder_new.py.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/.../tasks/teach_task/test_teach_task.py'), 115, '    winners, _ = TickLoop._arbitrate(object.__new__(TickLoop), commands)')]
listing    = '  - .../tasks/teach_task/test_teach_task.py:115: winners, _ = TickLoop._arbitrate(object.__new__(TickLoop), commands)'

dimos/codebase_checks/test_no_dunder_new.py:67: AssertionError
dimos.robot.manipulators.openyam.test_openyam::test_make_openyam_model_config_uses_canonical_arm_joints
Stack Traces | 3.16s run time
def test_make_openyam_model_config_uses_canonical_arm_joints() -> None:
        config = make_openyam_model_config()
    
>       assert OPENYAM_MODEL_PATH.parts[-2:] == ("i2rt", "yam.urdf")

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

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

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

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

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '5d133084-915b-49e8-8f6d-e09354715778.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/yam_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/yam_description.tar.gz', '--exclude='])
relative_path = PosixPath('data/.lfs/yam_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:224: RuntimeError
dimos.robot.manipulators.openyam.blueprints.test_learning_collection::test_native_openyam_paths_are_configurable_from_cli
Stack Traces | 3.28s run time
value = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffd7cbe14140>

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

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

.../coordination/blueprint_config/values.py:113: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
........................................../usr/lib/python3.12/copy.py:162: in deepcopy
    y = _reconstruct(x, memo, *rv)
        _nil       = []
        cls        = <class 'dimos.control.coordinator.TaskConfig'>
        copier     = None
        d          = 281302303588672
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        reductor   = <built-in method __reduce_ex__ of TaskConfig object at 0xffd7cbe14140>
        rv         = (<function __newobj__ at 0xffd8ca8b05e0>, (<class 'dimos.control.coordinator.TaskConfig'>,), {'auto_start': False, 'jo...lter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}, None, None)
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffd7cbe14140>
        y          = []
........................................../usr/lib/python3.12/copy.py:259: in _reconstruct
    state = deepcopy(state, memo)
        args       = <generator object _reconstruct.<locals>.<genexpr> at 0xffd79ff55a80>
        deep       = True
        deepcopy   = <function deepcopy at 0xffd8ca262020>
        dictiter   = None
        func       = <function __newobj__ at 0xffd8ca8b05e0>
        listiter   = None
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        state      = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint...nt_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] TaskConfig object at 0xffd7cbe14140>
        y          = <[AttributeError("'TaskConfig' object has no attribute 'name'") raised in repr()] TaskConfig object at 0xffd840ab4110>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffd8ca090ae0>
        d          = 281304338630592
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint...nt_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xffd8ca262020>
        key        = 'params'
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = {'bindings': [{'hand': 'right', 'target_frame': 'gripper_tip'}], 'joint_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}
        x          = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint...nt_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}, ...}
        y          = {'auto_start': False, 'joint_names': ['yam_joint1', 'yam_joint2', 'yam_joint3', 'yam_joint4', 'yam_joint5', 'yam_joint6'], 'name': 'teleop_openyam', 'priority': 20, ...}
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffd8ca090ae0>
        d          = 281302295383616
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'bindings': [{'hand': 'right', 'target_frame': 'gripper_tip'}], 'joint_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xffd8ca262020>
        key        = 'robot_model'
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xffd8452c7520>
        x          = {'bindings': [{'hand': 'right', 'target_frame': 'gripper_tip'}], 'joint_command_filter_cutoff_hz': 30.0, 'max_command_tracking_error_deg': 10.0, 'max_joint_velocity_rad_s': 2.0, ...}
        y          = {}
........................................../usr/lib/python3.12/copy.py:143: in deepcopy
    y = copier(memo)
        _nil       = []
        cls        = <class 'dimos.manipulation.planning.spec.config.RobotModelConfig'>
        copier     = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...scription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] method object at 0xffd83b450400>
        d          = 281304338560288
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xffd8452c7520>
        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       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModelConfig object at 0xffd8452c7520>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffd8ca090ae0>
        d          = 281302295383808
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'auto_convert_meshes': True, 'base_link': 'base', 'base_pose': Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), 'collision_exclusion_pairs': [], ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xffd8ca262020>
        key        = 'model'
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xffd7cbe1a7b0>
        x          = {'auto_convert_meshes': True, 'base_link': 'base', 'base_pose': Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), 'collision_exclusion_pairs': [], ...}
        y          = {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id_prefix': None, 'g': GlobalConfig(robot_ip=None, robot_ips=N...dless=True, local_relay=False, relay_url=None, dimos_cloud_url='https://api.dimensional.org', dimos_api_key=None), ...}
........................................../usr/lib/python3.12/copy.py:162: in deepcopy
    y = _reconstruct(x, memo, *rv)
        _nil       = []
        cls        = <class 'dimos.robot.assets.model.RobotModel'>
        copier     = None
        d          = 281302303614896
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        reductor   = <built-in method __reduce_ex__ of RobotModel object at 0xffd7cbe1a7b0>
        rv         = (<function __newobj__ at 0xffd8ca8b05e0>, (<class 'dimos.robot.assets.model.RobotModel'>,), {'_fixed_frames': (), '_fi...xclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c5f550>),), ...}, None, None)
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xffd7cbe1a7b0>
        y          = []
........................................../usr/lib/python3.12/copy.py:259: in _reconstruct
    state = deepcopy(state, memo)
        args       = <generator object _reconstruct.<locals>.<genexpr> at 0xffd83b492020>
        deep       = True
        deepcopy   = <function deepcopy at 0xffd8ca262020>
        dictiter   = None
        func       = <function __newobj__ at 0xffd8ca8b05e0>
        listiter   = None
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        state      = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('yam_description', <[Runt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c5f550>),), ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...ption.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] RobotModel object at 0xffd7cbe1a7b0>
        y          = <[AttributeError("'RobotModel' object has no attribute '_source_path'") raised in repr()] RobotModel object at 0xffd7c2e11a00>
........................................../usr/lib/python3.12/copy.py:136: in deepcopy
    y = copier(x, memo)
        _nil       = []
        cls        = <class 'dict'>
        copier     = <function _deepcopy_dict at 0xffd8ca090ae0>
        d          = 281301566557120
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('yam_description', <[Runt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c5f550>),), ...}
        y          = []
........................................../usr/lib/python3.12/copy.py:221: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
        deepcopy   = <function deepcopy at 0xffd8ca262020>
        key        = '_source_path'
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        value      = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...cription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c71050>
        x          = {'_fixed_frames': (), '_fixed_joints': (), '_joint_position_limits': (), '_package_paths': (('yam_description', <[Runt...tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c5f550>),), ...}
        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          = 281304650682448
        memo       = {281301566557120: {}, 281302295383616: {}, 281302295383808: {'default_rpc_timeout': 120.0, 'frame_id': None, 'frame_id...rl='https://api.dimensional.org', dimos_api_key=None), ...}, 281302295385536: {'build': 86400.0, 'start': 1200.0}, ...}
        x          = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...cription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c71050>
        y          = []
dimos/utils/data.py:370: in __getattribute__
    resolved = object.__getattribute__(self, "_ensure_downloaded")()
        name       = '__deepcopy__'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...cription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c71050>
dimos/utils/data.py:353: in _ensure_downloaded
    cache = get_data(filename)
        cache      = None
        filename   = 'yam_description/i2rt/yam.urdf'
        self       = <[RuntimeError("Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempt...cription.tar.gz', '--exclude=']' returned non-zero exit status 1.") raised in repr()] LfsPath object at 0xffd857c71050>
dimos/utils/data.py:310: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'yam_description'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/yam_description/i2rt/yam.urdf')
        name       = 'yam_description/i2rt/yam.urdf'
        nested_path = PosixPath('i2rt/yam.urdf')
        path_parts = ('yam_description', 'i2rt', 'yam.urdf')
dimos/utils/data.py:254: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/data/.lfs/yam_description.tar.gz')
        filename   = 'yam_description'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

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

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

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '5d133084-915b-49e8-8f6d-e09354715778.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/data/.lfs/yam_description.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/yam_description.tar.gz', '--exclude='])
relative_path = PosixPath('data/.lfs/yam_description.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:224: RuntimeError

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

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


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

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

    def _copy_opaque(value: Any) -> Any:
        try:
            return copy.deepcopy(value)
        except Exception as error:
>           raise BlueprintConfigError(
                f"Configuration value of type {type(value).__name__} cannot be copied safely: {error}"
            ) from error
E           dimos.core.coordination.blueprint_config.errors.BlueprintConfigError: Configuration value of type TaskConfig cannot be copied safely: Failed to pull LFS file .../dimos/data/.lfs/yam_description.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/yam_description.tar.gz', '--exclude=']' returned non-zero exit status 1.

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

.../coordination/blueprint_config/values.py:115: BlueprintConfigError
dimos.experimental.memory.test_rust_recorder_e2e::test_rust_artifact_is_readable_by_python_memory2[sqlite]
Stack Traces | 5.64s run time
@pytest.fixture(scope="module")
    def rust_recorder_executable() -> Path:
>       subprocess.run(
            [
                "nix",
                "--extra-experimental-features",
                "nix-command flakes",
                "build",
                "-L",
                ".#dimos-memory-recorder",
                "--no-write-lock-file",
            ],
            cwd=_RUST_WORKSPACE,
            check=True,
        )


.../experimental/memory/test_rust_recorder_e2e.py:76: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

input = None, capture_output = False, timeout = None, check = True
popenargs = (['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', ...],)
kwargs = {'cwd': PosixPath('.../dimos/native/rust')}
process = <Popen: returncode: 1 args: ['nix', '--extra-experimental-features', 'nix-co...>
stdout = None, stderr = None, retcode = 1

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', '--no-write-lock-file']' returned non-zero exit status 1.

capture_output = False
check      = True
input      = None
kwargs     = {'cwd': PosixPath('.../dimos/native/rust')}
popenargs  = (['nix', '--extra-experimental-features', 'nix-command flakes', 'build', '-L', '.#dimos-memory-recorder', ...],)
process    = <Popen: returncode: 1 args: ['nix', '--extra-experimental-features', 'nix-co...>
retcode    = 1
stderr     = None
stdout     = None
timeout    = None

../../../../..../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/subprocess.py:571: CalledProcessError
View the full list of 2 ❄️ flaky test(s)
dimos.e2e_tests.test_manipulation_planning_groups::test_dual_arm_plans_and_dispatches_both_arms_through_control_coordinator

Flake rate in main: 14.29% (Passed 18 times, Failed 3 times)

Stack Traces | 39.2s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7c1f2c331a30>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7c1e2131a5c0>

    def test_dual_arm_plans_and_dispatches_both_arms_through_control_coordinator(
        lcm_spy: LcmSpy,
        start_blueprint: Callable[..., DimosCliCall],
    ) -> None:
        """Plan one generated plan over both arms and dispatch through one trajectory task."""
        _start_openarm_mock_planner(start_blueprint, lcm_spy)
    
        client = RPCClient(None, ManipulationModule)
        coordinator_client = RPCClient(None, ControlCoordinator)
        try:
            groups = _wait_for_groups(client, ALL_GROUP_IDS)
            left_id = groups[LEFT_GROUP_ID].id
            right_id = groups[RIGHT_GROUP_ID].id
    
            tasks = coordinator_client.list_tasks()
            assert tasks == [JOINT_TRAJECTORY_TASK_NAME]
    
            _prepare_for_planning(client, (left_id, right_id))
    
            snapshot = client.get_state()
            planned = client.plan_to_joints(
                {
                    left_id: _offset_target(snapshot, left_id, 0.02),
                    right_id: _offset_target(snapshot, right_id, 0.02),
                }
            )
            assert planned.succeeded, planned
            result = client.execute(blocking=True)
>           assert result.status is ExecutionStatus.COMPLETED
E           assert <ExecutionStatus.REJECTED: 3> is <ExecutionStatus.COMPLETED: 5>
E            +  where <ExecutionStatus.REJECTED: 3> = ExecutionResult(REJECTED, message="joint 'openarm_left_joint1' has no finite ordered position limits", coordinator_result=TrajectoryExecutionResult(status=<TrajectoryExecutionStatus.POSITION_LIMITS_UNAVAILABLE: 6>, message="joint 'openarm_left_joint1' has no finite ordered position limits"), trajectory_status=None).status
E            +  and   <ExecutionStatus.COMPLETED: 5> = ExecutionStatus.COMPLETED

client     = <dimos.core.rpc_client.RPCClient object at 0x7c1e744e2db0>
coordinator_client = <dimos.core.rpc_client.RPCClient object at 0x7c1e2237ca10>
groups     = {'both_arms': PlanningGroupInfo('both_arms', joints=('openarm_left_joint1', 'openarm_left_joint2', 'openarm_left_joint...arm_right_joint6', 'openarm_right_joint7'), base='openarm_body_link0', tip='openarm_right_grasp_frame', gripper=False)}
lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7c1f2c331a30>
left_id    = 'left_arm'
planned    = PlanResult(SUCCEEDED, groups=('left_arm', 'right_arm'), waypoints=2, duration=0.2s, path_length=0.0748, planning_time=0.00218s, iterations=0, message='RoboPlan path found')
result     = ExecutionResult(REJECTED, message="joint 'openarm_left_joint1' has no finite ordered position limits", coordinator_res..._UNAVAILABLE: 6>, message="joint 'openarm_left_joint1' has no finite ordered position limits"), trajectory_status=None)
right_id   = 'right_arm'
snapshot   = ManipulationSnapshot(timestamp=1788471235.18721, operation_status=IDLE, error=None, has_pending_plan=False, execution_..._joint7'], position=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], velocity=[], effort=[])})})
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7c1e2131a5c0>
tasks      = ['joint_trajectory']

dimos/e2e_tests/test_manipulation_planning_groups.py:215: AssertionError
dimos.e2e_tests.test_manipulation_planning_groups::test_single_arm_plans_and_executes_through_control_coordinator

Flake rate in main: 11.76% (Passed 30 times, Failed 4 times)

Stack Traces | 40s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7c1e214d5b80>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7c1e21319d00>

    def test_single_arm_plans_and_executes_through_control_coordinator(
        lcm_spy: LcmSpy,
        start_blueprint: Callable[..., DimosCliCall],
    ) -> None:
        """Plan with one arm and execute through its trajectory task."""
        _start_openarm_mock_planner(start_blueprint, lcm_spy)
    
        client = RPCClient(None, ManipulationModule)
        coordinator_client = RPCClient(None, ControlCoordinator)
        try:
            groups = _wait_for_groups(client, ALL_GROUP_IDS)
            left_id = groups[LEFT_GROUP_ID].id
    
            tasks = coordinator_client.list_tasks()
            assert tasks == [JOINT_TRAJECTORY_TASK_NAME]
    
            _prepare_for_planning(client, (left_id,))
    
            planned = client.plan_to_joints(
                {left_id: _offset_target(client.get_state(), left_id, 0.02)}
            )
            assert planned.succeeded, planned
            result = client.execute(blocking=True)
>           assert result.status is ExecutionStatus.COMPLETED
E           assert <ExecutionStatus.REJECTED: 3> is <ExecutionStatus.COMPLETED: 5>
E            +  where <ExecutionStatus.REJECTED: 3> = ExecutionResult(REJECTED, message="joint 'openarm_left_joint1' has no finite ordered position limits", coordinator_result=TrajectoryExecutionResult(status=<TrajectoryExecutionStatus.POSITION_LIMITS_UNAVAILABLE: 6>, message="joint 'openarm_left_joint1' has no finite ordered position limits"), trajectory_status=None).status
E            +  and   <ExecutionStatus.COMPLETED: 5> = ExecutionStatus.COMPLETED

client     = <dimos.core.rpc_client.RPCClient object at 0x7c1f2c79a870>
coordinator_client = <dimos.core.rpc_client.RPCClient object at 0x7c1e215f0710>
groups     = {'both_arms': PlanningGroupInfo('both_arms', joints=('openarm_left_joint1', 'openarm_left_joint2', 'openarm_left_joint...arm_right_joint6', 'openarm_right_joint7'), base='openarm_body_link0', tip='openarm_right_grasp_frame', gripper=False)}
lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7c1e214d5b80>
left_id    = 'left_arm'
planned    = PlanResult(SUCCEEDED, groups=('left_arm',), waypoints=2, duration=0.2s, path_length=0.0529, planning_time=0.00283s, iterations=0, message='RoboPlan path found')
result     = ExecutionResult(REJECTED, message="joint 'openarm_left_joint1' has no finite ordered position limits", coordinator_res..._UNAVAILABLE: 6>, message="joint 'openarm_left_joint1' has no finite ordered position limits"), trajectory_status=None)
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7c1e21319d00>
tasks      = ['joint_trajectory']

dimos/e2e_tests/test_manipulation_planning_groups.py:179: 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.

Comment thread dimos/cli/commands/collect.py
Comment thread dimos/control/tasks/teach_task/teach_task.py Outdated
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