Agent Mermaid reporting for duty
Note see also my standard AI Disclaimer
Update: This post was updated to use the latest Mermaid version, and a few other tweaks.
Introduction
My last few posts have been rather theoretical, this is back to a “here is how I solved a problem” post.
A while ago I blogged about better ways to make Mermaid diagrams but that was in the “before times” when I would do these things by hand, like a savage. OK I still edit them by hand - but LLMs like Claude Code are pretty good at drawing Mermaid - it’s just text, after all.
However, out of the box, Claude is trained on sample mermaid code in the wild - so it doesn’t always know the latest syntax, recent additions, or my own preferences. So this is a classic scenario where prompting can help.
The basic prompt
My first attempt at this just embedded all my Mermaid preferences in a markdown file I could load. Note I load this as needed - I don’t always want to draw Mermaid diagrams, and a key thing we are all discovering about agentic LLM tools is don’t pollute the context - the more things you tell Claude about, the worse it performs. So for Mermaid I have a prompt command /better-mermaid that loads all my settings into the current session. If I start a new session, or run /clear, all that noise is gone.
My prompt starts:
# Mermaid.js Guide for Claude Code
This is an advanced guide to building mermaid diagrams
## My preferences
- I like light backgrounds, so any text displayed on the background should be black or dark
- For text on shapes, you should make sure that either you use light text on a dark shape, or dark text on a light shape.
## Quick Reference
- **Hand-drawn look**: Add `config: look: handDrawn` in frontmatter
- **Invisible subgraphs**: Use `classDef invisible fill:#0000,stroke:#0000;`
- **New shape syntax**: `NodeName@{shape: diamond, label: "Decision"}`
- **Common shapes**: rect, rounded, diamond (decisions), cyl (database), doc (document), hex (process), trap-b (trapezoid), lean-r (I/O)
Note this was largely generated by Claude, partly based on my previous blog post! I’ve tweaked it a few times but it’s very hard to predict what will and won’t make good diagrams; this definitely needs more tweaking over time, and to suit individual preferences.
I wanted a before-and-after comparison - so I made a simple markdown file of all the famous actors in “One Flew Over the Cuckoo’s Nest” - here is a diagram using Claude without any extra prompts:
graph TD
JN[Jack Nicholson<br/>McMurphy]
SC[Scatman Crothers<br/>Turkel]
DD[Danny DeVito<br/>Martini]
CL[Christopher Lloyd<br/>Max Taber]
LF[Louise Fletcher<br/>Nurse Ratched]
BD[Brad Dourif<br/>Billy Bibbit]
WS[Will Sampson<br/>Chief Bromden]
JN ---|"4 films<br/>1972-1980"| SC
JN ---|"3 films<br/>1975-1983"| DD
DD ---|"3 works<br/>1975-1983"| CL
JN ---|"2 films<br/>1975-1978"| CL
JN --- LF
JN --- BD
JN --- WS
style JN fill:#ff9999
style SC fill:#ffcc99
style DD fill:#99ccff
style CL fill:#99ccff
style LF fill:#cccccc
style BD fill:#cccccc
style WS fill:#cccccc
and here is the same, using my basic prompt:
⏺ Now let me test the diagram to make sure it renders correctly:
⎿ Error: Parse error on line 20:
...o nicholson ==|"2 films"| lloyd
----------------------^
Expecting 'LINK', 'UNICODE_TEXT', 'EDGE_TEXT', got 'STR'
Oops! That was actually the first try, so I tried again:
---
config:
theme: base
themeVariables:
primaryColor: '#e0e7ff'
primaryTextColor: '#1e293b'
primaryBorderColor: '#6366f1'
lineColor: '#94a3b8'
secondaryColor: '#fef3c7'
tertiaryColor: '#fecaca'
fontSize: 14px
---
graph TB
%% Define actor nodes
nicholson["Jack Nicholson<br/>(McMurphy)"]
fletcher["Louise Fletcher<br/>(Nurse Ratched)"]
devito["Danny DeVito<br/>(Martini)"]
lloyd["Christopher Lloyd<br/>(Max Taber)"]
dourif["Brad Dourif<br/>(Billy Bibbit)"]
sampson["Will Sampson<br/>(Chief Bromden)"]
crothers["Scatman Crothers<br/>(Turkel)"]
%% Central film connection
ofotcn["One Flew Over the<br/>Cuckoo's Nest<br/>(1975)"]
%% OFOTCN connections (single weight)
nicholson --- ofotcn
fletcher --- ofotcn
devito --- ofotcn
lloyd --- ofotcn
dourif --- ofotcn
sampson --- ofotcn
crothers --- ofotcn
%% Strong connection: Nicholson & Crothers (4 films)
nicholson ---|"4 films<br/>1972-1980"| crothers
%% Strong connection: DeVito & Lloyd (3 collaborations)
devito ---|"3 works<br/>incl. Taxi"| lloyd
%% Strong connection: Nicholson & DeVito (3 films)
nicholson ---|"3 films<br/>1975-1983"| devito
%% Medium connection: Nicholson & Lloyd (2 films)
nicholson ---|"2 films"| lloyd
%% Define styles
classDef actorStyle fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e293b
classDef filmStyle fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#78350f
classDef starStyle fill:#e0e7ff,stroke:#6366f1,stroke-width:3px,color:#1e293b
%% Apply styles
class nicholson starStyle
class fletcher,devito,lloyd,dourif,sampson,crothers actorStyle
class ofotcn filmStyle
%% Style the edges by strength (numbered in order of definition)
linkStyle 7 stroke:#dc2626,stroke-width:5px
linkStyle 8 stroke:#dc2626,stroke-width:4px
linkStyle 9 stroke:#dc2626,stroke-width:4px
linkStyle 10 stroke:#f59e0b,stroke-width:3px
Better results through feedback and iteration
This is nice - but Claude didn’t pick up on the error initially - we needed one key thing that helps agentic LLMs - feedback! On its own, Claude will sometimes make a mistake and not even notice. But - give it a way to get some feedback and things get much better. And it’s pretty easy to get feedback on Mermaid diagrams, using the Mermaid-cli command-line tool - this can syntax check mermaid diagrams, and convert them to png files using a simple command like:
npx -p @mermaid-js/mermaid-cli@latest mmdc -s 3 -i tmp.mmd -o tmp.png
(note: previously I didn’t include @latest which meant npx only ever fetched the Mermaid CLI tool once and never updated it! Now I add @latest and npx will check for new versions and stay up to date)
So I modified my prompt to gather feedback:
You should test any mermaid diagrams you draw:
1. extract just the mermaid diagram from the markdown file,
2. call mermaid-cli to convert it to a png file
3. check for any syntax errors returned
4. read the png file to see if it looks like what was wanted
5. delete the png file if all is OK
(followed by instructions on using mermaid-cli)
This means Claude can both see any syntax errors, and also read the image that is produced - despite being named a “Large Language Model”, Claude is actually a Multi-Modal Model and can read and interpret images. So if we tell it to, it will read the png file and “understand” it, sort-of. It’s really doing something similar to what it does with language - it turns the image into some sort of tokens and then does pattern matching. The actual abilities and limitations are very unclear - I encourage people to experiment. It certainly seems to be good enough for validating diagrams!
(Side note - Anthropic have recommended maximum image sizes to use - sadly mermaid-cli doesn’t actually respect image sizes you pass to it, for really big diagrams you might need to lower the -s 3 parameter)
(Another side note - I tried using SVG outputs instead, but they ended up doing much worse - the mermaid svg output files are massive and Claude didn’t do well at reading them)
If you use this prompt, Claude will make a diagram, turn it into an image, and then iterate - if there’s an error or something that goes against your requests, it will keep trying - this is especially good for avoiding syntax errors.
Aside - reverse engineering diagram images
Because Claude can read images, it can also turn an image of a diagram into a mermaid diagram. This isn’t perfect - a lot of diagrams can’t be turned exactly into Mermaid - but it works pretty well. Just yesterday a colleague sent me a screenshot of a mermaid diagram they’d drawn, and I just pasted it into Claude Code (you can paste images directly into Claude Code!) and said “please generate a mermaid diagram from this image”.
For another example - if I grab the diagram from this wikipedia page:

And ask Claude to turn it into a mermaid diagram, I get:
---
config:
theme: base
themeVariables:
primaryColor: '#d4f4dd'
primaryTextColor: '#000000'
primaryBorderColor: '#333333'
secondaryColor: '#b3e5fc'
secondaryTextColor: '#000000'
secondaryBorderColor: '#333333'
tertiaryColor: '#90ee90'
fontSize: 14px
lineColor: '#333333'
---
graph TB
%% Define stakeholder nodes (light green boxes)
IDPS["Interface<br/>Data<br/>Processing<br/>Segment<br/>(IDPS)"]
MMC["Mission<br/>Management<br/>Center<br/>(MMC)"]
PD["Product<br/>Developers"]
IPO["Integrated<br/>Program<br/>Office<br/>(IPO)"]
CUSTOMERS["CUSTOMERS"]
CLASS["Comprehensive<br/>Large Array<br/>Stewardship<br/>System<br/>(CLASS)"]
MGMT["Management"]
%% Define central node (light blue ellipse)
NDE(["NPOESS<br/>Data<br/>Exploitation<br/>(NDE)"])
%% Data flows
IDPS -->|xDRs| NDE
MMC -->|Instrument Status| NDE
MMC -->|Satellite Status| NDE
PD -->|Algorithms| NDE
NDE -->|Operational<br/>Standards| PD
IPO -->|Service Requests| NDE
NDE -->|xDR Enhancement<br/>Requirements| IPO
NDE -->|NOAA-unique Products| CUSTOMERS
NDE -->|Tailored Products| CUSTOMERS
NDE -->|Service Requests| CUSTOMERS
NDE -->|Service Responses| CUSTOMERS
NDE -->|NOAA-unique<br/>Products| CLASS
NDE -->|System<br/>Components| CLASS
NDE -->|Reports| MGMT
%% Style definitions
classDef stakeholder fill:#90ee90,stroke:#333,stroke-width:2px,color:#000;
classDef central fill:#b3e5fc,stroke:#333,stroke-width:3px,color:#000;
%% Apply styles
class IDPS,MMC,PD,IPO,CUSTOMERS,CLASS,MGMT stakeholder;
class NDE central;
It looks quite different - Mermaid doesn’t really let you tweak layout much - but the content is correct (I haven’t checked closely!) and now I have an easily modified diagram.
A further refinement - using a subagent
The only downside of all of this mermaid processing is - it consumes context. A key thing I’ve learned in the last few months is that LLMs have limited context storage, and the more context you give them, the worse they work. Basically, too much information confuses them - they start ignoring things, they start getting tangled up. This is especially true if you change the subject a lot - ask an LLM about “One Flew Over the Cuckoo’s Nest” and then about “subgraphs in mermaid” and you run the risk of getting poorer results. Especially if you run out of context - Claude has an auto-packing mechanism that compresses context for you, but it isn’t perfect - and generally you are far better using /clear and starting from scratch as often as you can.
So if I want to draw one diagram, the basic feedback-driven prompt is fine - but if I want to draw a lot, I use a sub-agent. In Claude Code, a sub-agent is simply a markdown file in a special location - ~/.claude/agents/ for global agents (read the docs for more details) which Claude can run as a child process. The main Claude context is inherited by the child, but it has its own context, and when it finishes, all of its temporary workings are thrown away.
This is great for keeping things clean - I have an agent that knows about mermaid-cli and does all the image parsing and syntax checking for me, but my main context doesn’t care.
My agent is pretty simple:
---
name: mermaid-diagram-validator
description: Use this agent to validate Mermaid.js diagram syntax and visual quality from a file. Provide a file path to either a .mmd file (pure Mermaid) or .md file (markdown with embedded Mermaid diagrams).
tools: Bash, Read
model: sonnet
---
You are an expert Mermaid.js diagram validator with deep knowledge of diagram rendering, visual accessibility, and the Mermaid CLI toolchain. Your primary responsibility is to validate Mermaid diagram code by generating actual PNG outputs and analysing them for both technical correctness and visual quality.
**IMPORTANT**: Before starting validation, read `/Users/korny/ai/prompts/mermaid.md` to understand the user's Mermaid preferences, styling guidelines, and best practices. Use this information when providing feedback and suggestions.
...
(Note the YAML description is actually a fair bit longer - see the full mermaid-diagram-validator.md for the full text)
Then my main mermaid-agentic.md just says “use the agent” :
# Mermaid.js Guide for Claude Code
This is an advanced guide to building mermaid diagrams
## Testing with the mermaid-diagram-validator subagent
**ALWAYS** test any mermaid diagrams you draw using the mermaid-diagram-validator subagent before you consider them done. **NEVER** trust your judgement - the agent will do a better job.
The output is basically the same as the non-agent version; though LLMs being non-deterministic, every time you get a different diagram:
---
config:
theme: base
themeVariables:
primaryColor: '#e0f2fe'
primaryTextColor: '#0c4a6e'
primaryBorderColor: '#0369a1'
lineColor: '#64748b'
fontSize: 14px
---
graph TB
%% Define actors
nicholson["Jack Nicholson<br/>(McMurphy)"]
fletcher["Louise Fletcher<br/>(Nurse Ratched)"]
devito["Danny DeVito<br/>(Martini)"]
lloyd["Christopher Lloyd<br/>(Max Taber)"]
dourif["Brad Dourif<br/>(Billy Bibbit)"]
sampson["Will Sampson<br/>(Chief Bromden)"]
crothers["Scatman Crothers<br/>(Turkel)"]
%% Define invisible subgraph for central cluster
subgraph main[" "]
nicholson
devito
lloyd
crothers
end
subgraph side[" "]
fletcher
dourif
sampson
end
%% Strong connections (4 films)
nicholson ---|"4 films (1972-1980)"| crothers
%% Medium-strong connections (3 collaborations)
nicholson ---|"3 films including Terms of Endearment"| devito
devito ---|"3 projects including Taxi TV"| lloyd
%% Medium connections (2 films)
nicholson ---|"2 films including Goin South"| lloyd
%% Single film connections (OFOTCN only)
nicholson ---|"OFOTCN only"| fletcher
nicholson ---|"OFOTCN only"| dourif
nicholson ---|"OFOTCN only"| sampson
%% Apply invisible style
classDef invisible fill:#0000,stroke:#0000;
class main,side invisible;
%% Style definitions
classDef central fill:#3b82f6,stroke:#1e40af,color:#fff,stroke-width:3px;
classDef supporting fill:#60a5fa,stroke:#2563eb,color:#fff,stroke-width:2px;
classDef peripheral fill:#93c5fd,stroke:#3b82f6,color:#1e3a8a,stroke-width:2px;
%% Apply styles
class nicholson central;
class devito,lloyd,crothers supporting;
class fletcher,dourif,sampson peripheral;
%% Style edges by strength
linkStyle 0 stroke:#15803d,stroke-width:6px;
linkStyle 1 stroke:#16a34a,stroke-width:4px;
linkStyle 2 stroke:#16a34a,stroke-width:4px;
linkStyle 3 stroke:#22c55e,stroke-width:3px;
linkStyle 4,5,6 stroke:#94a3b8,stroke-width:1px,stroke-dasharray:5 5;
I didn’t like this so I asked:
the diagram is still a bit ugly - can you clear it up and make it look nicer?
and got:
---
config:
theme: base
themeVariables:
fontSize: 16px
---
graph LR
%% Define actors with stadium shape for central hub
nicholson(["Jack Nicholson<br/>McMurphy"])
%% Supporting actors with rounded rectangles
devito["Danny DeVito<br/>Martini"]
lloyd["Christopher Lloyd<br/>Max Taber"]
crothers["Scatman Crothers<br/>Turkel"]
%% Peripheral actors
fletcher["Louise Fletcher<br/>Nurse Ratched"]
dourif["Brad Dourif<br/>Billy Bibbit"]
sampson["Will Sampson<br/>Chief Bromden"]
%% Layout using invisible subgraphs
subgraph left[" "]
fletcher
dourif
sampson
end
subgraph center[" "]
nicholson
end
subgraph right[" "]
crothers
devito
lloyd
end
%% Connections - strongest first
nicholson ---|"4 films (1972-1980)"| crothers
nicholson ---|"3 films including Terms of Endearment"| devito
devito ---|"3 projects including Taxi TV"| lloyd
nicholson ---|"2 films including Goin South"| lloyd
%% Single film connections
nicholson ---|"OFOTCN only"| fletcher
nicholson ---|"OFOTCN only"| dourif
nicholson ---|"OFOTCN only"| sampson
%% Apply invisible style to subgraphs
classDef invisible fill:#0000,stroke:#0000;
class left,center,right invisible;
%% Style definitions - warm sophisticated palette
classDef hub fill:#f59e0b,stroke:#d97706,color:#fff,stroke-width:4px,rx:20,ry:20;
classDef inner fill:#3b82f6,stroke:#2563eb,color:#fff,stroke-width:3px,rx:10,ry:10;
classDef outer fill:#e0e7ff,stroke:#818cf8,color:#1e1b4b,stroke-width:2px,rx:10,ry:10;
%% Apply styles
class nicholson hub;
class crothers,devito,lloyd inner;
class fletcher,dourif,sampson outer;
%% Style edges with color gradient by strength
linkStyle 0 stroke:#dc2626,stroke-width:7px;
linkStyle 1 stroke:#ea580c,stroke-width:5px;
linkStyle 2 stroke:#ea580c,stroke-width:5px;
linkStyle 3 stroke:#f59e0b,stroke-width:4px;
linkStyle 4,5,6 stroke:#94a3b8,stroke-width:1.5px,stroke-dasharray:4 4;
For a real problem I tend to iterate a lot - “Can you group these things together?” or “Can you change the colour of X to something more blue-green?” - but it’s quite straightforward.
Note, by the way, that this is a fun example of using agents but not a huge improvement. I find myself going back to the non-agentic version for tweaking - it uses more context, but it also knows a lot more about the image produced - the agentic version has to turn the image to a text description, which means it loses a bit of information along the way. But for generating a lot of diagrams quickly, it’s great - especially when I’m documenting a whole project, I just want diagrams that work and want them quickly.
There is one slight hassle - permissions. Claude will prompt you over and over again “Do you want to run npx -p @mermaid-js/mermaid-cli@latest mmdc -s 3 -i ...?” - in theory you can set Bash permissions with wildcards but in my experience it just doesn’t work, except for very simple exact matches. Even giving it permissions Bash(rm /tmp/mermaid:*) doesn’t work - I get asked for every single file removal. I don’t mind Claude being cautious here - but it is very tedious that it asks ever single time.
So I built a workaround - using their suggestion Additional permission control with hooks - which I’ll document in the next post!
I’ve given up on Discus for comments - if you want to discuss this post, please reply to My post on Mastodon or My post on Bluesky (I’m doing both as one is more free, one is more convenient for many people)
-
This secret agent mermaid was generated by Claude, inspired by Simon Willison’s pelican on a bicycle LLM benchmark. ↩