Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c247bb0
navigation: general packages at the top, the rest filed
leshy Sep 17, 2026
d76f385
rerun bridge: a topics allowlist, and a stack that shares a zenoh bus
leshy Sep 17, 2026
5362055
navigation: local planner and trajectory follower, with go2-zenoh-motion
leshy Sep 17, 2026
11d8038
lidar drivers: plain config defaults, no os.environ
leshy Sep 17, 2026
c131962
mcap: a dimos topic decodes itself
leshy Sep 17, 2026
d159313
io() leaves out the lifecycle RPCs every module has
leshy Sep 17, 2026
7c6e5dc
nix/rust: a tiny toolchain flake for the rust hooks and the raytracer…
leshy Sep 17, 2026
f457b28
Merge origin/main into fix/ivan/motion_stability
leshy Sep 17, 2026
2e5bad1
navigation: drop the planner and follower READMEs and tools.md
leshy Sep 17, 2026
7eaa2a4
dimos-module: one extract_xyz for PointCloud2, and it cannot panic
leshy Sep 17, 2026
054469c
mcap: a stream name never covers two topics
leshy Sep 17, 2026
cf866ca
bake: the discovery test counts IO ports
leshy Sep 17, 2026
a1de060
drop CmdVelMux; MovementManager already muxes cmd_vel
leshy Sep 17, 2026
519b69e
global_config: one line on serve_coordinator_rpc
leshy Sep 17, 2026
514d1f1
planner.rs: cut the comments to what the code does not say
leshy Sep 18, 2026
ffcc41d
comments: no double-dash asides across the branch
leshy Sep 18, 2026
3098c0e
comments: keep what the code does not say
leshy Sep 18, 2026
72914e2
go2 zenoh: drop the nav-baked and htc blueprints
leshy Sep 18, 2026
def5393
mid360 mount setting
leshy Sep 18, 2026
ef23514
Merge remote-tracking branch 'origin/main' into feat/ivan/local_plann…
leshy Sep 18, 2026
66d9f35
natives: no log while idle
leshy Sep 18, 2026
be0b850
go2 zenoh: the pointlio rig pins the robot's router
leshy Sep 18, 2026
b5b7f32
blueprint config: a sparse override on a dataclass field completes fr…
leshy Sep 18, 2026
505192b
Merge remote-tracking branch 'origin/main' into feat/ivan/local_plann…
leshy Sep 18, 2026
5417bfb
Merge remote-tracking branch 'origin/main' into feat/ivan/local_plann…
leshy Sep 19, 2026
9458f9a
blueprint config: an environment override on a dataclass leaf takes JSON
leshy Sep 19, 2026
3962fd2
Merge remote-tracking branch 'origin/main' into feat/ivan/local_plann…
leshy Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ misc/DimSim/public/sims/*.json filter=lfs diff=lfs merge=lfs -text
misc/DimSim/public/embodiment/*.glb filter=lfs diff=lfs merge=lfs -text
misc/DimSim/scenes/**/*.glb filter=lfs diff=lfs merge=lfs -text
misc/DimSim/scenes/**/*.gltf filter=lfs diff=lfs merge=lfs -text
# Machine-generated, and reviewed as artifacts rather than as source: a lock file
# is cargo's output, and motion's planner crate is what an autoresearch lab
# emitted. Marking them keeps a diff about the code that was actually written.
Cargo.lock linguist-generated=true
dimos/navigation/local_planner/rust/src/planner.rs linguist-generated=true
4 changes: 3 additions & 1 deletion .github/scripts/wheel_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
from pathlib import Path
import urllib.request

from dimos.navigation.replanning_a_star.min_cost_astar_ext import min_cost_astar_cpp # noqa: F401
from dimos.navigation.go2.replanning_a_star.min_cost_astar_ext import (
min_cost_astar_cpp, # noqa: F401
)
from dimos.web.relay_bridge import locate
from dimos.web.relay_bridge.relay_process import RelayProcess

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,13 @@ jobs:
uv sync --group tests --frozen
source .venv/bin/activate
maturin develop -m dimos/mapping/ray_tracing/rust/py/Cargo.toml
maturin develop -m dimos/navigation/nav_3d/mls_planner/rust/py/Cargo.toml
maturin develop -m dimos/navigation/global_planner/mls_planner/rust/py/Cargo.toml
python -c "import dimos_voxel_ray_tracing, dimos_mls_planner"
python -m pytest -c /dev/null --rootdir . --noconftest --import-mode=importlib -p no:cacheprovider -v \
dimos/mapping/ray_tracing/test_voxel_map.py \
dimos/mapping/ray_tracing/test_transformer.py \
dimos/navigation/nav_3d/mls_planner/test_mls_planner.py \
dimos/navigation/nav_3d/mls_planner/test_transformer.py
dimos/navigation/global_planner/mls_planner/test_mls_planner.py \
dimos/navigation/global_planner/mls_planner/test_transformer.py
- name: Bake e2e tests
run: uv run pytest -m bake_e2e dimos/cli/bake/test_bake_e2e.py --no-cov
- name: Native module e2e tests
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ members = [
"native/rust/dimos-module-macros",
"dimos/mapping/ray_tracing/rust",
"dimos/mapping/ray_tracing/rust/py",
"dimos/navigation/nav_3d/mls_planner/rust",
"dimos/navigation/nav_3d/mls_planner/rust/py",
"dimos/navigation/global_planner/mls_planner/rust",
"dimos/navigation/global_planner/mls_planner/rust/py",
"dimos/hardware/sensors/lidar/livox/rust",
"dimos/hardware/sensors/lidar/pointlio/rust",
"dimos/hardware/sensors/lidar/virtual_mid360",
Expand Down
2 changes: 1 addition & 1 deletion bin/build-test-natives
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ python setup.py build_ext --inplace
# and the rust native-module binaries the livox e2e tests spawn from target/release.
if command -v cargo &>/dev/null; then
maturin develop --uv -m dimos/mapping/ray_tracing/rust/py/Cargo.toml
maturin develop --uv -m dimos/navigation/nav_3d/mls_planner/rust/py/Cargo.toml
maturin develop --uv -m dimos/navigation/global_planner/mls_planner/rust/py/Cargo.toml
cargo build --release --locked -p dimos-livox -p dimos-virtual-mid360
else
echo "warning: cargo not found; skipping Rust builds, their tests will skip or fail." >&2
Expand Down
8 changes: 4 additions & 4 deletions dimos/agents/skills/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from dimos.agents.annotation import skill
from dimos.agents.capabilities import CAP_MOVEMENT
from dimos.agents.skills.visual_servoing.query import get_object_bbox_from_image
from dimos.core.core import rpc
from dimos.core.module import Module
from dimos.core.stream import In
Expand All @@ -28,8 +29,7 @@
from dimos.msgs.geometry_msgs.Vector3 import Vector3, make_vector3
from dimos.msgs.sensor_msgs.Image import Image
from dimos.navigation.base import NavigationState
from dimos.navigation.navigation_spec import NavigationInterfaceSpec
from dimos.navigation.visual.query import get_object_bbox_from_image
from dimos.navigation.go2.replanning_a_star.spec import NavigationInterfaceSpec
from dimos.perception.experimental.object_tracking_spec import ObjectTrackingSpec
from dimos.perception.experimental.spatial_memory_spec import SpatialMemorySpec
from dimos.types.robot_location import RobotLocation
Expand Down Expand Up @@ -114,8 +114,8 @@ def tag_location(self, location_name: str) -> str:
# TODO(capabilities): this skill is `instant`, so the `movement` hold is
# released the moment the call returns even though the tagged-location and
# semantic-map paths only fire set_goal() and keep navigating. Make it
# `background` and close the hold when the robot actually stops -- the
# planner already emits a goal-reached signal (see PatrollingModule) -- so
# `background` and close the hold when the robot actually stops (the
# planner already emits a goal-reached signal, see PatrollingModule) so
# patrol/follow/explore can't start over an active navigation goal.
@skill(uses=[CAP_MOVEMENT])
def navigate_with_text(self, query: str) -> str:
Expand Down
6 changes: 3 additions & 3 deletions dimos/agents/skills/person_follow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

from dimos.agents.annotation import skill
from dimos.agents.capabilities import CAP_MOVEMENT
from dimos.agents.skills.visual_servoing.detection_navigation import DetectionNavigation
from dimos.agents.skills.visual_servoing.query import get_object_bbox_from_image
from dimos.agents.skills.visual_servoing.visual_servoing_2d import VisualServoing2D
from dimos.constants import DEFAULT_THREAD_JOIN_TIMEOUT
from dimos.core.core import rpc
from dimos.core.module import Module, ModuleConfig
Expand All @@ -35,9 +38,6 @@
from dimos.msgs.sensor_msgs.Image import Image, ImageFormat
from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2
from dimos.msgs.tf2_msgs.TFMessage import TFMessage
from dimos.navigation.visual.query import get_object_bbox_from_image
from dimos.navigation.visual_servoing.detection_navigation import DetectionNavigation
from dimos.navigation.visual_servoing.visual_servoing_2d import VisualServoing2D
from dimos.utils.logging_config import setup_logger
from dimos.utils.turbojpeg import get_turbojpeg

Expand Down
10 changes: 7 additions & 3 deletions dimos/cli/bake/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
select_modules,
)
from dimos.cli.bake.errors import BakeError
from dimos.core.stream import In, Out
from dimos.core.stream import IO, In, Out
from dimos.core.transport_factory import zenoh_key_expr
from dimos.protocol.pubsub.impl.zenohpubsub import Topic as ZenohTopic

Expand Down Expand Up @@ -141,13 +141,17 @@ def test_the_real_repo_registers_the_two_shipped_modules() -> None:


def wrapper_ports(python_ref: str) -> dict[str, type]:
"""The python wrapper's ports, keyed by name, as message classes."""
"""The python wrapper's ports, keyed by name, as message classes.

`IO` counts: a port that both subscribes and publishes (tf) rides in the
manifest's `inputs` table, because the bake graph has no io kind.
"""
module_name, _, class_name = python_ref.partition(":")
wrapper = getattr(importlib.import_module(module_name), class_name)
return {
name: get_args(hint)[0]
for name, hint in get_type_hints(wrapper).items()
if get_origin(hint) in (In, Out)
if get_origin(hint) in (In, Out, IO)
}


Expand Down
2 changes: 1 addition & 1 deletion dimos/cli/dimos.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def cli_main() -> None:
main.add_typer(topic_app, name="topic")
main.add_typer(map_app, name="map")

from dimos.navigation.nav_3d.evaluator.cli import app as nav_eval_app
from dimos.navigation.global_planner.evaluator.cli import app as nav_eval_app

main.add_typer(nav_eval_app, name="nav-eval")
main.add_typer(dataprep_app, name="dataprep")
Expand Down
3 changes: 3 additions & 0 deletions dimos/codebase_checks/test_no_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
".tox",
# third-party vendored code
"gtsam",
# cargo's build dir: gitignored, so CI never sees it, but locally it holds
# whole vendored python envs whose stdlib is full of section banners.
"target",
}

# Lines that match section patterns but are actually programmatic / intentional.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
unwrapped from its daemon thread and rebuilt as a passive ControlTask.

Algorithm is a faithful port of
:class:`dimos.navigation.replanning_a_star.local_planner.LocalPlanner`:
:class:`dimos.navigation.go2.replanning_a_star.local_planner.LocalPlanner`:
PController + 0.5 m fixed lookahead + rotate-then-drive heuristic +
state machine (initial_rotation → path_following → final_rotation → arrived).

Expand Down Expand Up @@ -57,8 +57,8 @@
from dimos.msgs.geometry_msgs.Vector3 import Vector3
from dimos.msgs.nav_msgs.Path import Path
from dimos.msgs.std_msgs.Float32 import Float32
from dimos.navigation.replanning_a_star.controllers import PController
from dimos.navigation.replanning_a_star.path_distancer import PathDistancer
from dimos.navigation.go2.replanning_a_star.controllers import PController
from dimos.navigation.go2.replanning_a_star.path_distancer import PathDistancer
from dimos.protocol.service.spec import BaseConfig
from dimos.utils.logging_config import setup_logger
from dimos.utils.trigonometry import angle_diff
Expand Down
6 changes: 6 additions & 0 deletions dimos/core/coordination/blueprint_config/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from __future__ import annotations

from collections.abc import Mapping
from dataclasses import is_dataclass
import inspect
from types import UnionType
from typing import Annotated, Any, Union, get_args, get_origin, get_type_hints
Expand Down Expand Up @@ -165,6 +166,11 @@ def prepare_model_input(
continue
nested_models = _base_model_types(info.annotation)
if not nested_models:
# A dataclass field has no per-field defaults to fall back on, so a
# sparse override is completed from the field's default instance.
default = _parse_field_default(info)
if is_dataclass(default) and not isinstance(default, type):
prepared[name] = {**plain(default), **plain_mapping(raw_value)}
continue

selected: type[BaseModel] | None = None
Expand Down
15 changes: 14 additions & 1 deletion dimos/core/coordination/blueprint_config/merging.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from collections.abc import Mapping
import difflib
import json
from typing import Any

from dimos.core.coordination.blueprint_config.errors import BlueprintConfigError
Expand Down Expand Up @@ -98,7 +99,19 @@ def merge_environment(
def set_coerced(path: tuple[str, ...], raw_name: str, value: str) -> None:
identity = _environment_identity(path)
target = targets.get(identity) if identity is not None else None
coerced = value if target is None else coerce_environment_value(value, target, raw_name)
if target is not None:
coerced = coerce_environment_value(value, target, raw_name)
elif value.lstrip().startswith(("[", "{")):
# A leaf the schema does not enumerate (inside a dataclass field) still
# takes a sequence or object as JSON.
try:
coerced = json.loads(value)
except json.JSONDecodeError as error:
raise BlueprintConfigError(
f"Environment variable {raw_name} contains invalid JSON: {error.msg}."
) from error
else:
coerced = value
deep_set(env_source, path, coerced)

for raw_name, value in environ.items():
Expand Down
38 changes: 38 additions & 0 deletions dimos/core/coordination/blueprint_config/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Annotated, Any, Literal

Expand Down Expand Up @@ -53,6 +54,21 @@ class SecondaryModule(Module):
config: SecondaryConfig


@dataclass(frozen=True)
class Body:
length: float
max_speed: float
rows: tuple[tuple[float, float], ...]


class BodyConfig(ModuleConfig):
body: Body = Body(length=1.0, max_speed=0.5, rows=((1.0, 2.0),))


class BodyModule(Module):
config: BodyConfig


class ProviderConfig(BaseModel):
api_key: str
retries: int = 1
Expand All @@ -68,6 +84,28 @@ def subscribe(self, callback: Any, selfstream: Stream[bytes] | None = None) -> A
return lambda: None


def test_sparse_dataclass_override_completes_from_the_default() -> None:
parsed = BlueprintConfigParser(BodyModule.blueprint()).parse(
[], environ={"BODYMODULE__BODY__MAX_SPEED": "0.6"}
)

body = parsed.module_kwargs("bodymodule")["body"]
assert body == {"length": 1.0, "max_speed": 0.6, "rows": ((1.0, 2.0),)}

# A blueprint-pinned body is what gets overlaid, not the class default, and a
# sequence leaf takes JSON.
pinned = BodyModule.blueprint(body=Body(length=2.0, max_speed=0.4, rows=()))
parsed = BlueprintConfigParser(pinned).parse(
[], environ={"BODYMODULE__BODY__MAX_SPEED": "0.6", "BODYMODULE__BODY__ROWS": "[[3, 4]]"}
)

assert parsed.module_kwargs("bodymodule")["body"] == {
"length": 2.0,
"max_speed": 0.6,
"rows": ((3.0, 4.0),),
}


def test_parse_supports_value_forms_types_nested_defaults_and_last_wins() -> None:
blueprint = PrimaryModule.blueprint(nested={"mode": "manual"})

Expand Down
5 changes: 5 additions & 0 deletions dimos/core/coordination/module_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ def _stop_manager(m: WorkerManager) -> None:

def start_rpc_service(self) -> None:
"""Expose the coordinator's API as @rpc methods over LCM."""
if not self._global_config.serve_coordinator_rpc:
# Deliberate: the name is bus-wide, and this stack shares the bus with
# one that owns it. Costs remote introspection of THIS stack, nothing else.
logger.info("serve_coordinator_rpc is off; not claiming the Coordinator name")
return
with self._rpc_lock:
if self._coordinator_rpc is not None:
return
Expand Down
4 changes: 3 additions & 1 deletion dimos/core/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class GlobalConfig(BaseSettings):
zenoh_multicast: bool = True
# Multicast group scouting joins, e.g. 224.0.0.224:7446. Empty takes zenoh's
# own. Moving it walks a session onto a private discovery bus, which is how
# parallel sessions on one machine stay apart -- LCM_DEFAULT_URL's analog.
# parallel sessions on one machine stay apart (LCM_DEFAULT_URL's analog).
zenoh_scout_addr: str = ""
# Whether peers propagate the peers they already know over established links.
# Unlike multicast scouting this reaches nothing new on the LAN, and zenoh
Expand All @@ -94,6 +94,8 @@ class GlobalConfig(BaseSettings):
# Seconds ZenohService.start() blocks for the configured connect endpoints to
# link before giving up and continuing. 0 disables the wait.
zenoh_connect_timeout: float = Field(default=1.0, ge=0, le=86400)
# Off: share a zenoh bus with a stack that already owns the Coordinator name.
serve_coordinator_rpc: bool = True
viewer: ViewerBackend = "rerun"
rerun_open: RerunOpenOption = RERUN_OPEN_DEFAULT
rerun_web: bool = RERUN_ENABLE_WEB
Expand Down
8 changes: 8 additions & 0 deletions dimos/core/introspection/module/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,17 @@
from typing import Any

# Internal RPCs to hide from io() output
# Every module has these; io() shows what a module adds.
INTERNAL_RPCS = {
"build",
"dynamic_skills",
"get_skills",
"peek_stream",
"set_module_ref",
"set_transport",
"skills",
"start",
"stop",
"_io_instance",
}

Expand Down
4 changes: 2 additions & 2 deletions dimos/experimental/memory/rust/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
../../../../native/rust/dimos-module-macros
../../../../dimos/mapping/ray_tracing/rust
../../../../dimos/mapping/ray_tracing/rust/py
../../../../dimos/navigation/nav_3d/mls_planner/rust
../../../../dimos/navigation/nav_3d/mls_planner/rust/py
../../../../dimos/navigation/global_planner/mls_planner/rust
../../../../dimos/navigation/global_planner/mls_planner/rust/py
../../../../dimos/hardware/sensors/lidar/livox/rust
../../../../dimos/hardware/sensors/lidar/pointlio/rust
../../../../dimos/hardware/sensors/lidar/virtual_mid360
Expand Down
10 changes: 5 additions & 5 deletions dimos/experimental/security_demo/security_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@
from dimos.msgs.nav_msgs.OccupancyGrid import OccupancyGrid
from dimos.msgs.sensor_msgs.CameraInfo import CameraInfo
from dimos.msgs.sensor_msgs.Image import Image
from dimos.navigation.patrolling.create_patrol_router import create_patrol_router
from dimos.navigation.patrolling.routers.patrol_router import PatrolRouter
from dimos.navigation.experimental.patrolling.create_patrol_router import create_patrol_router
from dimos.navigation.experimental.patrolling.routers.patrol_router import PatrolRouter
from dimos.agents.skills.speak_skill_spec import SpeakSkillSpec
from dimos.navigation.replanning_a_star.module_spec import ReplanningAStarPlannerSpec
from dimos.navigation.visual_servoing.visual_servoing_2d import VisualServoing2D
from dimos.navigation.go2.replanning_a_star.module_spec import ReplanningAStarPlannerSpec
from dimos.agents.skills.visual_servoing.visual_servoing_2d import VisualServoing2D
from dimos.utils.logging_config import setup_logger
from dimos.navigation.patrolling.constants import EXTRA_CLEARANCE
from dimos.navigation.experimental.patrolling.constants import EXTRA_CLEARANCE

if TYPE_CHECKING:
from dimos.perception.detection.type.detection2d.bbox import Detection2DBBox
Expand Down
10 changes: 4 additions & 6 deletions dimos/hardware/sensors/lidar/fastlio2/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

from __future__ import annotations

import os
import time
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -63,10 +62,9 @@ class FastLio2Config(NativeModuleConfig):
stdin_config: bool = True
base_fields: frozenset[str] = frozenset({"frame_id"})
# Livox SDK hardware config. lidar_ip required; host_ip optional (auto-derived
# from lidar_ip's subnet). Both fall back to DIMOS_FASTLIO_LIDAR_IP /
# DIMOS_FASTLIO_HOST_IP.
host_ip: str | None = Field(default_factory=lambda: os.environ.get("DIMOS_FASTLIO_HOST_IP"))
lidar_ip: str | None = Field(default_factory=lambda: os.environ.get("DIMOS_FASTLIO_LIDAR_IP"))
# from lidar_ip's subnet).
host_ip: str | None = None
lidar_ip: str | None = None
frequency: float = 10.0

# Odometry is published as frame_id (fixed) -> sensor_frame_id (moving sensor),
Expand Down Expand Up @@ -162,7 +160,7 @@ def _validate_network(self) -> None:
if not lidar_ip:
raise RuntimeError(
"FastLio2: lidar_ip not set — it's network-specific. Set it in the config "
"or via the DIMOS_FASTLIO_LIDAR_IP env var."
"or FASTLIO2__LIDAR_IP in the environment."
)
# host_ip optional: derive the local NIC on lidar_ip's /24 when unset or
# not one of our IPs (shared with the Mid360 driver).
Expand Down
Loading
Loading