Workflow Engine
Executing diagrams as workflows: node types, variables, retries, and execution control
Every diagram you draw with Beep.Skia can also be executed. The workflow engine walks the connected components in dependency order and runs each one as an automation node, passing data along the connections. This is what turns a diagram from documentation into a runnable process.
Table of Contents
Execution Model
Components that implement IAutomationNode are the units of work. The engine sorts them topologically
from the diagram's connections, so a node runs only after everything feeding into it has completed. Each execution gets
its own ExecutionContext carrying input data, variables, and the execution id.
| Concept | Type | Purpose |
|---|---|---|
| Engine | WorkflowEngine (implements IWorkflowEngine) | Loads workflow definitions and executes them |
| Definition | WorkflowDefinition | Nodes, connections, variables, retry policies, priority |
| Node | IAutomationNode | Initialize / validate / execute, with status and error events |
| Context | ExecutionContext | Per-execution data, variables, and node results |
| Result | WorkflowResult | Final status, per-node results, output data, errors |
Built-in Node Types
The engine registers these by default, so a diagram built from them runs without any wiring:
| Node | Role |
|---|---|
ManualTriggerNode | Starts a run on demand (button text, shortcut, cooldown, auto-reset) |
TimerTriggerNode | Starts a run on a schedule or interval |
DataInputNode | Feeds a dataset into the pipeline |
DataTransformNode | Map / filter / aggregate / custom transforms with field mappings |
ConditionalNode | Branches on an expression, with true/false outputs |
DataSourceAutomationNode | Reads or writes through a registered data source |
HttpRequestNode | Calls an HTTP endpoint (method, URL, headers, body) |
Custom nodes are registered by type name, which is also how diagram-sourced workflows resolve their components:
Registering a custom node
var engine = new WorkflowEngine();
engine.RegisterNodeType("MyCustomNode", () => new MyCustomNode());
engine.RegisterNodeType(typeof(MyCustomNode).FullName, () => new MyCustomNode());
Inputs and Variables
Workflow variables are seeded into every node's context before execution, and input data supplied at run time is available to all nodes. This is how the same workflow behaves differently per invocation — an environment name, a batch size, a date range.
Variables and input data
var workflow = new WorkflowDefinition("nightly", "Nightly Sync");
workflow.Variables.Add(new WorkflowVariable("environment", DataType.String)
{
DefaultValue = "production",
IsRequired = true
});
await engine.LoadWorkflowAsync(workflow);
var result = await engine.ExecuteWorkflowAsync("nightly", new Dictionary<string, object>
{
["batchSize"] = 500
});
Retries and Timeouts
Reliability is configured per node, not globally, so a flaky HTTP call can retry while a deterministic transform fails fast:
Retry policies
nodeDef.RetryPolicy = RetryPolicy.Simple(maxRetries: 3, delaySeconds: 2);
nodeDef.RetryPolicy = RetryPolicy.ExponentialBackoff(maxRetries: 5, initialDelaySeconds: 1, maxDelaySeconds: 30);
nodeDef.ExecutionTimeout = TimeSpan.FromSeconds(30);
A node that keeps failing marks the execution Failed and the error is recorded against the node id, so
you can see exactly where a run broke.
Pause, Resume, Cancel
Long-running workflows can be controlled while they execute. Pausing takes effect at the next node boundary, and cancelling propagates a cancellation token into the running node:
Controlling a running execution
await engine.PauseExecutionAsync(executionId); // holds before the next node
await engine.ResumeExecutionAsync(executionId);
await engine.CancelExecutionAsync(executionId); // cooperative cancellation
Results and Events
The engine raises events for the whole lifecycle — WorkflowStarted, WorkflowCompleted,
WorkflowFailed, WorkflowPaused, WorkflowResumed, WorkflowCancelled —
and returns a WorkflowResult with per-node results and the accumulated output data. Execution history is
kept per workflow and is safe to query from other threads while runs are in flight.
Running a Diagram
DrawingManager.ToWorkflowDefinition() converts the current canvas into a workflow: automation
components become nodes (with their configuration), and connection lines become the dependency graph.
Diagram to execution
var manager = new DrawingManager();
manager.AddComponent(new ManualTriggerNode { X = 40, Y = 40, Name = "Start" });
manager.AddComponent(new DataTransformNode { X = 40, Y = 180, Name = "Transform" });
manager.ConnectComponents(manager.GetComponents()[0], manager.GetComponents()[1]);
var workflow = manager.ToWorkflowDefinition("Saved Diagram");
await engine.LoadWorkflowAsync(workflow);
var result = await engine.ExecuteWorkflowAsync(workflow.Id);
Related
- Triggers — start workflows on a schedule, an event, or a file change
- Credential Vault & Connections — keep secrets out of diagrams
- Server Execution SKU — run workflows as queued jobs over HTTP
- Automation Data Sources — connect nodes to real data