Skip to content

Jeff/feat/graph#1485

Draft
jeff-hykin wants to merge 9 commits intodevfrom
jeff/feat/graph
Draft

Jeff/feat/graph#1485
jeff-hykin wants to merge 9 commits intodevfrom
jeff/feat/graph

Conversation

@jeff-hykin
Copy link
Member

DRAFT: just making this so I don't forget about it later

Problem

Closes DIM-XXX

Solution

Breaking Changes

How to Test

Contributor License Agreement

  • I have read and approved the CLA.

@jeff-hykin jeff-hykin marked this pull request as draft March 7, 2026 21:30
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR adds a dimos graph CLI command that discovers Blueprint globals in a user-supplied Python file, renders them as SVG diagrams via graphviz, and serves the result in a one-shot browser-based HTTP viewer. It also extends the existing DOT renderer with a show_disconnected flag that visualises producer-only or consumer-only streams as dashed stub nodes.

Key changes:

  • dimos/core/introspection/blueprint/dot.py — adds show_disconnected parameter to render() and render_svg(); introduces _COMPACT_ONLY_IGNORED_MODULES to control which modules appear only in full (disconnected) view.
  • dimos/core/introspection/svg.py — passes show_disconnected through to the blueprint DOT renderer.
  • dimos/utils/cli/graph.py — new module: imports a Python file dynamically, finds all Blueprint globals, writes SVGs to temp files, builds an HTML page, and serves it from a one-shot HTTPServer.
  • dimos/robot/cli/dimos.py — registers the new graph sub-command, defaulting to show_disconnected=True (opt-out via --no-disconnected).
  • Tests added for both the DOT renderer and the CLI helper.

Issues found:

  • The HTTP server binds to "0.0.0.0" instead of "127.0.0.1", exposing rendered blueprint diagrams to all hosts on the local network.
  • Temporary SVG files created by tempfile.mkstemp are not guarded by try/finally, so they leak on any exception thrown by to_svg.
  • Blueprint names are embedded raw into HTML; while Python identifiers preclude injection today, using html.escape would be safer going forward.
  • tmp_path fixture parameters in test_graph.py are typed as object instead of pathlib.Path, forcing an unnecessary Path(str(...)) round-trip.

Confidence Score: 3/5

  • Draft PR — safe to iterate on, but the 0.0.0.0 binding issue should be fixed before merging.
  • The core DOT rendering changes are clean and well-tested. The main concern is in the new HTTP server utility: binding to all interfaces instead of loopback exposes internal architecture diagrams on the network, and the missing try/finally for temp files is a resource-leak risk. These are straightforward fixes but warrant attention before the PR leaves draft.
  • dimos/utils/cli/graph.py — server bind address and temp file cleanup need addressing.

Important Files Changed

Filename Overview
dimos/utils/cli/graph.py New CLI utility that loads a Python file, finds Blueprint globals, renders SVGs via graphviz, and serves them in a one-shot HTTP server. Two notable issues: the server binds to 0.0.0.0 (should be 127.0.0.1), and temp SVG files are not cleaned up on exception.
dimos/core/introspection/blueprint/dot.py Adds show_disconnected flag to render() and render_svg(). Logic for building disconnected channel stubs and the _COMPACT_ONLY_IGNORED_MODULES set-difference approach is correct and well-structured.
dimos/core/introspection/blueprint/test_dot.py New test file covering the show_disconnected=True/False/default cases. Tests are clear and properly verify both connected and disconnected channel behaviour.
dimos/core/introspection/svg.py Thin pass-through of the new show_disconnected parameter to blueprint_dot.render_svg(). No issues.
dimos/robot/cli/dimos.py New graph CLI command wired into the main typer app. Correctly inverts --no-disconnected flag to show_disconnected=True by default. No issues.
dimos/utils/cli/test_graph.py Tests for the graph CLI utility. Minor: tmp_path typed as object instead of pathlib.Path causing unnecessary Path(str(...)) round-trips. Does not test the HTTP server or full SVG rendering path (requires graphviz).

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as dimos graph (dimos.py)
    participant GraphMain as graph.main()
    participant BuildHTML as _build_html()
    participant Loader as importlib
    participant ToSVG as to_svg()
    participant DotRender as dot.render_svg()
    participant Graphviz as graphviz (dot)
    participant HTTPServer
    participant Browser

    User->>CLI: dimos graph <file.py> [--no-disconnected] [--port N]
    CLI->>GraphMain: main(python_file, show_disconnected, port)
    GraphMain->>BuildHTML: _build_html(python_file, show_disconnected)
    BuildHTML->>Loader: exec_module(file.py)
    Loader-->>BuildHTML: module with Blueprint globals
    loop For each Blueprint
        BuildHTML->>ToSVG: to_svg(bp, tmp_svg, show_disconnected)
        ToSVG->>DotRender: render_svg(bp, path, show_disconnected)
        DotRender->>Graphviz: dot -Tsvg (subprocess)
        Graphviz-->>DotRender: SVG bytes
        DotRender-->>ToSVG: writes SVG file
        ToSVG-->>BuildHTML: done
        BuildHTML->>BuildHTML: read SVG, unlink tmp file
    end
    BuildHTML-->>GraphMain: HTML string
    GraphMain->>HTTPServer: HTTPServer("0.0.0.0", port)
    GraphMain->>Browser: webbrowser.open(url)
    Browser->>HTTPServer: GET /
    HTTPServer-->>Browser: text/html response
    GraphMain->>GraphMain: server.handle_request() returns
    GraphMain-->>User: "Served. Exiting."
Loading

Last reviewed commit: d31d6a8

def log_message(self, format: str, *args: object) -> None:
pass

server = HTTPServer(("0.0.0.0", port), Handler)
Copy link
Contributor

Choose a reason for hiding this comment

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

HTTP server exposed on all network interfaces

The server binds to "0.0.0.0", which makes it reachable from any machine on the same network, not just the local developer's browser. This exposes the rendered blueprint diagrams (which may include internal architecture details) to anyone on the LAN.

Change the bind address to "127.0.0.1" (loopback only):

Suggested change
server = HTTPServer(("0.0.0.0", port), Handler)
server = HTTPServer(("127.0.0.1", port), Handler)

Comment on lines +62 to +67
fd, svg_path = tempfile.mkstemp(suffix=".svg", prefix=f"dimos_{name}_")
os.close(fd)
to_svg(bp, svg_path, show_disconnected=show_disconnected)
with open(svg_path) as f:
svg_content = f.read()
os.unlink(svg_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

Temp file leaked if to_svg raises an exception

If to_svg(...) raises (e.g. graphviz fails, a bad blueprint, etc.) the temporary file created by tempfile.mkstemp is never deleted — the os.unlink on line 67 is only reached on the happy path. Wrap this in a try/finally:

fd, svg_path = tempfile.mkstemp(suffix=".svg", prefix=f"dimos_{name}_")
os.close(fd)
try:
    to_svg(bp, svg_path, show_disconnected=show_disconnected)
    with open(svg_path) as f:
        svg_content = f.read()
finally:
    os.unlink(svg_path)
sections.append(f'<h2>{name}</h2>\n<div class="diagram">{svg_content}</div>')

with open(svg_path) as f:
svg_content = f.read()
os.unlink(svg_path)
sections.append(f'<h2>{name}</h2>\n<div class="diagram">{svg_content}</div>')
Copy link
Contributor

Choose a reason for hiding this comment

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

Blueprint name is embedded into HTML without escaping

name comes from Python module attribute names (so angle-bracket injection isn't possible today), but embedding it raw is a fragile pattern. If any future code path produces a name string with HTML special characters, this will produce broken or malicious HTML. Consider using html.escape(name) here:

Suggested change
sections.append(f'<h2>{name}</h2>\n<div class="diagram">{svg_content}</div>')
sections.append(f'<h2>{__import__("html").escape(name)}</h2>\n<div class="diagram">{svg_content}</div>')

Or import html at the top of the file and use html.escape(name).


def test_no_blueprints(tmp_path: object) -> None:
import pathlib

Copy link
Contributor

Choose a reason for hiding this comment

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

Incorrect type annotation for pytest tmp_path fixture

The pytest tmp_path fixture injects a pathlib.Path, but the parameter is typed as object. This makes the pathlib.Path(str(tmp_path)) on the next line an unnecessary round-trip. The same pattern appears at line 37.

Suggested change
def test_no_blueprints(tmp_path: "pathlib.Path") -> None:

Or import pathlib at the top and use pathlib.Path directly, eliminating the Path(str(tmp_path)) workaround.

…nnected stream indicators

Replace graphviz SVG rendering with client-side Mermaid diagrams for
blueprint visualization. Adds pan/zoom, two color themes (vivid and
tailwind), colored edge labels, and dashed borders on dangling streams.
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.

1 participant