Skip to content

JUST TRYING THING OUT refactor(rpc): replace pickle with CBOR in RPC and the worker pipe - #4821

Open
paul-nechifor wants to merge 3 commits into
mainfrom
paul/refactor/cbor-rpc
Open

paul-nechifor wants to merge 3 commits into
mainfrom
paul/refactor/cbor-rpc

Conversation

@paul-nechifor

Copy link
Copy Markdown
Contributor

No description provided.

ZenohRPC, LCMRPC and the forkserver worker pipe now serialize with a
CBOR codec (dimos/protocol/encode/cbor.py) instead of pickle. Tuples,
NamedTuples, sets, numpy arrays and scalars, enums, dataclasses,
pydantic models, Path, message types (class ref plus LCM bytes),
classes, generic aliases, module-level functions and functools.partial
round-trip as their original types. Anything else raises TypeError at
the call site naming the type and its path in the payload; classes opt
in with cbor_encode()/cbor_decode() or register_codec().

Transports, RPCClient and its proxies, Actor, streams, Blueprint,
system configurators, LCM and Zenoh services, the relay-bridge
metaclass, lazy asset paths and grasp candidates gain the hooks; their
pickle hooks are removed. StreamDescriptor carries the real type and
transport, HostFragment encodes its payload with the codec, and the
pickled pubsub transports keep pickle.

Decode failures surface at the caller instead of timing out, a worker
that cannot decode a request replies with an error, and the worker
encodes before taking its lock. Also fixes start/stop on
DemoCalculatorSkill and OsmSkill (missing @rpc, so pickle ran them on
a parent-side copy) and gives dimos Header its own lcm_decode.
@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

❌ 9 Tests Failed:

Tests completed Failed Passed Skipped
7863 9 7854 58
View the top 3 failed test(s) by shortest run time
dimos.core.test_e2e_daemon.TestCLIWithRealBlueprint::test_status_shows_live_entry_via_registry
Stack Traces | 4.14s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 210, in live_blueprint
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestCLIWithRealBlueprint::test_stop_kills_real_workers
Stack Traces | 4.2s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 210, in live_blueprint
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestCLIWithRealBlueprint::test_status_shows_live_blueprint
Stack Traces | 4.27s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 210, in live_blueprint
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestDaemonE2E::test_stale_cleanup
Stack Traces | 4.27s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 78, in coordinator
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestDaemonE2E::test_single_worker_lifecycle
Stack Traces | 4.28s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 78, in coordinator
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestDaemonE2E::test_registry_entry_details
Stack Traces | 4.58s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 78, in coordinator
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestDaemonE2E::test_health_check_detects_dead_worker
Stack Traces | 5.03s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 78, in coordinator
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.core.test_e2e_daemon.TestDaemonE2E::test_multiple_workers
Stack Traces | 6.26s run time
+ Exception Group Traceback (most recent call last):
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 341, in from_call
  |     result: TResult | None = func()
  |                              ^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 242, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 840, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/logging.py", line 829, in _runtest_for
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/capture.py", line 893, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
  |     yield
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 160, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File ".../dimos/dimos/.venv/lib/python3.12............/site-packages/_pytest/runner.py", line 514, in setup
  |     col.setup()
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 697, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 532, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 617, in _get_active_fixturedef
  |     fixturedef.execute(request=subrequest)
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1091, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12....../site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pytest_asyncio/plugin.py", line 926, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
  |     return result.get_result()
  |            ^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/pluggy/_result.py", line 103, in get_result
  |     raise exc.with_traceback(tb)
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
  |     res = yield
  |           ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |   File ".../dimos/dimos/.venv/lib/python3.12.../site-packages/_pytest/setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12......................................./site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 1140, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../dimos/dimos/.venv/lib/python3.12................../site-packages/_pytest/fixtures.py", line 891, in call_fixture_func
  |     fixture_result = next(generator)
  |                      ^^^^^^^^^^^^^^^
  |   File ".../dimos/core/test_e2e_daemon.py", line 88, in coordinator_2w
  |     coord = ModuleCoordinator.build(bp)
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 456, in build
  |     start_durations = coordinator.start_all_modules()
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".../core/coordination/module_coordinator.py", line 300, in start_all_modules
  |     safe_thread_map(modules, start)
  |   File ".../dimos/utils/safe_thread_map.py", line 84, in safe_thread_map
  |     raise ExceptionGroup("safe_thread_map failed", errors)
  | ExceptionGroup: safe_thread_map failed (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PingModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".../dimos/utils/safe_thread_map.py", line 68, in safe_thread_map
    |     outcomes[idx] = fut.result()
    |                     ^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 449, in result
    |     return self.__get_result()
    |            ^^^^^^^^^^^^^^^^^^^
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12............/concurrent/futures/_base.py", line 401, in __get_result
    |     raise self._exception
    |   File "................../github/home/.local.../uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12....../concurrent/futures/thread.py", line 59, in run
    |     result = self.fn(*self.args, **self.kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File ".../core/coordination/module_coordinator.py", line 297, in start
    |     module.start()
    |     ^^^^^^^^^^^^
    |   File ".../dimos/core/rpc_client.py", line 226, in __getattr__
    |     raise AttributeError(
    | AttributeError: PongModule.start is a method without @rpc, so the worker cannot send it back; decorate it with @rpc to call it from the host
    +------------------------------------
dimos.e2e_tests.test_dimsim_spatial_memory::test_go_to_the_bed
Stack Traces | 275s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x78f630f095b0>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x78f623ca8900>
wait_for_system_ready = <function wait_for_system_ready.<locals>.wait at 0x78f623ca8fe0>
human_input = <function human_input.<locals>.send_human_input at 0x78f623ca8360>
dim_sim = <dimos.e2e_tests.dim_sim_client.DimSimClient object at 0x78f630623740>
explore_house = <function explore_house.<locals>.explore at 0x78f623ca9800>

    @pytest.mark.self_hosted_large
    def test_go_to_the_bed(
        lcm_spy, start_blueprint, wait_for_system_ready, human_input, dim_sim, explore_house
    ) -> None:
        start_blueprint(
            "run",
            "unitree-go2-agentic",
            simulator="dimsim",
        )
        wait_for_system_ready(timeout=300.0)
    
        explore_house()
    
        human_input("go to the bed")
    
>       lcm_spy.wait_until_odom_position(-3.567, -1.332, threshold=2, timeout=180)

dim_sim    = <dimos.e2e_tests.dim_sim_client.DimSimClient object at 0x78f630623740>
explore_house = <function explore_house.<locals>.explore at 0x78f623ca9800>
human_input = <function human_input.<locals>.send_human_input at 0x78f623ca8360>
lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x78f630f095b0>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x78f623ca8900>
wait_for_system_ready = <function wait_for_system_ready.<locals>.wait at 0x78f623ca8fe0>

dimos/e2e_tests/test_dimsim_spatial_memory.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/e2e_tests/lcm_spy.py:143: in wait_until_odom_position
    self.wait_for_message_result(
        predicate  = <function LcmSpy.wait_until_odom_position.<locals>.predicate at 0x78f623cbb880>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x78f630f095b0>
        threshold  = 2
        timeout    = 180
        x          = -3.567
        y          = -1.332
dimos/e2e_tests/lcm_spy.py:129: in wait_for_message_result
    wait_until(
        event      = <threading.Event at 0x78f649984110: unset>
        fail_message = 'Failed to get to position x=-3.567, y=-1.332'
        listener   = <function LcmSpy.wait_for_message_result.<locals>.listener at 0x78f623ca9d00>
        predicate  = <function LcmSpy.wait_until_odom_position.<locals>.predicate at 0x78f623cbb880>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x78f630f095b0>
        timeout    = 180
        topic      = '/odom#geometry_msgs.PoseStamped'
        type       = <class 'dimos.msgs.geometry_msgs.PoseStamped.PoseStamped'>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

predicate = <bound method Event.is_set of <threading.Event at 0x78f649984110: unset>>

    def wait_until(
        predicate: Callable[[], bool],
        *,
        timeout: float,
        interval: float = 0.1,
        message: str | None = None,
    ) -> None:
        """Poll ``predicate`` until it returns truthy or ``timeout`` elapses."""
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            if predicate():
                return
            time.sleep(interval)
>       raise TimeoutError(message or f"Timed out after {timeout}s waiting for condition")
E       TimeoutError: Failed to get to position x=-3.567, y=-1.332

deadline   = 9378580.356357168
interval   = 0.1
message    = 'Failed to get to position x=-3.567, y=-1.332'
predicate  = <bound method Event.is_set of <threading.Event at 0x78f649984110: unset>>
timeout    = 180

.../utils/testing/waiting.py:35: TimeoutError

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

@greptile-apps

greptile-apps Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 3/5

[High impact] The change reaches shared RPC and worker communication across Dimos features.

Not safe to merge until image roundtrips and DemoRobot startup are fixed; the blueprint-test coverage concern is non-blocking.

Findings

  1. P1 Images change across RPC ▶
  2. P1 Demo startup fails ▶
  3. P2 Broken blueprints look skipped ▶

Summary

Replaces pickle with CBOR for RPC, worker messages, and hosted fragments, adding typed encoding and custom support for proxies, streams, transports, and lazy paths.

Two compatibility regressions must be fixed before merging: floating-point RGB images fail to decode and ordinary images become read-only; DemoRobot startup fails because its plain lifecycle methods cannot cross the new worker pipe. The new blueprint test also has a non-blocking coverage gap: unexpected import errors become skips instead of test failures.

paul-nechifor explicitly retains the trusted-network limitation in the changed distributed-use documentation: reachable hosts can import and run installed code, without authentication or encryption.

  • RPC calls now send CBOR requests and replies.
  • Workers now receive blueprints and commands as CBOR.
  • Configured Dimos values now cross processes with their types intact.

Reviews (1) · Last reviewed commit: "refactor(rpc): replace pickle with CBOR ..." · Reviewed by Greptile

Comment thread dimos/protocol/encode/cbor.py Outdated
Comment thread dimos/core/coordination/python_worker.py
Comment thread dimos/robot/test_blueprints_cross_processes.py Outdated
Review fixes for the pickle-to-CBOR commit.

Decode no longer calls anything the payload names. A registered codec
travels as its type plus the registering module; the receiver imports
that module and takes the decoder from its own registry, so a payload
naming builtins:eval is refused.

StreamDescriptor carries a TransportSpec instead of a live transport, so
list_streams and dimos shell no longer open an LCM socket per stream on
every call. TransportSpec moves to dimos/core/stream.py and describes a
live transport with TransportSpec.of(). Every transport's cbor_encode()
returns constructor kwargs and PubSubTransport.cbor_decode is cls(**data);
ZenohTransport and pZenohTransport take a qos kwarg, DDSTransport accepts
the qos dict it encodes, and ROSTransport drops its unused kwargs.

LCM RPC wraps bodies as bytes in a primitives-only envelope with a wire
version: an undecodable request gets an error reply, an undecodable reply
raises at the caller instead of timing out, and traffic from another
dimos version is logged and ignored.

Codec: encode() only raises TypeError and hook failures keep their path,
enums travel by value so Flag combinations survive, typing.List, Optional,
Union, Literal and Any are accepted, union and generic-alias payloads must
hold types, a missing dataclass field names the class and field, and the
fields-only dataclass rule and the pydantic private-state rule are
documented and tested. The worker decodes replies outside its lock.

Obstacle.points is an (N, 3) ndarray with an array-aware __eq__, so the
voxel map crosses RPC as one tag instead of one tuple per cell. A cbor2
native-encoder hybrid was measured and rejected; _prepare says why.

Also: isolated_python sends its config over stdin as CBOR, stale pickle
comments and deepcopy-hook docstrings are updated, and the LCM and Zenoh
service, Zenoh pubsub and DDS transport round trips are tested again.
@paul-nechifor paul-nechifor added the backport:skip Skip creating a backport to any release branches label Oct 25, 2026

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

go2 replay realtime (arm64)

Details
Benchmark suite Current: 66a52ab Previous: bedd25b Ratio
peak memory 1750.379 MB 1715.012 MB 1.02
peak threads 379 threads 376 threads 1.01
network (transport) 2726.593 MB 2728.384 MB 1.00
disk write 1.473 MB 1.695 MB 0.87
instructions 137.522 G 137.518 G 1.00

This comment was automatically generated by workflow using github-action-benchmark.

…ides fail clearly

Review fixes for PR #4821.

Image travelled as its LCM form, which labels every RGB image rgb8 and decodes
through np.frombuffer: float32 RGB images failed to reshape and uint8 images came
back read-only. Image now has cbor_encode/cbor_decode built on its pickle state,
so dtype, shape, format, timestamp and writability survive.

Module.rpcs only sees methods carrying the @rpc marker, so a plain start()
override drops out of the proxy's RPC set and the host fetched the bound method
from the worker, which CBOR rejects (pickle shipped a module copy and ran start()
in the parent). DemoRobot and the image encoding demo modules get @rpc, the proxy
raises an AttributeError that names the missing decorator, and a codebase check
fails any built-in blueprint whose module overrides an @rpc method without it.

The blueprint codec test skipped on any exception; it now uses load_blueprint and
skips only on OptionalDependencyError, with the self_hosted marks and robot_ips
monkeypatch the sibling tests use.

This branch was successfully deployed

1 active (outdated) deployment
cachix — 313e32c3 Deployed Oct 25, 2026 by paul-nechifor via cachix-build #14238
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip Skip creating a backport to any release branches

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant