Skip to content

async recorder callbacks#2577

Open
jeff-hykin wants to merge 7 commits into
mainfrom
jeff/fix/pose_setter_for
Open

async recorder callbacks#2577
jeff-hykin wants to merge 7 commits into
mainfrom
jeff/fix/pose_setter_for

Conversation

@jeff-hykin

@jeff-hykin jeff-hykin commented Jun 24, 2026

Copy link
Copy Markdown
Member

Async pose_setter_for for memory2. Updates the fastlio and go2 recorders to match.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
1907 3 1904 159
View the top 3 failed test(s) by shortest run time
dimos.robot.test_all_blueprints::test_blueprint_is_valid[mid360-realsense-record-with-pcap]
Stack Traces | 0.006s run time
blueprint_name = 'mid360-realsense-record-with-pcap'

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

blueprint_name = 'mid360-realsense-record-with-pcap'

dimos/robot/test_all_blueprints.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/test_all_blueprints.py:79: in _check_blueprint
    blueprint = get_blueprint_by_name(blueprint_name)
        blueprint_name = 'mid360-realsense-record-with-pcap'
        message    = '@pose_setter_for must decorate an `async def` method; PointlioPoseRecorder._odom_pose is not async'
dimos/robot/get_all_blueprints.py:47: in get_blueprint_by_name
    module = __import__(module_path, fromlist=[attr])
        attr       = 'mid360_realsense_record_with_pcap'
        module_path = 'dimos.robot.assembly.mid360_realsense_30'
        name       = 'mid360-realsense-record-with-pcap'
.../robot/assembly/mid360_realsense_30.py:55: in <module>
    from dimos.hardware.sensors.lidar.pointlio.pose_recorder import PointlioPoseRecorder
        In         = <class 'dimos.core.stream.In'>
        Mid360     = <class 'dimos.hardware.sensors.lidar.livox.module.Mid360'>
        PointLio   = <class 'dimos.hardware.sensors.lidar.pointlio.module.PointLio'>
        RealSenseCamera = <class 'dimos.hardware.sensors.camera.realsense.camera.RealSenseCamera'>
        __annotations__ = {}
        __builtins__ = <builtins>
        __cached__ = '.../assembly/__pycache__/mid360_realsense_30.cpython-312.pyc'
        __doc__    = 'The RealSense D435i + Mid-360 rig: static mount frames, recorder, record blueprints.\n\nA single physical sensor asse...-IMU extrinsic comes from the official Mid-360 config (extrinsic_T flipped gives\nthe IMU position in lidar coords).\n'
        __file__   = '/home/runner/work/dimos/dimos/.../robot/assembly/mid360_realsense_30.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13bb3d1c0>
        __name__   = 'dimos.robot.assembly.mid360_realsense_30'
        __package__ = 'dimos.robot.assembly'
        __spec__   = ModuleSpec(name='dimos.robot.assembly.mid360_realsense_30', loader=<_frozen_importlib_external.SourceFileLoader object at 0xfff13bb3d1c0>, origin='/home/runner/work/dimos/dimos/.../robot/assembly/mid360_realsense_30.py')
        annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)
        autoconnect = <function autoconnect at 0xfff1e94be7a0>
        math       = <module 'math' (built-in)>
.../lidar/pointlio/pose_recorder.py:50: in <module>
    class PointlioPoseRecorder(Recorder):
        In         = <class 'dimos.core.stream.In'>
        Odometry   = <class 'dimos.msgs.nav_msgs.Odometry.Odometry'>
        OnExisting = <enum 'OnExisting'>
        PointCloud2 = <class 'dimos.msgs.sensor_msgs.PointCloud2.PointCloud2'>
        PointlioPoseRecorderConfig = <class 'dimos.hardware.sensors.lidar.pointlio.pose_recorder.PointlioPoseRecorderConfig'>
        Pose       = <class 'dimos.msgs.geometry_msgs.Pose.Pose'>
        Recorder   = <class 'dimos.memory2.module.Recorder'>
        RecorderConfig = <class 'dimos.memory2.module.RecorderConfig'>
        _POSE_MATCH_TOL = 0.1
        __builtins__ = <builtins>
        __cached__ = '.../pointlio/__pycache__/pose_recorder.cpython-312.pyc'
        __doc__    = 'Memory2 recorder base that anchors Point-LIO frames with the live odometry pose.\n\nSubclass with whatever companion ...s.hardware.sensors.lidar.pointlio.recorder`, the\nstandalone time-aligning recorder used by the pcap-replay tooling.\n'
        __file__   = '/home/runner/work/dimos/dimos/.../lidar/pointlio/pose_recorder.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13bb3d3d0>
        __name__   = 'dimos.hardware.sensors.lidar.pointlio.pose_recorder'
        __package__ = 'dimos.hardware.sensors.lidar.pointlio'
        __spec__   = ModuleSpec(name='dimos.hardware.sensors.lidar.pointlio.pose_recorder', loader=<_frozen_importlib_external.SourceFileLo...ject at 0xfff13bb3d3d0>, origin='/home/runner/work/dimos/dimos/.../lidar/pointlio/pose_recorder.py')
        annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)
        pose_setter_for = <function pose_setter_for at 0xfff1415efb00>
        time       = <module 'time' (built-in)>
.../lidar/pointlio/pose_recorder.py:59: in PointlioPoseRecorder
    @pose_setter_for("pointlio_odometry")
        __annotations__ = {'_last_odom_pose': 'Pose | None', '_last_odom_raw_ts': 'float', 'config': 'PointlioPoseRecorderConfig', 'pointlio_lidar': 'In[PointCloud2]', ...}
        __module__ = 'dimos.hardware.sensors.lidar.pointlio.pose_recorder'
        __qualname__ = 'PointlioPoseRecorder'
        _last_odom_pose = None
        _last_odom_raw_ts = 0.0
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

fn = <function PointlioPoseRecorder._odom_pose at 0xfff13cb28220>

    def decorate(fn: Any) -> Any:
        if not inspect.iscoroutinefunction(fn):
>           raise TypeError(
                f"@pose_setter_for must decorate an `async def` method; "
                f"{getattr(fn, '__qualname__', fn)} is not async"
            )
E           TypeError: @pose_setter_for must decorate an `async def` method; PointlioPoseRecorder._odom_pose is not async

fn         = <function PointlioPoseRecorder._odom_pose at 0xfff13cb28220>
stream_names = ('pointlio_odometry',)

dimos/memory2/module.py:287: TypeError
dimos.robot.test_all_blueprints::test_blueprint_is_valid[mid360-realsense-record]
Stack Traces | 0.007s run time
blueprint_name = 'mid360-realsense-record'

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

blueprint_name = 'mid360-realsense-record'

dimos/robot/test_all_blueprints.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/test_all_blueprints.py:79: in _check_blueprint
    blueprint = get_blueprint_by_name(blueprint_name)
        blueprint_name = 'mid360-realsense-record'
        message    = '@pose_setter_for must decorate an `async def` method; PointlioPoseRecorder._odom_pose is not async'
dimos/robot/get_all_blueprints.py:47: in get_blueprint_by_name
    module = __import__(module_path, fromlist=[attr])
        attr       = 'mid360_realsense_record'
        module_path = 'dimos.robot.assembly.mid360_realsense_30'
        name       = 'mid360-realsense-record'
.../robot/assembly/mid360_realsense_30.py:55: in <module>
    from dimos.hardware.sensors.lidar.pointlio.pose_recorder import PointlioPoseRecorder
        In         = <class 'dimos.core.stream.In'>
        Mid360     = <class 'dimos.hardware.sensors.lidar.livox.module.Mid360'>
        PointLio   = <class 'dimos.hardware.sensors.lidar.pointlio.module.PointLio'>
        RealSenseCamera = <class 'dimos.hardware.sensors.camera.realsense.camera.RealSenseCamera'>
        __annotations__ = {}
        __builtins__ = <builtins>
        __cached__ = '.../assembly/__pycache__/mid360_realsense_30.cpython-312.pyc'
        __doc__    = 'The RealSense D435i + Mid-360 rig: static mount frames, recorder, record blueprints.\n\nA single physical sensor asse...-IMU extrinsic comes from the official Mid-360 config (extrinsic_T flipped gives\nthe IMU position in lidar coords).\n'
        __file__   = '/home/runner/work/dimos/dimos/.../robot/assembly/mid360_realsense_30.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13c32b3b0>
        __name__   = 'dimos.robot.assembly.mid360_realsense_30'
        __package__ = 'dimos.robot.assembly'
        __spec__   = ModuleSpec(name='dimos.robot.assembly.mid360_realsense_30', loader=<_frozen_importlib_external.SourceFileLoader object at 0xfff13c32b3b0>, origin='/home/runner/work/dimos/dimos/.../robot/assembly/mid360_realsense_30.py')
        annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)
        autoconnect = <function autoconnect at 0xfff1e94be7a0>
        math       = <module 'math' (built-in)>
.../lidar/pointlio/pose_recorder.py:50: in <module>
    class PointlioPoseRecorder(Recorder):
        In         = <class 'dimos.core.stream.In'>
        Odometry   = <class 'dimos.msgs.nav_msgs.Odometry.Odometry'>
        OnExisting = <enum 'OnExisting'>
        PointCloud2 = <class 'dimos.msgs.sensor_msgs.PointCloud2.PointCloud2'>
        PointlioPoseRecorderConfig = <class 'dimos.hardware.sensors.lidar.pointlio.pose_recorder.PointlioPoseRecorderConfig'>
        Pose       = <class 'dimos.msgs.geometry_msgs.Pose.Pose'>
        Recorder   = <class 'dimos.memory2.module.Recorder'>
        RecorderConfig = <class 'dimos.memory2.module.RecorderConfig'>
        _POSE_MATCH_TOL = 0.1
        __builtins__ = <builtins>
        __cached__ = '.../pointlio/__pycache__/pose_recorder.cpython-312.pyc'
        __doc__    = 'Memory2 recorder base that anchors Point-LIO frames with the live odometry pose.\n\nSubclass with whatever companion ...s.hardware.sensors.lidar.pointlio.recorder`, the\nstandalone time-aligning recorder used by the pcap-replay tooling.\n'
        __file__   = '/home/runner/work/dimos/dimos/.../lidar/pointlio/pose_recorder.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13c32b5c0>
        __name__   = 'dimos.hardware.sensors.lidar.pointlio.pose_recorder'
        __package__ = 'dimos.hardware.sensors.lidar.pointlio'
        __spec__   = ModuleSpec(name='dimos.hardware.sensors.lidar.pointlio.pose_recorder', loader=<_frozen_importlib_external.SourceFileLo...ject at 0xfff13c32b5c0>, origin='/home/runner/work/dimos/dimos/.../lidar/pointlio/pose_recorder.py')
        annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)
        pose_setter_for = <function pose_setter_for at 0xfff1415efb00>
        time       = <module 'time' (built-in)>
.../lidar/pointlio/pose_recorder.py:59: in PointlioPoseRecorder
    @pose_setter_for("pointlio_odometry")
        __annotations__ = {'_last_odom_pose': 'Pose | None', '_last_odom_raw_ts': 'float', 'config': 'PointlioPoseRecorderConfig', 'pointlio_lidar': 'In[PointCloud2]', ...}
        __module__ = 'dimos.hardware.sensors.lidar.pointlio.pose_recorder'
        __qualname__ = 'PointlioPoseRecorder'
        _last_odom_pose = None
        _last_odom_raw_ts = 0.0
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

fn = <function PointlioPoseRecorder._odom_pose at 0xfff139f0a160>

    def decorate(fn: Any) -> Any:
        if not inspect.iscoroutinefunction(fn):
>           raise TypeError(
                f"@pose_setter_for must decorate an `async def` method; "
                f"{getattr(fn, '__qualname__', fn)} is not async"
            )
E           TypeError: @pose_setter_for must decorate an `async def` method; PointlioPoseRecorder._odom_pose is not async

fn         = <function PointlioPoseRecorder._odom_pose at 0xfff139f0a160>
stream_names = ('pointlio_odometry',)

dimos/memory2/module.py:287: TypeError
dimos.robot.test_all_blueprints::test_blueprint_is_valid[unitree-go2-mid360-record]
Stack Traces | 0.017s run time
blueprint_name = 'unitree-go2-mid360-record'

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

blueprint_name = 'unitree-go2-mid360-record'

dimos/robot/test_all_blueprints.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/robot/test_all_blueprints.py:79: in _check_blueprint
    blueprint = get_blueprint_by_name(blueprint_name)
        blueprint_name = 'unitree-go2-mid360-record'
        message    = '@pose_setter_for must decorate an `async def` method; PointlioPoseRecorder._odom_pose is not async'
dimos/robot/get_all_blueprints.py:47: in get_blueprint_by_name
    module = __import__(module_path, fromlist=[attr])
        attr       = 'unitree_go2_mid360_record'
        module_path = 'dimos.robot.unitree.go2.blueprints.basic.unitree_go2_mid360_record'
        name       = 'unitree-go2-mid360-record'
.../blueprints/basic/unitree_go2_mid360_record.py:43: in <module>
    from dimos.robot.unitree.go2.go2_mid360_recorder import Go2Mid360Recorder
        GO2Connection = <class 'dimos.robot.unitree.go2.connection.GO2Connection'>
        Mid360     = <class 'dimos.hardware.sensors.lidar.livox.module.Mid360'>
        Mid360PcapRecorder = <class 'dimos.hardware.sensors.lidar.virtual_mid360.recorder.Mid360PcapRecorder'>
        ModuleCoordinator = <class 'dimos.core.coordination.module_coordinator.ModuleCoordinator'>
        MovementManager = <class 'dimos.navigation.movement_manager.movement_manager.MovementManager'>
        Path       = <class 'pathlib.Path'>
        PointLio   = <class 'dimos.hardware.sensors.lidar.pointlio.module.PointLio'>
        __builtins__ = <builtins>
        __cached__ = '.../basic/__pycache__/unitree_go2_mid360_record.cpython-312.pyc'
        __doc__    = 'Drive-and-record blueprint for the Go2 + Mid-360 rig.\n\nPygame WASD teleop drives the dog while Point-LIO odom+lidar...TLIO_LIDAR_IP=192.168.1.171\n    uv run python .../blueprints/basic/unitree_go2_mid360_record.py\n'
        __file__   = '/home/runner/work/dimos/dimos/.../blueprints/basic/unitree_go2_mid360_record.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13b9e0f50>
        __name__   = 'dimos.robot.unitree.go2.blueprints.basic.unitree_go2_mid360_record'
        __package__ = 'dimos.robot.unitree.go2.blueprints.basic'
        __spec__   = ModuleSpec(name='dimos.robot.unitree.go2.blueprints.basic.unitree_go2_mid360_record', loader=<_frozen_importlib_extern...b9e0f50>, origin='/home/runner/work/dimos/dimos/.../blueprints/basic/unitree_go2_mid360_record.py')
        autoconnect = <function autoconnect at 0xfff1e94be7a0>
        datetime   = <class 'datetime.datetime'>
        global_config = GlobalConfig(robot_ip=None, robot_ips=None, unitree_aes_128_key=None, xarm7_ip=None, xarm6_ip=None, can_port=None, dev...e, obstacle_avoidance=True, detection_model='moondream', listen_host='127.0.0.1', dimsim_scene='apt', dimsim_port=8090)
        os         = <module 'os' (frozen)>
.../unitree/go2/go2_mid360_recorder.py:27: in <module>
    from dimos.hardware.sensors.lidar.pointlio.pose_recorder import PointlioPoseRecorder
        In         = <class 'dimos.core.stream.In'>
        __builtins__ = <builtins>
        __cached__ = '.../go2/__pycache__/go2_mid360_recorder.cpython-312.pyc'
        __doc__    = 'Records the Go2 + Mid-360 rig into a memory2 SQLite db.\n\nCaptures Point-LIO odom + lidar (trajectory baked into ``p...rint to\ncapture it. Companion streams are recorded as-is and anchored via the static mount\nframes published on tf.\n'
        __file__   = '/home/runner/work/dimos/dimos/.../unitree/go2/go2_mid360_recorder.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13b9e1700>
        __name__   = 'dimos.robot.unitree.go2.go2_mid360_recorder'
        __package__ = 'dimos.robot.unitree.go2'
        __spec__   = ModuleSpec(name='dimos.robot.unitree.go2.go2_mid360_recorder', loader=<_frozen_importlib_external.SourceFileLoader object at 0xfff13b9e1700>, origin='/home/runner/work/dimos/dimos/.../unitree/go2/go2_mid360_recorder.py')
        annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)
.../lidar/pointlio/pose_recorder.py:50: in <module>
    class PointlioPoseRecorder(Recorder):
        In         = <class 'dimos.core.stream.In'>
        Odometry   = <class 'dimos.msgs.nav_msgs.Odometry.Odometry'>
        OnExisting = <enum 'OnExisting'>
        PointCloud2 = <class 'dimos.msgs.sensor_msgs.PointCloud2.PointCloud2'>
        PointlioPoseRecorderConfig = <class 'dimos.hardware.sensors.lidar.pointlio.pose_recorder.PointlioPoseRecorderConfig'>
        Pose       = <class 'dimos.msgs.geometry_msgs.Pose.Pose'>
        Recorder   = <class 'dimos.memory2.module.Recorder'>
        RecorderConfig = <class 'dimos.memory2.module.RecorderConfig'>
        _POSE_MATCH_TOL = 0.1
        __builtins__ = <builtins>
        __cached__ = '.../pointlio/__pycache__/pose_recorder.cpython-312.pyc'
        __doc__    = 'Memory2 recorder base that anchors Point-LIO frames with the live odometry pose.\n\nSubclass with whatever companion ...s.hardware.sensors.lidar.pointlio.recorder`, the\nstandalone time-aligning recorder used by the pcap-replay tooling.\n'
        __file__   = '/home/runner/work/dimos/dimos/.../lidar/pointlio/pose_recorder.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0xfff13b9e0230>
        __name__   = 'dimos.hardware.sensors.lidar.pointlio.pose_recorder'
        __package__ = 'dimos.hardware.sensors.lidar.pointlio'
        __spec__   = ModuleSpec(name='dimos.hardware.sensors.lidar.pointlio.pose_recorder', loader=<_frozen_importlib_external.SourceFileLo...ject at 0xfff13b9e0230>, origin='/home/runner/work/dimos/dimos/.../lidar/pointlio/pose_recorder.py')
        annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)
        pose_setter_for = <function pose_setter_for at 0xfff1415efb00>
        time       = <module 'time' (built-in)>
.../lidar/pointlio/pose_recorder.py:59: in PointlioPoseRecorder
    @pose_setter_for("pointlio_odometry")
        __annotations__ = {'_last_odom_pose': 'Pose | None', '_last_odom_raw_ts': 'float', 'config': 'PointlioPoseRecorderConfig', 'pointlio_lidar': 'In[PointCloud2]', ...}
        __module__ = 'dimos.hardware.sensors.lidar.pointlio.pose_recorder'
        __qualname__ = 'PointlioPoseRecorder'
        _last_odom_pose = None
        _last_odom_raw_ts = 0.0
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

fn = <function PointlioPoseRecorder._odom_pose at 0xfff1388ab920>

    def decorate(fn: Any) -> Any:
        if not inspect.iscoroutinefunction(fn):
>           raise TypeError(
                f"@pose_setter_for must decorate an `async def` method; "
                f"{getattr(fn, '__qualname__', fn)} is not async"
            )
E           TypeError: @pose_setter_for must decorate an `async def` method; PointlioPoseRecorder._odom_pose is not async

fn         = <function PointlioPoseRecorder._odom_pose at 0xfff1388ab920>
stream_names = ('pointlio_odometry',)

dimos/memory2/module.py:287: TypeError

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

@jeff-hykin jeff-hykin force-pushed the jeff/fix/pose_setter_for branch from 27a5a72 to 6276f5b Compare June 24, 2026 02:52
Convert the memory2 Recorder from thread/disposable rx subscriptions to
manual async callbacks via process_observable, and let pose_setter_for
methods be async (awaited in _resolve_pose). Update the fastlio and go2
recorders accordingly.
@jeff-hykin jeff-hykin force-pushed the jeff/fix/pose_setter_for branch from 6276f5b to dba1e5a Compare June 24, 2026 06:45
Raise TypeError at decoration time if a non-async function is decorated,
and always await the setter in _resolve_pose.
@jeff-hykin jeff-hykin changed the title recorder: async callbacks + async pose_setter_for async recorder callbacks Jun 24, 2026
@jeff-hykin jeff-hykin marked this pull request as ready for review June 24, 2026 07:12
@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates Recorder pose setters to async def, wires them through the module's event-loop dispatcher, and adds visible dropped-frame warnings when the sink can't keep up with input — addressing the silent data-loss concern from a previous review thread.

  • @pose_setter_for now enforces async def at decoration time (raises TypeError for sync methods), and _resolve_pose / on_msg are updated accordingly; both recorder subclasses (FastLio2Recorder, Go2Memory) are updated to match.
  • process_observable gains an on_drop hook (threaded through _make_async_dispatch) so callers can react when the LATEST coalescing slot discards a message; Recorder uses this to count and periodically log dropped frames per stream.

Confidence Score: 5/5

Safe to merge — the async migration is mechanically straightforward and the new on_drop hook is backward-compatible with a default of None.

All changed paths are well-contained: @pose_setter_for validation fires at decoration time (caught immediately in tests or at startup), the dispatcher plumbing is unchanged beyond threading through the optional on_drop callback, and both recorder subclasses are updated in the same commit. The only notable discrepancy is a docstring that promises a power-of-ten warning cadence but implements a fixed-stride one — a cosmetic mismatch that doesn't affect correctness.

The dropped-frame logging logic in dimos/memory2/module.py (_on_frame_dropped) has a doc/code mismatch worth a quick look before merging.

Important Files Changed

Filename Overview
dimos/core/module.py Adds optional on_drop callback to process_observable / _make_async_dispatch; callback is invoked on the event-loop thread inside _set() exactly when the LATEST slot is overwritten — logic is correct and backward-compatible.
dimos/memory2/module.py Migrates on_msg / _resolve_pose to async, enforces async def on @pose_setter_for, adds dropped-frame counting. Minor docstring discrepancy: _on_frame_dropped describes a power-of-ten schedule but implements a fixed % 1000 threshold.
dimos/hardware/sensors/lidar/fastlio2/recorder.py Both _odom_pose and _lidar_pose converted to async def to satisfy the updated @pose_setter_for contract — straightforward, no logic changes.
dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py Setters converted to async def; _lidar_pose switches to getattr(self, "_last_odom_pose", None) (defensive) and adds a TODO comment acknowledging the world-frame / map.py mismatch.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Pub as Publisher Thread
    participant EventLoop as Event Loop (_set)
    participant Dispatcher as Dispatcher Task
    participant OnMsg as on_msg (async)
    participant ResolvePose as _resolve_pose (async)
    participant Setter as pose_setter method
    participant Store as stream.append

    Pub->>EventLoop: call_soon_threadsafe(_set)
    alt slot already full
        EventLoop->>EventLoop: "on_drop() -> _on_frame_dropped(name)"
    end
    EventLoop->>EventLoop: "slot value=msg, event.set()"
    Dispatcher->>Dispatcher: await event.wait()
    Dispatcher->>OnMsg: await async_handler(msg)
    OnMsg->>ResolvePose: await _resolve_pose(name, msg, ts)
    alt pose setter registered
        ResolvePose->>Setter: await setter(msg)
        Setter-->>ResolvePose: Pose or None
    else tf fallback
        ResolvePose->>ResolvePose: tf.get(root_frame, frame_id, ts)
    end
    ResolvePose-->>OnMsg: Pose or None
    OnMsg->>Store: stream.append(msg, ts, pose)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Pub as Publisher Thread
    participant EventLoop as Event Loop (_set)
    participant Dispatcher as Dispatcher Task
    participant OnMsg as on_msg (async)
    participant ResolvePose as _resolve_pose (async)
    participant Setter as pose_setter method
    participant Store as stream.append

    Pub->>EventLoop: call_soon_threadsafe(_set)
    alt slot already full
        EventLoop->>EventLoop: "on_drop() -> _on_frame_dropped(name)"
    end
    EventLoop->>EventLoop: "slot value=msg, event.set()"
    Dispatcher->>Dispatcher: await event.wait()
    Dispatcher->>OnMsg: await async_handler(msg)
    OnMsg->>ResolvePose: await _resolve_pose(name, msg, ts)
    alt pose setter registered
        ResolvePose->>Setter: await setter(msg)
        Setter-->>ResolvePose: Pose or None
    else tf fallback
        ResolvePose->>ResolvePose: tf.get(root_frame, frame_id, ts)
    end
    ResolvePose-->>OnMsg: Pose or None
    OnMsg->>Store: stream.append(msg, ts, pose)
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into jeff/fix/pose_s..." | Re-trigger Greptile

Comment thread dimos/memory2/module.py Outdated
stream.append(msg, ts=ts, pose=pose)

self.register_disposable(Disposable(input_topic.subscribe(on_msg)))
self.process_observable(input_topic.pure_observable(), on_msg)

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.

P2 Silent frame drops under sustained high-frequency input

process_observable uses a single-slot LATEST mailbox: if on_msg is still awaiting _resolve_pose / stream.append when the next message arrives, the queued-but-unprocessed item is silently overwritten. The old Disposable(input_topic.subscribe(on_msg)) approach would block the publisher instead of dropping.

For typical sensor rates and a fast SQLite write this should be fine in practice, but there's no dropped-frame counter or warning anywhere — if the DB slows down or a pose setter blocks, missing frames would be invisible. Consider logging a warning (or incrementing a counter) inside the LATEST-coalescing slot in _make_async_dispatch, or in on_msg itself if a pre-existing slot is detected, so operators know when data is being lost.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

good idea, I'll add a warning for dropped stuff

process_observable gains an optional on_drop callback fired once per
message dropped by the dispatcher's single-slot LATEST mailbox. The
Recorder uses it to count dropped frames per stream and log a throttled
warning, so a slow sink no longer loses data silently.
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 24, 2026
@jeff-hykin jeff-hykin enabled auto-merge (squash) June 24, 2026 08:24
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Jun 25, 2026
Comment thread dimos/memory2/module.py
"""Mark a method ``(self, msg) -> Pose | None`` as the pose setter for the
given recorded stream(s). Streams without a setter fall back to the tf-based
``world <- frame_id`` lookup."""
"""Mark an ``async def`` method ``(self, msg) -> Pose | None`` as the pose

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.

You've changed pose_setter_for to only work on async functions, but it is still decorating some non-async function, for example PointlioPoseRecorder._odom_pose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants