One constrained workflow graph, reused to author a draft, read a published definition, and watch a Run. Build mode edits the draft; run mode overlays Run and span state on the frozen snapshot.
1 min read Updated Aug 31, 2026
Workflow visualizer
A workflow must look the same when a person builds it, reads it, and watches it run.
The visualizer is one graph component over the workflow definition. Build mode edits draft_config. Run mode overlays execution state on the snapshot the Run froze. There is no second visualization model for runtime.
One picture of a workflow, and the three places a person meets it. The middle band is authoring: only the boxes the runtime can execute may be placed, and a draft cannot run until it is published. The third band is a Run, which keeps its own frozen copy of the workflow, so an old Run always shows what really ran. The strip below it is the whole vocabulary a box can carry while the Run is on.
Workflow definition
│
▼
Workflow graph
│
┌────┼───────────┐
▼ ▼ ▼
build view run
draft published live / historical
A node means the same thing in all three places. A Company Research Agent node is editable in the builder, read-only on the definition page, and status-bearing in a Run.
The platform already holds every record the graph needs. A workflow is a definition, and a definition is data with a small deterministic vocabulary. A Run freezes an immutable snapshot of published_config at start. Execution produces child Runs and spans. The visualizer combines those records and adds none.
TEXT
build mode
draft_config ──> WorkflowGraph ──> DefinitionService ──> validate ──> publish
run mode
Run snapshot + Run + child Runs + spans ──> WorkflowGraph
No execution logic belongs in the visualizer. Inngest stays the durable orchestrator, the worker executes the workflow, and the UI renders product state only. See runtime definitions and runtime execution.
The visual builder places the eight node types the runtime executes, and nothing else. Two of them are containers. The runtime owns this table; the visualizer renders it.
Node
Kind
What the graph draws
sequence
container
The ordered relationship between children
parallel
container
A fixed set of children that run at the same time
tool
step
One deterministic tool call
agent
step
One agent as a child Run
subworkflow
step
One published workflow as a child Run
branch
step
One child chosen by the shared ConditionEvaluator, with a declared default
wait
step
A hold for an event or a delay
approval
step
A hold for a person
wait and approval are two nodes, not one. A wait resumes on an event or a timer. An approval resolves an approval row, and the human review inbox is the global queue for it.
This is not an unrestricted DAG canvas. No code nodes, no loops, no dynamic graph rewriting at run time, no user-defined node types, and no dynamic fan-out node. Fan-out stays inside one tool step or one agent step, which reports one result, so the shape stays checkable at publish time.
Selecting a node opens a side panel. Configuration never sits on the canvas.
TEXT
Enrich Company
Type agent
Agent Company Research Agent
Input company = find_companies.company
Output company_intelligence
The editor writes draft_config through PATCH /api/v1/agentic/definitions/{id}/draft, and sends expected_updated_at on every write. Two people editing one draft is normal, so a stale write is refused rather than merged. The editor never writes execution records and never bypasses validation.
The builder may give immediate client-side guidance. Publishability is decided by the deterministic definition validator, and by nothing in the frontend.
The validator holds:
node ids are unique;
every referenced agent, tool, skill and workflow exists and is visible to the organization;
required inputs are connected, and every input and output reference resolves;
a branch declares a default;
branch conditions use the supported deterministic operators;
subworkflow nesting stays inside the platform cap;
the scopes the referenced capabilities need are grantable.
A workflow written in chat and a workflow drawn by hand run the same validator, because both are one draft_config.
Run mode renders the immutable snapshot the Run froze at start. It does not render the current published definition.
TEXT
current published_config ────┐
│ they differ after an edit
Run snapshot ────────────────┘
│
▼
run visualizer
The page states plainly when the current definition differs from the snapshot, and links to the current definition. A historical Run must render correctly without reading the current definition at all.
Live state comes from the Run stream. Historical state is rebuilt from the Run row and its spans, with no event log. The UI never reads Inngest execution state.
An approval node shows the decision where it interrupted the Run.
TEXT
✓ Qualify Prospects
│
▼
⚠ Human Review
waiting for approval
│
▼
○ Email Sequence
Selecting it shows the proposed action, the rule that asked for a person, who requested it, the exact target or content, the expiry, and the action that resolves it. The approval inbox stays the global queue. The visualizer renders the same approval row, and never a second one.
A person drills into the child Run instead of expanding the nested graph inline. Nesting is capped at publish time, and the parent graph stays readable.
Definition first. Node identity and labels come from the definition. Runtime only adds an overlay.
Stable identity. A node keeps one id across build, view and run.
Configuration off-canvas. The graph carries shape. The panel carries detail.
Progressive disclosure. Show business status first. Tool, LLM and span detail is one click deeper.
No infrastructure vocabulary. Never show an Inngest function id, an attempt count or a worker detail in the product UI.
No silent definition drift. A historical Run always renders its own snapshot.
Mobile degrades vertically. The same definition stays understandable without a wide canvas.
The visualizer never changes a Run in flight. Editing edits a draft for future Runs. Cancel stays a Run control, and disable stops a new Run tree only.
GET /api/v1/agentic/definitions/{id}
PATCH /api/v1/agentic/definitions/{id}/draft expected_updated_at
POST /api/v1/agentic/definitions/{id}/validate
POST /api/v1/agentic/definitions/{id}/publish expected_updated_at
POST /api/v1/agentic/definitions/{id}/fork
GET /api/v1/agentic/runs/{id}
GET /api/v1/agentic/runs/{id}/spans?cursor=
GET /api/v1/agentic/runs?parent_run_id={id}&cursor=
GET /api/v1/agentic/runs/{id}/stream
There is no run tree route, and the visualizer must not ask for one. Run detail embeds no child list and carries child_count only, because a parallel node's widest case is five hundred children. The graph reads children through the paged list route, one node at a time, as it needs them.
definition (or Run snapshot)
nodes[]
id
type
label
config
input bindings
the deterministic parent/child relationships
RunExecutionOverlay
run_id
node_id ->
status
started_at
ended_at
progress
child_run_id?
error_summary?
result_summary?
The overlay is a projection of the Run and its spans. Do not persist a second graph for visualization, and do not add a durable event table beside the spans. Layout metadata may live with the definition if manual positioning earns its place later. Execution state stays in Runs and spans.
A person asks for a prospect qualification workflow in chat. The UI opens that same draft in the graph. The person swaps the qualification agent, adds an approval node, validates and publishes.
2
A person builds it by hand
An admin starts from a blank workflow or forks a platform template, places nodes from the constrained set, connects inputs, validates and publishes. The result is the same definition shape a conversation produces.
3
A person watches a live Run
The Run starts from chat or a trigger. The Run Explorer opens the graph from the snapshot. Nodes move pending -> running -> succeeded, and counters update on the stream.
4
A Run waits for a person
The approval node reads waiting, and later nodes stay pending. The person approves it in the node, and the same graph resumes when the runtime resumes the Run.
5
The definition changed after the Run
A person republished the workflow. An older Run still renders its own snapshot. The page says the current definition differs, and links to it.
6
The Run is older than the span window
Spans are kept 90 days and Runs 13 months, so a Run in between renders partly blank: an agent or subworkflow node still has a child Run and keeps its status, while a tool, wait, branch or approval node loses the span it read and has none. Say so on the graph. Left unlabelled it reads as a workflow whose steps never ran, which is the one thing it does not mean.
6
A failure is investigated
The enrichment node fails. The graph shows the failed node and a short error. Clicking it opens the execution detail, then the tool span that holds the failed web_search call.
This replaces the V1 rule "no visual DAG builder" with a narrower one:
Build a constrained visual editor and runtime visualizer over the same definition graph. Do not build a general-purpose DAG platform.
The exclusion that stays is arbitrary workflow code, loops and dynamic fan-out. The runtime vocabulary does not grow. What grows is how a person sees it.