Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
56480e6
modules docs sketch
leshy Dec 11, 2025
571b9a2
extracted introspection tooling
leshy Dec 12, 2025
3d0f80d
first sketch of module docs
leshy Dec 12, 2025
ff13582
including go2 basic blueprint svg
leshy Dec 12, 2025
47ad8b4
new dot2 generator
leshy Dec 12, 2025
dc790a9
adding different layouts
leshy Dec 12, 2025
5642806
removing other blueprint rendering algos
leshy Dec 12, 2025
79285fe
better dot2 algo
leshy Dec 12, 2025
8a2a62d
modules tutorial work
leshy Dec 12, 2025
84437fc
more docs work
leshy Dec 12, 2025
6a1cf6e
small modules docs changes
leshy Dec 12, 2025
08db954
go2 agentic svg
leshy Dec 12, 2025
bb138c8
mutliprocessing sketch
leshy Dec 12, 2025
ae9cc4f
small wording changes
leshy Dec 16, 2025
005fcb3
Merge branch 'dev' into ivan-docs
leshy Dec 25, 2025
b1dd785
Merge branch 'dev' into ivan-docs
leshy Dec 28, 2025
29209f9
Merge branch 'dev' into ivan-docs
leshy Dec 28, 2025
bd313b3
config docs, moved old docs, dot file included
leshy Dec 29, 2025
6797b8a
doclinks tool
leshy Dec 29, 2025
7b5a105
config docs moved to generic place
leshy Dec 29, 2025
e5ea40a
doclinks updates, transform docs sketch
leshy Dec 29, 2025
c5f003e
transforms docs
leshy Dec 29, 2025
ec9cf99
docs agent docs, reactivex docs
leshy Dec 29, 2025
1578e31
reactivex docs
leshy Dec 29, 2025
c22022a
small changes on transform images
leshy Dec 29, 2025
ef03935
folded code blocks
leshy Dec 29, 2025
f892b39
moved modules doc
leshy Dec 29, 2025
83a0dd9
transforms work
leshy Dec 29, 2025
df22b82
transform docs
leshy Dec 29, 2025
8a7f1ba
transforms modules image
leshy Dec 29, 2025
da513d6
Merge branch 'dev' into ivan-docs
leshy Dec 31, 2025
21b3d6d
Update docs/concepts/modules.md
leshy Dec 31, 2025
2deb827
Update docs/concepts/modules.md
leshy Dec 31, 2025
40a606a
Update docs/concepts/modules.md
leshy Dec 31, 2025
e435c62
Update docs/concepts/modules.md
leshy Dec 31, 2025
52d3f4b
Update docs/concepts/modules.md
leshy Dec 31, 2025
0e947f0
Merge branch 'dev' into ivan-docs
leshy Dec 31, 2025
0e91089
Merge branch 'ivan-docs' of github.com:dimensionalOS/dimos into ivan-…
leshy Dec 31, 2025
f12b007
doclinks ignore
leshy Dec 31, 2025
f0e356f
doclinks in pre-commit hook
leshy Dec 31, 2025
d39e5f1
camera module fixes
leshy Dec 31, 2025
fb6044a
vis cleanup
leshy Dec 31, 2025
909903b
doclinks always runs
leshy Dec 31, 2025
7521cda
introspection tooling cleanup
leshy Dec 31, 2025
175d91a
kwargs typing
leshy Dec 31, 2025
4f1eed2
paul comment
leshy Dec 31, 2025
8999464
lcm and transports docs
leshy Jan 1, 2026
84d99de
camera module fixes
leshy Jan 3, 2026
dcd5d53
small cleanup, vibed sensor.py deleted
leshy Jan 3, 2026
239c33d
sharpness barrier fix
leshy Jan 3, 2026
73d3e32
moved gstreamer into separate dir, removed fake zed
leshy Jan 3, 2026
6d6a6a1
removed all typing ignores
leshy Jan 3, 2026
e4c658d
mypy fix
leshy Jan 3, 2026
2328cdd
Out stream is observable
leshy Jan 3, 2026
bdd5a8e
Merge branch 'dev' into ivan-docs-clean
leshy Jan 3, 2026
adadaf6
better video stream skill
leshy Jan 3, 2026
951cd64
Merge branch 'cameramodule_repairs' into ivan-docs-clean
leshy Jan 3, 2026
198ad29
stream changes undo, typing fixes
leshy Jan 3, 2026
6ca7d8e
Merge branch 'stream_repairs' into ivan-docs-clean
leshy Jan 3, 2026
75cf778
reverted pyproject for fast tests in CI for now
leshy Jan 3, 2026
6f1cfd8
CI code cleanup
leshy Jan 3, 2026
23a1c2e
type fixes
leshy Jan 3, 2026
b5a4577
ignore gps skill publish
leshy Jan 3, 2026
ebb4aec
Merge branch 'ivan-docs-clean' of github.com:dimensionalOS/dimos into…
leshy Jan 3, 2026
e1ed2a0
transform test fix
leshy Jan 3, 2026
9b92d1b
core test fix
leshy Jan 3, 2026
6c2ff95
watchdog was missing
leshy Jan 3, 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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
*.mov filter=lfs diff=lfs merge=lfs -text binary
*.gif filter=lfs diff=lfs merge=lfs -text binary
*.foxe filter=lfs diff=lfs merge=lfs -text binary
docs/**/*.png filter=lfs diff=lfs merge=lfs -text
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,11 @@ repos:
pass_filenames: false
entry: bin/lfs_check
language: script

- id: doclinks
name: Doclinks
always_run: true
pass_filenames: false
entry: python -m dimos.utils.docs.doclinks docs/
language: system
files: ^docs/.*\.md$
2 changes: 2 additions & 0 deletions dimos/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
from dimos.agents.spec import AgentSpec
from dimos.protocol.skill.skill import skill
from dimos.protocol.skill.type import Output, Reducer, Stream

__all__ = ["Agent", "AgentSpec", "Output", "Reducer", "Stream", "deploy", "skill"]
2 changes: 1 addition & 1 deletion dimos/agents/skills/gps_nav_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def set_gps_travel_points(self, *points: dict[str, float]) -> str:
logger.info(f"Set travel points: {new_points}")

if self.gps_goal._transport is not None:
self.gps_goal.publish(new_points)
self.gps_goal.publish(new_points) # type: ignore[arg-type]

if self._set_gps_travel_goal_points:
self._set_gps_travel_goal_points(new_points)
Expand Down
20 changes: 20 additions & 0 deletions dimos/core/introspection/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2025 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.

"""Module and blueprint introspection utilities."""

from dimos.core.introspection.module import INTERNAL_RPCS, render_module_io
from dimos.core.introspection.svg import to_svg

__all__ = ["INTERNAL_RPCS", "render_module_io", "to_svg"]
24 changes: 24 additions & 0 deletions dimos/core/introspection/blueprint/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2025 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.

"""Blueprint introspection and rendering.

Renderers:
- dot: Graphviz DOT format (hub-style with type nodes as intermediate hubs)
"""

from dimos.core.introspection.blueprint import dot
from dimos.core.introspection.blueprint.dot import LayoutAlgo, render_svg

__all__ = ["LayoutAlgo", "dot", "render_svg"]
253 changes: 253 additions & 0 deletions dimos/core/introspection/blueprint/dot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# 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.

"""Hub-style Graphviz DOT renderer for blueprint visualization.

This renderer creates intermediate "type nodes" for data flow, making it clearer
when one output fans out to multiple consumers:

ModuleA --> [name:Type] --> ModuleB
--> ModuleC
"""

from collections import defaultdict
from enum import Enum, auto

from dimos.core.blueprints import ModuleBlueprintSet
from dimos.core.introspection.utils import (
GROUP_COLORS,
TYPE_COLORS,
color_for_string,
sanitize_id,
)
from dimos.core.module import Module
from dimos.utils.cli import theme


class LayoutAlgo(Enum):
"""Layout algorithms for controlling graph structure."""

STACK_CLUSTERS = auto() # Stack clusters vertically (invisible edges between clusters)
STACK_NODES = auto() # Stack nodes within clusters vertically
FDP = auto() # Use fdp (force-directed) layout engine instead of dot


# Connections to ignore (too noisy/common)
DEFAULT_IGNORED_CONNECTIONS = {("odom", "PoseStamped")}

DEFAULT_IGNORED_MODULES = {
"WebsocketVisModule",
"UtilizationModule",
# "FoxgloveBridge",
}


def render(
blueprint_set: ModuleBlueprintSet,
*,
layout: set[LayoutAlgo] | None = None,
ignored_connections: set[tuple[str, str]] | None = None,
ignored_modules: set[str] | None = None,
) -> str:
"""Generate a hub-style DOT graph from a ModuleBlueprintSet.

This creates intermediate "type nodes" that represent data channels,
connecting producers to consumers through a central hub node.

Args:
blueprint_set: The blueprint set to visualize.
layout: Set of layout algorithms to apply. Default is none (let graphviz decide).
ignored_connections: Set of (name, type_name) tuples to ignore.
ignored_modules: Set of module names to ignore.

Returns:
A string in DOT format showing modules as nodes, type nodes as
small colored hubs, and edges connecting them.
"""
if layout is None:
layout = set()
if ignored_connections is None:
ignored_connections = DEFAULT_IGNORED_CONNECTIONS
if ignored_modules is None:
ignored_modules = DEFAULT_IGNORED_MODULES

# Collect all outputs: (name, type) -> list of producer modules
producers: dict[tuple[str, type], list[type[Module]]] = defaultdict(list)
# Collect all inputs: (name, type) -> list of consumer modules
consumers: dict[tuple[str, type], list[type[Module]]] = defaultdict(list)
# Module name -> module class (for getting package info)
module_classes: dict[str, type[Module]] = {}

for bp in blueprint_set.blueprints:
module_classes[bp.module.__name__] = bp.module
for conn in bp.connections:
# Apply remapping
remapped_name = blueprint_set.remapping_map.get((bp.module, conn.name), conn.name)
key = (remapped_name, conn.type)
if conn.direction == "out":
producers[key].append(bp.module)
else:
consumers[key].append(bp.module)

# Find all active channels (have both producers AND consumers)
active_channels: dict[tuple[str, type], str] = {} # key -> color
for key in producers:
name, type_ = key
type_name = type_.__name__
if key not in consumers:
continue
if (name, type_name) in ignored_connections:
continue
# Check if all modules are ignored
valid_producers = [m for m in producers[key] if m.__name__ not in ignored_modules]
valid_consumers = [m for m in consumers[key] if m.__name__ not in ignored_modules]
if not valid_producers or not valid_consumers:
continue
label = f"{name}:{type_name}"
active_channels[key] = color_for_string(TYPE_COLORS, label)

# Group modules by package
def get_group(mod_class: type[Module]) -> str:
module_path = mod_class.__module__
parts = module_path.split(".")
if len(parts) >= 2 and parts[0] == "dimos":
return parts[1]
return "other"

by_group: dict[str, list[str]] = defaultdict(list)
for mod_name, mod_class in module_classes.items():
if mod_name in ignored_modules:
continue
group = get_group(mod_class)
by_group[group].append(mod_name)

# Build DOT output
lines = [
"digraph modules {",
" bgcolor=transparent;",
" rankdir=LR;",
# " nodesep=1;", # horizontal spacing between nodes
# " ranksep=1.5;", # vertical spacing between ranks
" splines=true;",
f' node [shape=box, style=filled, fillcolor="{theme.BACKGROUND}", fontcolor="{theme.FOREGROUND}", color="{theme.BLUE}", fontname=fixed, fontsize=12, margin="0.1,0.1"];',
" edge [fontname=fixed, fontsize=10];",
"",
]

# Add subgraphs for each module group
sorted_groups = sorted(by_group.keys())
for group in sorted_groups:
mods = sorted(by_group[group])
color = color_for_string(GROUP_COLORS, group)
lines.append(f" subgraph cluster_{group} {{")
lines.append(f' label="{group}";')
lines.append(" labeljust=r;")
lines.append(" fontname=fixed;")
lines.append(" fontsize=14;")
lines.append(f' fontcolor="{theme.FOREGROUND}";')
lines.append(' style="filled,dashed";')
lines.append(f' color="{color}";')
lines.append(" penwidth=1;")
lines.append(f' fillcolor="{color}10";')
for mod in mods:
lines.append(f" {mod};")
# Stack nodes vertically within cluster
if LayoutAlgo.STACK_NODES in layout and len(mods) > 1:
for i in range(len(mods) - 1):
lines.append(f" {mods[i]} -> {mods[i + 1]} [style=invis];")
lines.append(" }")
lines.append("")

# Add invisible edges between clusters to force vertical stacking
if LayoutAlgo.STACK_CLUSTERS in layout and len(sorted_groups) > 1:
lines.append(" // Force vertical cluster layout")
for i in range(len(sorted_groups) - 1):
group_a = sorted_groups[i]
group_b = sorted_groups[i + 1]
# Pick first node from each cluster
node_a = sorted(by_group[group_a])[0]
node_b = sorted(by_group[group_b])[0]
lines.append(f" {node_a} -> {node_b} [style=invis, weight=10];")
lines.append("")

# Add type nodes (outside all clusters)
lines.append(" // Type nodes (data channels)")
for key, color in sorted(
active_channels.items(), key=lambda x: f"{x[0][0]}:{x[0][1].__name__}"
):
name, type_ = key
type_name = type_.__name__
node_id = sanitize_id(f"chan_{name}_{type_name}")
label = f"{name}:{type_name}"
lines.append(
f' {node_id} [label="{label}", shape=note, style=filled, '
f'fillcolor="{color}35", color="{color}", fontcolor="{theme.FOREGROUND}", '
f'width=0, height=0, margin="0.1,0.05", fontsize=10];'
)

lines.append("")

# Add edges: producer -> type_node -> consumer
lines.append(" // Edges")
for key, color in sorted(
active_channels.items(), key=lambda x: f"{x[0][0]}:{x[0][1].__name__}"
):
name, type_ = key
type_name = type_.__name__
node_id = sanitize_id(f"chan_{name}_{type_name}")

# Edges from producers to type node (no arrow, kept close)
for producer in producers[key]:
if producer.__name__ in ignored_modules:
continue
lines.append(f' {producer.__name__} -> {node_id} [color="{color}", arrowhead=none];')

# Edges from type node to consumers (with arrow)
for consumer in consumers[key]:
if consumer.__name__ in ignored_modules:
continue
lines.append(f' {node_id} -> {consumer.__name__} [color="{color}"];')

lines.append("}")
return "\n".join(lines)


def render_svg(
blueprint_set: ModuleBlueprintSet,
output_path: str,
*,
layout: set[LayoutAlgo] | None = None,
) -> None:
"""Generate an SVG file from a ModuleBlueprintSet using graphviz.

Args:
blueprint_set: The blueprint set to visualize.
output_path: Path to write the SVG file.
layout: Set of layout algorithms to apply.
"""
import subprocess

if layout is None:
layout = set()

dot_code = render(blueprint_set, layout=layout)
engine = "fdp" if LayoutAlgo.FDP in layout else "dot"
result = subprocess.run(
[engine, "-Tsvg", "-o", output_path],
input=dot_code,
text=True,
capture_output=True,
)
if result.returncode != 0:
raise RuntimeError(f"graphviz failed: {result.stderr}")
45 changes: 45 additions & 0 deletions dimos/core/introspection/module/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2025 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.

"""Module introspection and rendering.

Renderers:
- ansi: ANSI terminal output (default)
- dot: Graphviz DOT format
"""

from dimos.core.introspection.module import ansi, dot
from dimos.core.introspection.module.info import (
INTERNAL_RPCS,
ModuleInfo,
ParamInfo,
RpcInfo,
SkillInfo,
StreamInfo,
extract_module_info,
)
from dimos.core.introspection.module.render import render_module_io

__all__ = [
"INTERNAL_RPCS",
"ModuleInfo",
"ParamInfo",
"RpcInfo",
"SkillInfo",
"StreamInfo",
"ansi",
"dot",
"extract_module_info",
"render_module_io",
]
Loading
Loading