Skip to main content

MAF v1 — Workflow Visualization (Python + .NET)

MAF v1 — Workflow Visualization (Python + .NET)
MAF v1: Python and .NET - This article is part of a series.
Part 20: This Article

Series note: Final advanced chapter before the wrap-up (Ch21: Putting It All Together). After this you can commit workflow graphs to the repo and trust that diffs in PRs mean something real.

Repo: Runnable code: tutorials/20-visualization.

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.

What’s covered
#

  • The two exporters (to_mermaid / ToMermaidString, to_digraph / ToDotString) and when to pick each.
  • Why the output is deterministic, and how that turns a diagram into a committable, diff-able artifact.
  • A CI drift check that regenerates the graph on every PR and fails when the committed diagram falls out of sync.
  • The Python and .NET API shapes side by side.

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.

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#2563eb','primaryTextColor': '#ffffff','primaryBorderColor': '#1e40af', 'lineColor': '#64748b','secondaryColor': '#f59e0b','tertiaryColor': '#10b981', 'background': 'transparent'}}}%% flowchart LR classDef core fill:#2563eb,stroke:#1e40af,color:#ffffff classDef external fill:#f59e0b,stroke:#b45309,color:#000000 classDef success fill:#10b981,stroke:#047857,color:#ffffff classDef infra fill:#64748b,stroke:#334155,color:#ffffff wf["Workflow object
(executors + edges)"] viz["WorkflowViz / ToMermaidString / ToDotString"] mmd[".mmd file
(committed to repo)"] dot[".dot file
(committed to repo)"] github["GitHub / GitLab
renders in PRs"] dotcli[Graphviz `dot` CLI] svg["SVG / PNG
(runbooks, wikis)"] ci["CI drift check
(regenerate → diff)"] wf --> viz viz --> mmd viz --> dot mmd --> github dot --> dotcli dotcli --> svg mmd -. regen .-> ci dot -. regen .-> ci class wf,viz core class mmd,dot infra class github,svg success class dotcli,ci external

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 guide 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:

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:

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 guide 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
#

# 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.

Worth remembering
#

  • Determinism is the feature, not a detail. Byte-identical output is what lets a diagram live in git as a diff-able artifact.
  • Commit the .mmd/.dot next to the code and add a CI drift check; a graph change then reviews like a code change.
  • One Workflow object feeds both exporters. You never re-describe the graph to get a picture.
  • MAF’s default palette is Mermaid stock, not this series’ scheme. Post-process the output if the diagram has to read in both light and dark themes.

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.

Further reading & links#

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

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.

MAF v1: Python and .NET - This article is part of a series.
Part 20: This Article

Related