Getting a graph
from agentloom import WorkflowGraphfrom agentloom.core.parser import WorkflowParser
workflow = WorkflowParser.from_yaml("examples/03_router_workflow.yaml")graph = WorkflowGraph.from_workflow(workflow)
graph.layers # [['classify'], ['route'], ['handle_billing', 'handle_general', 'handle_technical']]graph.critical_path() # ['classify', 'route', 'handle_billing']print(graph.to_mermaid())WorkflowGraph.from_workflow(workflow)takes a parsed workflow or one built with the Python DSL. It refuses a cycle with aValidationError, but doesn’t check that dependencies exist: validate a DSL build first, withWorkflowParser.from_dict(workflow.model_dump()).WorkflowGraph.from_dag(dag)takes a bareDAGfromagentloom.core.dag. Its nodes have no step behind them: each is typedllm_call, andget_step_definitionreturnsNone.
WorkflowGraph, GraphNode and GraphEdge import from agentloom itself, and need nothing beyond the core package, except to_networkx.
Nodes and edges
| Model | Field | What it is |
|---|---|---|
GraphNode | id | The step’s id. |
type | The step type: llm_call, tool, router, subworkflow, approval_gate, embed or agent. | |
depends_on | The step’s dependencies. | |
label | The step’s id. | |
GraphEdge | source, target | An edge from a step to one that depends on it. |
label | On a router’s edges, the condition that leads to the target, or default. Empty elsewhere. |
- Edges come from
depends_ononly. A router’s conditions label the edges to its targets; a target that doesn’t list the router independs_ongets no edge from it. - A router’s edge has one label. When several conditions lead to one target, the last one names the edge, and
defaultwins over all of them. - Every branch is a path, though a run takes only one of a router’s branches.
- A subworkflow is one node. Its own steps aren’t expanded.
Both models are immutable.
Structure
| Property | What it holds |
|---|---|
nodes | The nodes, in the order they can run; steps that could run together are sorted by id. |
edges | The edges, sorted by source and target. |
roots | The steps that depend on nothing. |
leaves | The steps nothing depends on. |
layers | The steps grouped in the layers the engine runs them in. See Dependencies and layers. |
Paths
critical_path()is the longest chain of steps, counted in steps. It knows nothing of durations or costs, so it isn’t a latency estimate. Between chains of the same length, the step ids decide which one it returns, not how likely a branch is: in example 03, all three branches have three steps, and it returns the billing one.all_paths()lists every chain from a root to a leaf, each of a router’s branches included.prime_paths(max_paths=10_000)lists the paths that no longer path contains. In a DAG, these are the same asall_paths().max_pathscaps the paths the search keeps along the way, which start at one per step, and a graph that needs more raises aValueError.get_step_definition(node_id)returns the step behind a node, orNonefor an unknown id.
Exports
print(graph.to_dot())digraph workflow { rankdir=LR; "classify" [label="classify", shape=box, style=rounded]; "route" [label="route", shape=diamond]; "handle_billing" [label="handle_billing", shape=box, style=rounded]; "handle_general" [label="handle_general", shape=box, style=rounded]; "handle_technical" [label="handle_technical", shape=box, style=rounded]; "classify" -> "route"; "route" -> "handle_billing" [label="state.classification.lower() == 'billing'"]; "route" -> "handle_general" [label="default"]; "route" -> "handle_technical" [label="state.classification.lower() == 'technical'"];}print(graph.to_mermaid())graph TD classify["classify"] route{route} handle_billing["handle_billing"] handle_general["handle_general"] handle_technical["handle_technical"] classify --> route route -->|state.classification.lower() == 'billing'| handle_billing route -->|default| handle_general route -->|state.classification.lower() == 'technical'| handle_technicalimport jsonjson.dumps(graph.to_dict())to_dict() returns nodes, edges, roots, leaves, layers and critical_path, ready for json.dumps.
| Export | What it gives |
|---|---|
to_dot() | Graphviz DOT, left to right. Routers are diamonds, tools trapeziums, subworkflows double octagons, LLM calls rounded boxes, and other steps plain boxes. Render it with dot -Tsvg. |
to_mermaid() | A Mermaid flowchart, without a code fence. Routers are diamonds, tools parallelograms and subworkflows subroutine boxes. Ids keep only letters, digits and _. Steps that would end up with the same id get a numbered one: a-b and a_b become a_b and a_b_2. |
to_pnml() | A Petri net in PNML, as XML: a place for each step and a transition for each edge, named source->target. Edge labels are left out. |
to_dict() | A dictionary of nodes, edges, roots, leaves, layers and the critical path. |
to_networkx() | A networkx.DiGraph, with each node’s type and label and each edge’s label as attributes. |
to_networkx() needs the graph extra:
pip install "agentloom[graph]"Without it, the call raises an ImportError that names the extra.
The visualize command
agentloom visualize draws a workflow as text or as Mermaid without this API. Its Mermaid differs: steps keep the order of the file, ids aren’t changed, and edge labels are cut to 20 characters, so two conditions that start alike can read the same. For DOT, PNML, path analysis or exact labels, use WorkflowGraph. See CLI.
What’s next
Build and run workflows from Python.
Steps, dependencies and the layers they run in.
validateandvisualizefrom the command line.