A graph is a document.So is every run of it.
The canvas is where the work gets built, and the run monitor is the same canvas with the truth painted on it. What you save is a definition you can export and diff; what you get back is a run you can open a month later and still see what each node produced.


The working surface, control by control
The palette
330 nodes, by tool or by categorySearch by name, browse by tool, drag onto the canvas or click to place. Custom nodes built in this deployment sit in the same palette as the built-ins, because they are in the same catalog — there is no second-class shelf for the node you needed and did not have.
The inspector
typed parameters, with their helpEach parameter renders as the control its declaration asks for — a select for an enum, a code editor for SQL or JavaScript, a picker for a credential or another workflow, a masked box with a reveal for a key. Fields that are inert for the current configuration are hidden rather than left to mislead.
Expressions everywhere
{{ }} against the payload and the run{{$input.items[0].name}}reads the incoming payload,{{$merged[1]}}a branch of a join,{{$settings.region}}the run's context,{{$counter}}the loop iteration. A value that is exactly one expression keeps its type; one embedded in text interpolates.Handles that mean something
true · false · error · namedA branch node draws a true and a false output; any node can grow an error output that catches its failure; a switch invents one handle per case. An input can fire per arriving branch or wait for all of them — which is the difference between a fan-in and a join.
Arrange
dagre, plus the rules a workflow needsOne button lays the graph out: columns by depth, the happy path kept straight, error branches stacked underneath it, entry and terminal nodes pinned to the outer columns. Shift-click pins your current selection and lays out the rest around it.
Nodes that draw
chart · table · gauge · map · feedA node can be a widget: its body becomes the chart or the table it published, sized how you left it. That is what makes a run monitor legible at a glance instead of being a log with a graph above it.
The canvas, with the truth painted on it
A run is watched on the same graph you built. State is drawn at the corners of each node — a spinner while it works, a check with the repetition count beside it, a red mark carrying the failure message — because the body of the node belongs to the author and a status badge that pushes a chart around is one that made the graph less readable exactly when it mattered.


- Live over a websocket, and polled as well, so a dropped connection degrades to the poll interval
- Pause, step, stop — or fire a single node by hand with a payload you type
- Widgets replay when you open a finished run: the log carries them, not just the socket
- A node that failed says so where it failed, with the message and the attempt count
The run monitor and the editor are the same component with a different question in front of it. That is deliberate: an operator watching a failure and an author fixing it should be looking at the same picture.
Any payload, full page
The glance you take while a run is going is not the look you take when the glance raised a question. Any node's output opens full-page — sortable, filterable by column, searchable, chartable, profileable, exportable to CSV — and it works on the plain sql:query node nobody decorated, because it reads the task's own result when there is no published widget.
- Rows, chart, statistics and raw JSON of the same payload, one click apart
- Column picking, per-column filters, paging and search over everything
- Save the configuration — the sort, the filters, the hidden columns, the chart — onto a page
- What is saved is the configuration, never the data: the new page is as fresh as its graph's last run




The node you need, without a fork
Some of what a deployment automates is specific to that deployment: an internal REST service, a GraphQL endpoint behind your VPN, one SQL statement everybody keeps rewriting. Building those into a product would be wrong, and asking for a pull request to get them is worse. So the app builds them: describe the call, name its parameters, test it against the real endpoint, and it lands in the catalog.
- HTTP, GraphQL, SQL, a script, or another graph wrapped as one step
- Parameters you declare, with the same typed controls the built-in nodes get
- Test it before saving, against the credential it will use in production
- The palette, the reference page, the MCP tool list and the assistant pick it up immediately


Every graph, its project, and its size
A workflow is one JSON document: its nodes, their parameters, the edges between them and the layout. It exports, it imports, it diffs in a pull request — and it belongs to exactly one project, so what a deployment automates is a list you can read rather than an archaeology exercise.
- Search by name or tag, filter by project, see the node count before you open it
- Import a graph somebody exported; clone one to try a change without touching the original
- A draft is allowed to be incomplete — the header tells you whether it is runnable, and why not
- Deleting one refuses while it still has active runs, rather than orphaning them
A real graph is bigger than a diagram, and stays readable
Twenty-three nodes: a fan-out, a sub-workflow call per item, a gather, two filtered branches, an approval gate above the delegated limit, and the payment file written only after the release. The arrange button laid this out; the shape is the documentation.


Build one with us
Bring a process you run by hand. We will build it on the canvas, run it, break a node on purpose, and show you what the graph does about it.