v2.4.0New colors: the look of this website

Graphs are languages.
Edit them like code.

graphedit is a fast, native editor for state machines, Petri nets, automata and any graph language you define. Plugins bring the syntax, language servers check your models as you draw, the debugger steps through them, and AI agents edit right alongside you.

  • Linux
  • Windows
  • C++20 & Qt 6
  • MIT licensed
The graphedit editor: the files of a project, a labelled transition system and a Petri net side by side, and the diagnostics of language servers
pipeline.jsonprotocol.json
bootloadissuenextstopresettrapidlefetchdecodeexecutehaltfault!⚠ deadlock: no way out of 'fault'
deadlock-ls: 1 warningdebugging · stopped in idledrag the states

The editor

Made for people who think in graphs

Everything boils down to vertices and edges. graphedit makes drawing, checking and reorganizing them feel as quick as editing text.

Every command, a few keys away

The command palette (Ctrl+Shift+P) finds every command, panel and model of the project with a fuzzy search, shortcuts included.

Undo is a tree

Undo, then edit again: the undone changes stay on a branch that you can always go back to.

A canvas that keeps up

Create, connect, move, box select, copy and paste, with grid snapping and smooth zooming, even on large networks.

Problems as you draw

Language servers analyze models while you edit and highlight what is wrong right on the canvas.

Your language, your rules

Labelled transition systems, Petri nets and simple graphs out of the box. Plugins add syntaxes with their own types, rules and looks.

Split, drag, dock

Tabs in groups next to and below each other, two views of one model, and tabs dragged out into windows of their own.

Plays well with others

Import from and export to TAPAAL, open graphedit 1.x projects, and exchange H-UPPAAL projects with the NTTA plugin.

Extend

Bring your own language

graphedit knows what all graphs have in common. Everything specific to a language comes from plugins and language servers, the same way your code editor learns a new programming language.

  • Syntaxes

    Element types, their properties and the rules for connecting them.

  • Language servers

    Any program, in any language, that speaks JSON-RPC: diagnostics and debugging.

  • Views and panels

    How elements are drawn, and side panels of your own, in a graphical plugin.

  • Importers and exporters

    Bring models in from other tools and send them back out.

deadlock_ls.py
#!/usr/bin/env python3
# A language server: warns about states that cannot be left.
import json, sys

def read():
    headers = {}
    while line := sys.stdin.buffer.readline().strip():
        name, _, value = line.partition(b":")
        headers[name.strip().lower()] = value.strip()
    length = int(headers.get(b"content-length", 0))
    return json.loads(sys.stdin.buffer.read(length)) if length else None

def send(message):
    body = json.dumps({"jsonrpc": "2.0", **message}).encode()
    sys.stdout.buffer.write(b"Content-Length: %d\r\n\r\n" % len(body))
    sys.stdout.buffer.write(body)
    sys.stdout.buffer.flush()

def diagnose(buffer):
    graph = buffer["graph"]
    sources = {edge["source"] for edge in graph["edges"].values()}
    return [{"lintIdentifier": "DL-001", "severity": "warning",
             "title": "Deadlock",
             "message": "There is no way out of this state",
             "affectedElements": [vertex]}
            for vertex in graph["vertices"] if vertex not in sources]

while message := read():
    method, params = message.get("method"), message.get("params")
    if method == "initialize":
        send({"id": message["id"], "result": {
            "serverInfo": {"name": "deadlock-ls", "version": "1.0"},
            "language": "LTS",
            "capabilities": {"sync": "full", "diagnostics": True}}})
    elif method in ("graph/didOpen", "graph/didChange"):
        buffer = params["buffer"]
        send({"method": "graph/publishDiagnostics", "params": {
            "key": buffer["key"], "diagnostics": diagnose(buffer)}})
    elif method == "shutdown":
        send({"id": message["id"], "result": None})
    elif method == "exit":
        break

Share plugins through a plugin database: the plugins panel installs and updates them in a click.

Debug

Watch your model run

Step through models on the canvas, stop at breakpoints and rewind. The runtime comes from the language server, so one debugger works for every language, through a protocol modelled after DAP.

The editor stopped at a breakpoint while debugging a labelled transition system: the active state has a green ring, the steps that can be taken are dashed, and the debug panel lists them
  • Breakpoints on vertices and edges, with conditions and hit counts
  • Inspect and change variables while the run is stopped
  • Go back to any earlier state of the run
  • Attach to models running elsewhere, through the language server
Start debugging
F6
Continue or pause
F8
Step
F10
Step back
ShiftF10
Toggle breakpoint
F9
How debugging works

AI agents

An editor that agents can use too

The agent api offers everything the editor can do as JSON-RPC methods, for AI agents and your own scripts.

  • A chat next to the canvas

    The agent panel talks to Claude Code or GitHub Copilot CLI about the open project, and you watch every change land.

  • Same rules, same undo

    Every call is checked against the syntax and recorded as one undoable change, just like your own edits.

  • Sees what you see

    Agents read the diagnostics, point you to elements on the canvas and take screenshots of the editor.

The agent panel next to the canvas: asked to fix a dead end, the agent added a state and two transitions, and lists the tools it used

Headless when you need it

graphedit-cli calls the same methods from scripts and CI, serves them over JSON-RPC, or as a Model Context Protocol server. When the project is open in the editor, calls go there, so you see them.

The agent api
~/projects/pipeline
# give Claude Code the tools of grapheditclaude mcp add graphedit -- graphedit-cli --project . mcp# or call them yourselfgraphedit-cli call vertex.add model=pipeline.json 'properties:={"name": "retry"}'{"id": "7f3c9e1a-…", "vertex": {"type": "state", "properties": {"name": "retry"}}}graphedit-cli validatepipeline.json: warning: There is no way out of this state [DL-001]1 model(s) checked: 0 error(s), 1 warning(s)

Version 2.4.0

Get graphedit

Free and open source. Pick your system, or build it yourself.

Build from source

C++20, CMake 3.25+, Ninja and Qt 6.5+. The release workflow builds, tests and packages it.

git clone https://git.gtz.dk/gtz-agent/graphedit-qt.git && cd graphedit-qt && cmake --workflow --preset release

What is new in 2.4.0All releasesAbout the packages