Workflow graph
WorkflowDsl and WorkflowDag define the structural boundary for portable workflow documents. They parse nodes and edges, preserve unknown extension fields, validate graph structure, and derive a deterministic plan. The host still binds concrete node execution.
Flow does not grant model, database, or tool access from data.type. The host validates and authorizes a node before connecting its semantics to durable steps.
Full documents and bare graphs
A complete document includes version, application information, and workflow.graph. A host that only needs the canvas graph can parse WorkflowDag directly.
One UTF-8 document is limited to 10 MiB. The currently tested DSL version is 0.7.0. An older minor may import with warnings. A newer version or different major requires an explicit host decision.
Minimal graph shape
Every node needs a non-empty id and string data.type. Every edge needs a unique ID and existing source and target nodes.
Connection labels in the Playground
The Playground initially shows the source output-port name in the middle of a connection. For example, a batch node's done port is localized as “All complete”. This text helps people read the graph; it is not the port ID used for routing.
Select a connection and click the pencil beside its label, or double-click the label itself. You can also select the connection and press Enter or F2. Press Enter to save, Escape to discard, or leave the field to save it. Clearing the field restores the port's default name. Custom names are kept in the local draft and in an exported workflow file.
Renaming a label does not change sourceHandle, targetHandle, the execution plan, or runtime semantics. Condition branch names should still be edited through the node's matched_label and otherwise_label fields because those fields are part of the condition node's business copy. A regular connection name is presentation metadata, so validate the graph by its port IDs before publishing.
Derive an execution plan
When several nodes are ready in one scope, the plan sorts by stable ID to remove input-array ordering effects. A plan expresses structural topology only. It does not execute nodes or choose failure, retry, and authorization policy.
An empty canvas can parse and round-trip so an editor may retain drafts. execution_plan() rejects it because there is no executable node.
Structural validation
Planning rejects these conditions.
- Duplicate or empty node and edge IDs
- Missing string
data.type - An edge referencing a missing node
- A self-edge
- A cycle in any scope
- An edge crossing top-level and container scopes
- A
parentIdreferring to a missing or unsupported container - An iteration or loop container without its matching start child
- More than 10,000 nodes or 100,000 edges
These checks make topology deterministic. They do not validate business parameters for a particular executor. The host must apply a schema to every authorized node type.
Playground performance budget
The Playground keeps React Flow's visible-element rendering enabled. Layout input is transferred to a dedicated Worker, where the WebAssembly graph kernel computes coordinates. Configuration validation, DAG compilation, serialization, and element reconciliation stay deterministic and are measured separately.
Run the benchmark from website/ with:
The command measures 100, 500, and 1,000 node fan-out graphs in Node.js. It prints p50, p95, and p99 latency in milliseconds for each operation. The layout row is the JavaScript fallback because Node.js does not provide a browser Worker or the browser WebAssembly loader. Browser layout therefore needs a separate measurement in the target browser.
The following is a checked-in reference run from 2026-08-26 on an arm64 Mac14,9 with macOS 26.3, Node.js 26.7.0, and Vitest 3.2.7. Values are local baselines, not a promise about every device or deployment.
For review, keep the p95 budget at 2 ms for layout fallback, 1 ms for
configuration validation, 12 ms for DAG compilation and serialization, and 2 ms
for reconciliation at 1,000 nodes. A browser release should additionally record
the first Worker layout, scroll responsiveness, and the effect of the minimap.
Large graphs keep only visible React Flow nodes mounted, and long inspector and
library lists use CSS content-visibility with containment so off-screen rows do
not require a layout pass. The minimap pauses automatically above 800 nodes or
4,000 edges and shows a status note instead of competing with canvas scrolling.
Iteration and loop scopes
iteration and loop nodes may own a container scope. Children declare the scope through parentId, and internal edges may connect nodes within that same container only.
The matching start node has three requirements.
- Its
parentIdequals the container ID. - Iteration uses
iteration-start, while loop usesloop-start. - The container has at least one additional executable child.
plan.scope("for-each-item") returns deterministic order inside the container. The container itself remains in the top-level plan.
Semantic digest
execution_digest() produces a stable SHA-256 identity for executable semantics.
Node positions, selection state, viewport, and input-array order do not affect the digest. Node data, semantic edge data, endpoints, and scope do. A host may use it to detect whether two publications only changed canvas layout.
The digest proves matching input semantics only. It does not prove that host node bindings are identical. Executor versions still belong in host release identity or runtime_build_id.
Flow digest format v2 is shared by the Rust engine and the Flow UI. It uses
JavaScript number semantics, UTF-16 object-key ordering, treats edge labels as
presentation-only, rejects unsafe integers, and limits canonical JSON nesting
to 256 levels. Persisted digest values must be migrated explicitly when the
format version changes.
Lossless round trips and extensions
The parser retains unknown top-level, application, workflow, node-presentation, and edge-presentation fields. to_yaml() and to_json() write those fields back.
Preservation does not imply semantic acceptance. Keep an allowlist at the security boundary.
- Parse and enforce document size first.
- Run structural plan validation.
- Apply host schemas to allowed
data.typevalues. - Resolve credential references and reject plaintext secrets in graph documents.
- Record document digest and executor versions at publication time.
Connect graphs to durable execution
The graph compiler returns node order, while the Flow engine persists run decisions. A host commonly maps a node to a stable step ID and stores branch choices or container cursors in step output or continuation input.
If graph semantics change while old runs remain active, create a new workflow definition version or a replay-safe patch path. Do not interpret existing history with a replacement graph.
See examples/workflow_dsl_import.rs for a complete import program.
