Nitin Kumar SinghSolutions Architect

Type to search. to move, Enter to open.

    move open esc close
    Deep DiveAI EngineeringPart 22 of 29

    MAF v1 — Workflow Visualization (Python + .NET)

    Render any workflow as Mermaid (GitHub-friendly) or Graphviz DOT (production runbooks). One line each, deterministic output, dark-mode-safe palette.

    Why this chapter

    A workflow you can’t see is hard to review and impossible to reason about on-call at 3 AM. MAF ships two one-liner exporters, Mermaid (GitHub-native) and Graphviz DOT (production runbooks, wikis, Confluence), that turn any Workflow object into a diagram. Both are deterministic: the same workflow always produces the exact same bytes. That determinism is the whole point. Commit the output, and the next PR that changes the graph surfaces the diff exactly like a source-code diff.

    Three moments when a code-only graph bites:

    • A PR reviewer needs to know whether a refactor changed the workflow’s shape (rename _FanOutExecutor to _DispatchExecutor: same graph or different?).
    • On-call, you need to know which executor in a 14-node return-replace workflow handed an event to which next step.
    • A stakeholder asks what the agent actually does, in a flow chart they can paste into a deck.

    Mermaid covers the first two, Graphviz DOT the third.

    Prerequisites

    The concept

    Two representations, one purpose:

    MAF produces both from the same Workflow object. You never re-describe your graph for the sake of a picture.

    In codeCommitted to the repoWhere it shows upAnd in CIWorkflow objectexecutors + edgesWorkflowVizToMermaidString / ToDotString.mmd fileMermaid source.dot fileGraphviz sourceGitHub / GitLabrenders in pull requestsGraphviz dot CLISVG / PNGrunbooks, wikisDrift checkregenerate, then diff
    One object, two text formats, three destinations. The .mmd and .dot files are committed, which is what makes the CI drift check possible: regenerate them and diff.

    One source object → two serialized forms → two rendering pipelines. The dotted lines back to “CI drift check” are what make committing the diagram worthwhile: CI regenerates the graph on every PR and fails if it doesn’t match the committed file.

    Jargon recap

    • Mermaid. Text-based diagram syntax (flowchart, sequenceDiagram, stateDiagram-v2). See the Mermaid style guide2 for the palette this series uses.
    • Graphviz DOT. The .dot language used by the Graphviz toolkit since 1991. Rasterized via dot -Tsvg input.dot -o out.svg.
    • flowchart directive. Mermaid’s DAG syntax. First line of every flowchart declares direction (LR, TD, RL, BT).
    • Determinism. The exporter iterates executors and edges in insertion order, not hash order. Same graph → identical bytes on every rebuild.

    Python

    Full source: python/main.py. Key lines:

    from agent_framework._workflows._viz import WorkflowViz
    from agent_framework._workflows._workflow_builder import WorkflowBuilder
    workflow = (
    WorkflowBuilder(start_executor=uppercase, name="demo-pipeline")
    .add_edge(uppercase, validate)
    .add_edge(validate, log)
    .build()
    )
    mermaid = WorkflowViz(workflow).to_mermaid()
    dot = WorkflowViz(workflow).to_digraph()
    pathlib.Path("workflow.mmd").write_text(mermaid)
    pathlib.Path("workflow.dot").write_text(dot)

    That’s the whole API. WorkflowViz(...) wraps the workflow once and exposes .to_mermaid(), .to_digraph(), and .save_png(path) for one-shot image export (requires graphviz on the system).

    Rendered Mermaid (basic)

    flowchart TD
    uppercase["uppercase (Start)"];
    validate["validate"];
    log["log"];
    uppercase --> validate;
    validate --> log;

    Rendered DOT (basic)

    digraph Workflow {
    rankdir=TD;
    node [shape=box, style=filled, fillcolor=lightblue];
    "uppercase" [fillcolor=lightgreen, label="uppercase\n(Start)"];
    "validate" [label="validate"];
    "log" [label="log"];
    "uppercase" -> "validate";
    "validate" -> "log";
    }

    Note: MAF’s default node colours are lightblue / lightgreen. They look fine on most GitHub themes but don’t match this series’ palette. For production docs, post-process the DOT output, or wrap to_digraph() with a small templating pass that replaces the fill colours.

    Rendered Mermaid (complex: conditional edges + agents)

    For realistic workflows with agent-executors and conditional edges, the output has the same shape but more nodes. Excerpt from a pre-purchase research workflow (Ch11 + Ch13):

    flowchart LR
    user["start"]
    reviews["ReviewsAgent"]
    stock["StockExecutor"]
    price["PriceHistoryAgent"]
    shipping["ShippingExecutor"]
    synth["SynthesizeExecutor"]
    fanout{{fan-out}}
    fanin{{fan-in}}
    user --> fanout
    fanout --> reviews
    fanout --> stock
    fanout --> price
    reviews --> fanin
    stock --> shipping
    shipping --> fanin
    price --> fanin
    fanin --> synth

    Same one-liner, same file size, same determinism. The only code change is the WorkflowBuilder you pass in.

    Run it:

    Terminal window
    cd tutorials/20-visualization/python
    uv sync
    uv run python main.py
    # writes workflow.mmd + workflow.dot to cwd
    cat workflow.mmd

    .NET

    Full source: dotnet/Program.cs. The .NET API shape matches Python:

    using Microsoft.Agents.AI.Workflows;
    var workflow = new WorkflowBuilder<string>()
    .AddExecutor(uppercase)
    .AddEdge(uppercase, validate)
    .AddEdge(validate, log)
    .Build("demo-pipeline");
    string mermaid = workflow.ToMermaidString();
    string dot = workflow.ToDotString();
    File.WriteAllText("workflow.mmd", mermaid);
    File.WriteAllText("workflow.dot", dot);

    Extension methods ToMermaidString() and ToDotString() live on Workflow. Both are pure (no I/O). You own the file write.

    Run it:

    Terminal window
    cd tutorials/20-visualization/dotnet
    dotnet run
    cat workflow.mmd

    Side-by-side differences

    AspectPython.NET
    Mermaid outputWorkflowViz(wf).to_mermaid()wf.ToMermaidString()
    DOT outputWorkflowViz(wf).to_digraph()wf.ToDotString()
    PNG exportWorkflowViz(wf).save_png(path) (needs graphviz)Pipe ToDotString() through the dot CLI
    API shapeWrapper classExtension methods on Workflow
    Colour customisationSubclass or post-processPost-process string

    Why determinism matters: committing diagrams to the repo

    Because .to_mermaid() / ToMermaidString() produce byte-identical output for the same graph, you can treat the diagram file as a build artefact that lives in git. The workflow is:

    1. Developer edits workflows/foo.py (adds an executor).
    2. Locally runs scripts/visualize_workflows.py, which writes docs/workflows/foo.mmd and foo.dot.
    3. Commits all three files together in one PR.
    4. On CI, a drift check regenerates the diagrams from the code and runs git diff --exit-code docs/workflows/. Non-zero means the committed diagrams don’t match the code.

    Reviewers see the workflow diff in the PR rendered as a diagram (GitHub renders .mmd inline). A structural change to the graph is as visible as a typo in a function name.

    This is the pattern the capstone uses. See Capstone integration below.

    Gotchas

    • Node IDs must be unique. Workflows with two nodes sharing an ID fail at build time (we hit this writing this chapter, two ValidateExecutor() instances collide on id="validate"). Visualisation renders fine once the build succeeds.
    • Mermaid is GitHub-native, but with caveats. GitHub’s rendered theme tracks your profile setting; if you want a diagram that reads well for both light- and dark-mode readers, use the palette from the Mermaid style guide2 and explicit classDefs. MAF’s default output doesn’t; it uses Mermaid defaults.
    • DOT needs Graphviz to rasterize. The .dot text is portable; producing PNG/SVG requires graphviz locally or in CI.
    • Determinism isn’t free. If your builder adds edges in a non-deterministic order (e.g., iterating a set in Python or a HashSet<T> in .NET), the rendered output shuffles between runs. Iterate over ordered collections.
    • Cycles in a handoff mesh render as cycles. That’s correct but can look messy. Mermaid handles cycles natively; DOT does too, but rankdir=LR makes them easier to read than TD.

    Tests

    Terminal window
    # Python — 9 tests: non-empty output, flowchart directive, all nodes present,
    # all edges present, deterministic (mermaid + dot), valid digraph header,
    # build succeeds
    cd tutorials/20-visualization/python
    uv run pytest -v
    # 9 passed
    # .NET
    cd tutorials/20-visualization/dotnet
    dotnet test

    The determinism tests are the interesting ones: they call the exporter twice and assert byte-equality.

    How this shows up in the capstone

    scripts/visualize_workflows.py (part of Phase 7 refactor, see plans/refactor/13-visualization.md) iterates every registered workflow (pre-purchase, return-replace, concierge) and writes Mermaid + DOT to docs/workflows/. A GitHub Actions drift check runs the script on every PR and fails the build if committed diagrams disagree with the current code.

    This chapter

    Microsoft Agent Framework docs

    Supporting tools

    Where it lives in the capstone

    • scripts/visualize_workflows.py iterates every registered workflow.
    • docs/workflows/*.mmd + *.dot: committed artefacts.
    • .github/workflows/workflow-drift.yml: CI drift check.

    Series shared resources

    • Mermaid style guide2: the palette all series diagrams use.
    • Jargon glossary: one-line definitions.

    What’s next

    Chapter 20b: DevUI (upcoming) is a live browser dashboard for testing agents and workflows interactively. Static visualization meets interactive runtime inspection.

    Then Chapter 21: Putting it all together ties every chapter back to a specific file path in the real app.

    References

    1. tutorials/20-visualization — github.com 2

    2. Mermaid style guide — github.com 2 3

    3. Mermaid — mermaid.js.org

    4. Graphviz — graphviz.org

    5. Mermaid live editor — mermaid.live

    Comments

    Comments are GitHub discussions. Sign in with GitHub to post; reactions need no account.