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
_FanOutExecutorto_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
- Completed Chapter 19: Declarative Workflows
- Familiar with Chapter 09: Workflow Executors and Edges (executors, edges,
WorkflowBuilder)
The concept
Two representations, one purpose:
- Mermaid. Text diagrams that GitHub, GitLab, Bitbucket, Obsidian, and most modern markdown renderers understand natively. Commit
.mmdfiles next to your code and they render inline in PRs, issues, READMEs, and wikis. - Graphviz DOT. The venerable
.dotformat, rasterized via thedotCLI into PNG / SVG / PDF. Better for large graphs, runbook pages, and documentation where you need pixel-perfect control.
MAF produces both from the same Workflow object. You never re-describe your graph for the sake of a picture.
.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
.dotlanguage used by the Graphviz toolkit since 1991. Rasterized viadot -Tsvg input.dot -o out.svg. flowchartdirective. 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 WorkflowVizfrom 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 --> synthSame one-liner, same file size, same determinism. The only code change is the WorkflowBuilder you pass in.
Run it:
cd tutorials/20-visualization/pythonuv syncuv run python main.py# writes workflow.mmd + workflow.dot to cwdcat 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/dotnetdotnet runcat workflow.mmdSide-by-side differences
| Aspect | Python | .NET |
|---|---|---|
| Mermaid output | WorkflowViz(wf).to_mermaid() | wf.ToMermaidString() |
| DOT output | WorkflowViz(wf).to_digraph() | wf.ToDotString() |
| PNG export | WorkflowViz(wf).save_png(path) (needs graphviz) | Pipe ToDotString() through the dot CLI |
| API shape | Wrapper class | Extension methods on Workflow |
| Colour customisation | Subclass or post-process | Post-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:
- Developer edits
workflows/foo.py(adds an executor). - Locally runs
scripts/visualize_workflows.py, which writesdocs/workflows/foo.mmdandfoo.dot. - Commits all three files together in one PR.
- 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
.dottext is portable; producing PNG/SVG requiresgraphvizlocally or in CI. - Determinism isn’t free. If your builder adds edges in a non-deterministic order (e.g., iterating a
setin Python or aHashSet<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=LRmakes them easier to read thanTD.
Tests
# Python — 9 tests: non-empty output, flowchart directive, all nodes present,# all edges present, deterministic (mermaid + dot), valid digraph header,# build succeedscd tutorials/20-visualization/pythonuv run pytest -v# 9 passed
# .NETcd tutorials/20-visualization/dotnetdotnet testThe 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.
Further reading & links
This chapter
- Source on GitHub: tutorials/20-visualization1
- Previous: Chapter 19: Declarative Workflows · Next: Chapter 20b: DevUI (upcoming)
Microsoft Agent Framework docs
Supporting tools
- Mermaid3 · Graphviz4 · GitHub Mermaid support
- Mermaid live editor5: paste MAF output, preview, experiment.
Where it lives in the capstone
scripts/visualize_workflows.pyiterates 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
-
tutorials/20-visualization — github.com ↩ ↩2
-
Mermaid style guide — github.com ↩ ↩2 ↩3
-
Mermaid live editor — mermaid.live ↩
Comments
Comments are GitHub discussions. Sign in with GitHub to post; reactions need no account.