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
_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.
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#
- 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.
(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
.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 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)#
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):
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.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 guide 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 succeeds
cd tutorials/20-visualization/python
uv run pytest -v
# 9 passed
# .NET
cd tutorials/20-visualization/dotnet
dotnet testThe 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/.dotnext to the code and add a CI drift check; a graph change then reviews like a code change. - One
Workflowobject 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
- Source on GitHub: tutorials/20-visualization
- Previous: Chapter 19: Declarative Workflows · Next: Chapter 20b: DevUI (upcoming)
Microsoft Agent Framework docs
Supporting tools
- Mermaid · Graphviz · GitHub Mermaid support
- Mermaid live editor: 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 guide: 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.
