AgentGraph visualization
build_graph draws a picture of how an agent or a
Workflow is put together: its architecture, every
sub-agent, the tools each one can call, and a workflow’s steps and
routes. The result renders as a Mermaid
diagram. Use it to:
- Document a team layout in a README or design doc.
- Debug wiring: see at a glance whether a worker is connected where you think it is, and which tools it has.
- Onboard someone: a new engineer reads the diagram before the code.
This is a drawing only; nothing in it runs. To see the architecture of every version your app has run, with the path each request took highlighted, turn on Studio: it draws the same graph.
Quick render
build_graph is async (listing tools can go through an MCP server), so
await it. It returns an AgentGraph; call to_mermaid() on it for
the diagram text.
from loomflow import Agent, tool
from loomflow.graph import build_graph
from loomflow.team import Team
@tool
async def web_search(query: str) -> str:
"""Search the web."""
return "results"
researcher = Agent("Find sources for the topic.", model="echo", tools=[web_search])
writer = Agent("Write the draft.", model="echo")
team = Team.supervisor(
workers={"researcher": researcher, "writer": writer},
instructions="Manage the research-and-write pipeline.",
model="echo",
)
graph = await build_graph(team)
print(graph.to_mermaid())Output (the classDef styling lines are trimmed here):
Each agent is labelled with its name= when it has one, else the
first line of its instructions (cut at 60 characters). Node ids are
stable (a0, a1 for agents, t0, t1 for tools), so the output
diffs cleanly between runs.
Every Agent also has a shortcut that does the same thing:
mermaid_text = await team.generate_graph() # returns the text
await team.generate_graph("docs/team.md") # also writes a fileWorkflows
Pass a Workflow and the graph shows its steps: a start and end
node, each step, and the edges between them. Routes are labelled with
their keys and a default route is dashed. An agent in a node is drawn
with its tools (and its own team, if it has one); a nested workflow is
a node with a dashed runs edge into its own group; a
Workflow.parallel step has an in parallel edge to each branch.
from loomflow import Agent, Workflow
from loomflow.graph import build_graph
async def classify(text: str) -> str:
return "billing" if "invoice" in text else "tech"
billing = Agent("You handle billing.", model="echo", name="billing")
tech = Agent("You fix tech issues.", model="echo", name="tech-support")
router = Workflow.route(classify, {"billing": billing, "tech": tech}, name="support-router")
graph = await build_graph(router, title="support-router")
print(graph.to_mermaid())As data
graph.to_dict() returns the same graph as JSON-ready data, to draw
it your own way or to diff two versions:
data = graph.to_dict()
data["title"] # "support-router"
data["nodes"][2] # {"id": "a0", "label": "billing", "kind": "subagent",
# "keys": ["billing", "route_billing"],
# "detail": "node route_billing"}
data["edges"][-2] # {"source": "n0", "target": "a0",
# "label": "billing", "style": "solid"}
data["subgraphs"] # []A node’s kind is one of agent, subagent, tool, step,
workflow, start, end, store (a lesson store) or blackboard. Its
keys are the names recorded runs use for it (an agent’s name, the
workflow node it runs as, a tool’s name), which is how Studio
highlights the parts a run used. detail is a short note: a team’s
architecture, or the node an agent runs as. Pass
key_for=lambda agent: ... to build_graph to choose how agents are
keyed (the default is their name=).
Write to disk
write_graph(agent, path) renders the graph and writes it to path.
The file extension picks the format:
| Extension | What gets written |
|---|---|
.mmd | Raw Mermaid source. |
.md | A Markdown file with a # <title> heading and the diagram in a mermaid fence. Renders on GitHub, GitLab, and most IDE previews. |
.png / .svg | An image rendered by the public mermaid.ink service (needs network access). |
| anything else | Raw Mermaid source. |
from loomflow.graph import write_graph
await write_graph(team, "docs/team.md") # Markdown + diagram
await write_graph(team, "docs/team.mmd") # Mermaid source
await write_graph(team, "docs/team.svg", title="Team") # image via mermaid.inkwrite_graph returns the Mermaid text in every case. title= sets
the heading of the .md file and defaults to the file name without
its extension.
If mermaid.ink can’t be reached for a .png / .svg, write_graph
writes the Mermaid source next to the requested path (same name,
.mmd extension) and raises RuntimeError saying where it put it.
You can paste that file into mermaid.live to
render it.
What gets included
The walk starts at the agent you pass and follows its architecture down:
- Every agent, labelled with its name or from its instructions. The
same
Agentinstance reached twice is drawn once. - Every tool each agent can call, including tools that come from
an MCP server. There is no switch for this; tools are always drawn,
as parallelogram nodes with a
usesedge. - Sub-agents, recursively. A worker that is itself a Supervisor, or a Reflexion loop around a Supervisor, appears as a nested group.
- The relationships each architecture creates:
| Architecture | What the diagram shows |
|---|---|
| Supervisor | Coordinator to each worker, with delegate and (dashed) forward edges. Workers sit in a workers group. |
| Router | Classifier to each route with a classify: <name> edge, in a group noting that only one route runs. |
| Swarm | An entry edge to the entry peer, then dashed handoff edges between every pair of peers. |
| ActorCritic | Actor to critic (critique) and a dashed refine if below threshold edge back. |
| MultiAgentDebate | Each debater in a group, with responses edges to the judge. |
| Blackboard | A shared blackboard node that every agent reads and writes, plus the coordinator and decider. |
| Reflexion | A Reflexion retry loop group around the base architecture, plus a lesson_store node when one is set. |
| ReAct | Just the agent and its tools. |
A custom architecture is drawn from its declared_workers(): one edge
from the parent to each named worker.
Why not OTel traces?
The two answer different questions. OTel traces show what happened
during one specific agent.run(). AgentGraph shows the shape of your
agent, which is the same whatever the input.
| Question | Tool |
|---|---|
| ”What does this agent’s structure look like?” | AgentGraph |
| ”Why was this run slow?” | OTel traces |
| ”Which worker was called for this prompt?” | OTel traces |
| ”Could the supervisor delegate to the reviewer at all?” | AgentGraph |
Keep README diagrams in sync. Run a small script like this in your release process and the diagram always matches the code:
import asyncio
from loomflow.graph import write_graph
from myapp import team # your Agent or Team
asyncio.run(write_graph(team, "docs/team.md"))