From 412e75960cb2533a37af13fad73fb64d0b1f73b2 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 15 Mar 2026 11:32:59 -0700 Subject: [PATCH 1/6] fix(cli): speed up `dimos --help` by extracting lightweight type aliases Move NavigationStrategy and VlModelName type aliases into dimos/core/types.py so that global_config.py no longer pulls in matplotlib/scipy (via path_map.py) or torch/langchain (via create.py) at import time. Original modules re-export from the new file so existing imports continue to work. `dimos --help` drops from ~3-4s to ~1.9s. --- dimos/core/global_config.py | 3 +-- dimos/core/types.py | 19 +++++++++++++++++++ dimos/mapping/occupancy/path_map.py | 5 +---- dimos/models/vl/create.py | 5 ++--- 4 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 dimos/core/types.py diff --git a/dimos/core/global_config.py b/dimos/core/global_config.py index 60072ae7fd..00e08dc37f 100644 --- a/dimos/core/global_config.py +++ b/dimos/core/global_config.py @@ -17,8 +17,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict -from dimos.mapping.occupancy.path_map import NavigationStrategy -from dimos.models.vl.create import VlModelName +from dimos.core.types import NavigationStrategy, VlModelName ViewerBackend: TypeAlias = Literal["rerun", "rerun-web", "rerun-connect", "foxglove", "none"] diff --git a/dimos/core/types.py b/dimos/core/types.py new file mode 100644 index 0000000000..a726b3383d --- /dev/null +++ b/dimos/core/types.py @@ -0,0 +1,19 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Literal, TypeAlias + +NavigationStrategy: TypeAlias = Literal["simple", "mixed"] + +VlModelName = Literal["qwen", "moondream"] diff --git a/dimos/mapping/occupancy/path_map.py b/dimos/mapping/occupancy/path_map.py index a99a423de8..7acfb908aa 100644 --- a/dimos/mapping/occupancy/path_map.py +++ b/dimos/mapping/occupancy/path_map.py @@ -12,15 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Literal, TypeAlias - +from dimos.core.types import NavigationStrategy from dimos.mapping.occupancy.gradient import voronoi_gradient from dimos.mapping.occupancy.inflation import simple_inflate from dimos.mapping.occupancy.operations import overlay_occupied, smooth_occupied from dimos.msgs.nav_msgs.OccupancyGrid import OccupancyGrid -NavigationStrategy: TypeAlias = Literal["simple", "mixed"] - def make_navigation_map( occupancy_grid: OccupancyGrid, robot_width: float, strategy: NavigationStrategy diff --git a/dimos/models/vl/create.py b/dimos/models/vl/create.py index 6c778d4104..2303ad3739 100644 --- a/dimos/models/vl/create.py +++ b/dimos/models/vl/create.py @@ -1,9 +1,8 @@ -from typing import Any, Literal +from typing import Any +from dimos.core.types import VlModelName from dimos.models.vl.base import VlModel -VlModelName = Literal["qwen", "moondream"] - def create(name: VlModelName) -> VlModel[Any]: # This uses inline imports to only import what's needed. From afc58caa1fbc171cf15bcbdaa3d83f4a76bc0daa Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 15 Mar 2026 11:41:37 -0700 Subject: [PATCH 2/6] fix: move type aliases to their respective packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NavigationStrategy → dimos/mapping/occupancy/types.py VlModelName → dimos/models/vl/types.py Remove dimos/core/types.py --- dimos/core/global_config.py | 3 ++- dimos/core/types.py | 19 ------------------- dimos/mapping/occupancy/path_map.py | 2 +- dimos/mapping/occupancy/types.py | 3 +++ dimos/models/vl/create.py | 2 +- dimos/models/vl/types.py | 3 +++ 6 files changed, 10 insertions(+), 22 deletions(-) delete mode 100644 dimos/core/types.py create mode 100644 dimos/mapping/occupancy/types.py create mode 100644 dimos/models/vl/types.py diff --git a/dimos/core/global_config.py b/dimos/core/global_config.py index 00e08dc37f..49f4d4f325 100644 --- a/dimos/core/global_config.py +++ b/dimos/core/global_config.py @@ -17,7 +17,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict -from dimos.core.types import NavigationStrategy, VlModelName +from dimos.mapping.occupancy.types import NavigationStrategy +from dimos.models.vl.types import VlModelName ViewerBackend: TypeAlias = Literal["rerun", "rerun-web", "rerun-connect", "foxglove", "none"] diff --git a/dimos/core/types.py b/dimos/core/types.py deleted file mode 100644 index a726b3383d..0000000000 --- a/dimos/core/types.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2025-2026 Dimensional Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Literal, TypeAlias - -NavigationStrategy: TypeAlias = Literal["simple", "mixed"] - -VlModelName = Literal["qwen", "moondream"] diff --git a/dimos/mapping/occupancy/path_map.py b/dimos/mapping/occupancy/path_map.py index 7acfb908aa..397f8616ff 100644 --- a/dimos/mapping/occupancy/path_map.py +++ b/dimos/mapping/occupancy/path_map.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dimos.core.types import NavigationStrategy +from dimos.mapping.occupancy.types import NavigationStrategy from dimos.mapping.occupancy.gradient import voronoi_gradient from dimos.mapping.occupancy.inflation import simple_inflate from dimos.mapping.occupancy.operations import overlay_occupied, smooth_occupied diff --git a/dimos/mapping/occupancy/types.py b/dimos/mapping/occupancy/types.py new file mode 100644 index 0000000000..e6b7d5bd6b --- /dev/null +++ b/dimos/mapping/occupancy/types.py @@ -0,0 +1,3 @@ +from typing import Literal, TypeAlias + +NavigationStrategy: TypeAlias = Literal["simple", "mixed"] diff --git a/dimos/models/vl/create.py b/dimos/models/vl/create.py index 2303ad3739..7fe5a0dcb2 100644 --- a/dimos/models/vl/create.py +++ b/dimos/models/vl/create.py @@ -1,6 +1,6 @@ from typing import Any -from dimos.core.types import VlModelName +from dimos.models.vl.types import VlModelName from dimos.models.vl.base import VlModel diff --git a/dimos/models/vl/types.py b/dimos/models/vl/types.py new file mode 100644 index 0000000000..ac8b0f024d --- /dev/null +++ b/dimos/models/vl/types.py @@ -0,0 +1,3 @@ +from typing import Literal + +VlModelName = Literal["qwen", "moondream"] From 0113d87cb03ebfbcecf74bc20d247ba245bc3c8b Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 15 Mar 2026 12:02:33 -0700 Subject: [PATCH 3/6] test: add CLI startup speed regression test Guards against heavy imports (matplotlib, torch, scipy) leaking into the CLI entrypoint via GlobalConfig. Fails if dimos --help takes >8s. --- dimos/robot/cli/test_cli_startup.py | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 dimos/robot/cli/test_cli_startup.py diff --git a/dimos/robot/cli/test_cli_startup.py b/dimos/robot/cli/test_cli_startup.py new file mode 100644 index 0000000000..aa9886c24e --- /dev/null +++ b/dimos/robot/cli/test_cli_startup.py @@ -0,0 +1,63 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Guard against import-time regressions in the CLI entrypoint. + +`dimos --help` should never pull in heavy ML/viz libraries. If it does, +startup time balloons from <2s to >5s, which is a terrible UX. +""" + +import subprocess +import sys +import time + +# CI runners are slower — give generous headroom but still catch gross regressions. +HELP_TIMEOUT_SECONDS = 8 + + +def test_help_does_not_import_heavy_deps() -> None: + """GlobalConfig import must not drag in matplotlib, torch, or scipy.""" + result = subprocess.run( + [ + sys.executable, + "-c", + ( + "import sys; " + "from dimos.core.global_config import GlobalConfig; " + "bad = [m for m in ('matplotlib', 'torch', 'scipy') if m in sys.modules]; " + "assert not bad, f'Heavy deps imported: {bad}'" + ), + ], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0, f"Heavy deps leaked into GlobalConfig import:\n{result.stderr}" + + +def test_help_startup_time() -> None: + """`dimos --help` must finish in under {HELP_TIMEOUT_SECONDS}s.""" + start = time.monotonic() + result = subprocess.run( + [sys.executable, "-m", "dimos.robot.cli.dimos", "--help"], + capture_output=True, + text=True, + timeout=HELP_TIMEOUT_SECONDS + 5, # hard kill safety margin + ) + elapsed = time.monotonic() - start + assert result.returncode == 0, f"dimos --help failed:\n{result.stderr}" + assert elapsed < HELP_TIMEOUT_SECONDS, ( + f"dimos --help took {elapsed:.1f}s (limit: {HELP_TIMEOUT_SECONDS}s). " + f"Check for heavy imports in the CLI entrypoint or GlobalConfig." + ) From 4deeec6d566e3b0063b5248dcd0d72d1cbc0fb44 Mon Sep 17 00:00:00 2001 From: jeff-hykin <17692058+jeff-hykin@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:12:38 +0000 Subject: [PATCH 4/6] CI code cleanup --- dimos/mapping/occupancy/path_map.py | 2 +- dimos/mapping/occupancy/types.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/dimos/mapping/occupancy/path_map.py b/dimos/mapping/occupancy/path_map.py index 397f8616ff..a1a4640007 100644 --- a/dimos/mapping/occupancy/path_map.py +++ b/dimos/mapping/occupancy/path_map.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dimos.mapping.occupancy.types import NavigationStrategy from dimos.mapping.occupancy.gradient import voronoi_gradient from dimos.mapping.occupancy.inflation import simple_inflate from dimos.mapping.occupancy.operations import overlay_occupied, smooth_occupied +from dimos.mapping.occupancy.types import NavigationStrategy from dimos.msgs.nav_msgs.OccupancyGrid import OccupancyGrid diff --git a/dimos/mapping/occupancy/types.py b/dimos/mapping/occupancy/types.py index e6b7d5bd6b..87f2084698 100644 --- a/dimos/mapping/occupancy/types.py +++ b/dimos/mapping/occupancy/types.py @@ -1,3 +1,17 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from typing import Literal, TypeAlias NavigationStrategy: TypeAlias = Literal["simple", "mixed"] From 61f8588a58f121cbf72ba1ba7d22d32482a1b0de Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Thu, 19 Mar 2026 14:21:09 -0700 Subject: [PATCH 5/6] fix: exclude .venv and other non-source dirs from doclinks file index build_file_index now skips paths rooted in .venv, node_modules, __pycache__, or .git. Fixes test_excludes_venv failure when .venv is a symlink (not matched by gitignore trailing-slash patterns). --- dimos/utils/docs/doclinks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dimos/utils/docs/doclinks.py b/dimos/utils/docs/doclinks.py index 4d2fb6dc1c..541316e2ce 100644 --- a/dimos/utils/docs/doclinks.py +++ b/dimos/utils/docs/doclinks.py @@ -93,9 +93,16 @@ def build_file_index(root: Path, tracked_files: list[Path] | None = None) -> dic if tracked_files is None: tracked_files = get_git_tracked_files(root) + # Directories/symlinks to exclude from the index + _exclude_dirs = {".venv", "node_modules", "__pycache__", ".git"} + for rel_path in tracked_files: parts = rel_path.parts + # Skip paths rooted in excluded directories + if parts and parts[0] in _exclude_dirs: + continue + # Add all suffix combinations for i in range(len(parts)): suffix = "/".join(parts[i:]) From 75c0515f113464ca3aef21eab34d3e800829cfbd Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Thu, 19 Mar 2026 14:25:18 -0700 Subject: [PATCH 6/6] Revert "fix: exclude .venv and other non-source dirs from doclinks file index" This reverts commit 61f8588a58f121cbf72ba1ba7d22d32482a1b0de. --- dimos/utils/docs/doclinks.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dimos/utils/docs/doclinks.py b/dimos/utils/docs/doclinks.py index 541316e2ce..4d2fb6dc1c 100644 --- a/dimos/utils/docs/doclinks.py +++ b/dimos/utils/docs/doclinks.py @@ -93,16 +93,9 @@ def build_file_index(root: Path, tracked_files: list[Path] | None = None) -> dic if tracked_files is None: tracked_files = get_git_tracked_files(root) - # Directories/symlinks to exclude from the index - _exclude_dirs = {".venv", "node_modules", "__pycache__", ".git"} - for rel_path in tracked_files: parts = rel_path.parts - # Skip paths rooted in excluded directories - if parts and parts[0] in _exclude_dirs: - continue - # Add all suffix combinations for i in range(len(parts)): suffix = "/".join(parts[i:])