Appearance
Workflow Engine
Workflows let an instance react to events with multi-step automations: evaluate a score, branch, wait, call a plugin or an external API, and stop. Workflows in the User Guide explains how to build one in the visual editor. This page describes how the engine executes them.
Execution model
A workflow is an event subscription (which events start it, with optional filters) plus a directed acyclic graph of nodes and edges.
- Execution edges define order: a node runs after its predecessors. An edge may carry a JSON Logic condition; the edge is taken only when the condition is true, and an edge without a condition is always taken.
- Data connections let a node read another node's output. They do not affect ordering beyond the execution edges.
- The graph is validated when saved: cycles are rejected, and every node must be reachable from a start node.
A run is created each time a matching event arrives (or when a run is started manually or in simulation). The engine sorts the graph topologically and visits nodes in that order:
- Evaluate the incoming edge conditions. If the node has incoming conditional edges and none passes, the node is marked skipped and the traversal continues.
- Otherwise mark the node running, resolve its inputs (see below) and execute it by type.
- Record the resolved input and the output on the run node, mark it completed, and make the output available to later nodes.
- On error, mark the node failed. By default the whole run fails immediately; a node can be configured to continue on error, in which case the failure is logged and the traversal continues.
- An End node stops the traversal; the run is marked completed even if other nodes were never visited.
Subscriptions are isolated from each other: a run failing for one subscription never blocks runs of other subscriptions for the same event.
Node types
| Node | What it does | Input | Output |
|---|---|---|---|
| Action | Invokes an action of a configured plugin instance. | An input mapping resolved against the run context | Whatever the plugin action returns |
| Compute | Evaluates a JSON Logic expression. | The expression | The result (a scalar is wrapped as result) |
| Switch | Branching point; the outgoing edges carry the conditions. Mode first true or all true. | none | none |
| Join | Waits for several branches before continuing: all, any, or n of m, with an optional timeout. | none | none |
| Delay | Pauses the run for a fixed duration or until a computed timestamp. | Duration or a template resolving to a timestamp | none |
| HTTP | Performs an HTTP request without a plugin: method, URL, headers, query, body, timeout. | Request fields, each may be dynamic | Status code, response headers and parsed body |
| Mapper | Builds a JSON document from context data, for reuse by later nodes. | A mapping template | The resolved document |
| End | Terminates the run. | none | none |
HTTP nodes return non-success status codes as normal output so the workflow can branch on them; only connection failures fail the node. In simulation, HTTP requests, plugin actions and delays are not performed; the node records the fully resolved request instead.
Run context and data resolution
Every expression and mapping in a run is evaluated against a context with two namespaces:
event: the triggering event (identifier, type, source, subject, time and data). Submission events are enriched so that answers, scores and questionnaire metadata are addressable by path.nodes: the outputs of nodes that already completed, keyed by the node's key.
Dynamic values can be written in four ways, resolved in this order:
| Syntax | Reads from | Typical use |
|---|---|---|
{"var": "event.data.score"} | Run context | Replace a whole value with a context value, of any type |
| Any JSON Logic expression | Run context | Computed values: arithmetic, string building, array transforms |
Double-brace var: form (see below) | Run context | Embed a context value inside a longer string |
$secret:key, $var:key, double-brace secret: form | Vault | Credentials and stored variables, whole-value or embedded in a string |
The double-brace forms embed a value inside a longer string:
text
Hello {{var:event.data.name}}, your token is {{secret:api_token}}A mapping whose entire value is a single expression is passed through as-is, which is how a Mapper's whole output is forwarded to an Action, or how one of two payloads is chosen with an if. Note that the double-brace var: form reads the run context while $var:… reads the vault; they are different sources. Vault references are resolved at the last moment and never appear in run records. See Configuration & Vault.
Conditions with JSON Logic
Edge conditions, Compute nodes, subscription filters and mapping values use JSON Logic. Operator families available:
| Family | Examples |
|---|---|
| Comparison | equals, not equals, greater/less than, between |
| Boolean | and, or, not, if / else chains |
| Arithmetic | add, subtract, multiply, divide, modulo, min, max, rounding |
| String | concatenate, substring, case changes, contains, split |
| Array | map, filter, reduce, some, all, merge, length, in |
| Date and time | now, parse, format, add or subtract durations, compare |
| Utility | coalesce, type checks, missing-key detection |
A condition that routes a submission with a high score:
json
{ ">": [{ "var": "event.data.scores.total" }, 15] }The editor offers a path picker for the context and an evaluator that runs an expression against a sample or a recorded run, so conditions can be tested before enabling the workflow.
Run lifecycle and statuses
| Run status | Meaning |
|---|---|
pending | Created, not started. |
running | Nodes are executing. |
completed | All reachable nodes finished, or an End node was reached. |
failed | A node failed and the workflow was not configured to continue. |
| Node status | Meaning |
|---|---|
pending | Waiting for predecessors. |
running | Executing. |
completed | Finished; input and output recorded. |
failed | Raised an error; the error is recorded. |
skipped | All incoming edge conditions were false. |
Every run node stores the input it received before execution, so a failed node can be diagnosed from its recorded input. The web app shows runs and their nodes with these details; the same data is available through the API.
Simulation
A workflow can be simulated with a sample event without creating run records. Every node is resolved and evaluated exactly as in a real run, but side effects (plugin actions, HTTP requests, delays) are replaced by a description of what would have happened. Simulation returns the context snapshot after each node, which is the fastest way to debug mappings and conditions.
Error handling, retries and limits
- Plugin actions signal three classes of failure: a user error (bad input, fails the node without retry), a permission error (the plugin's identity lacks a scope, fails the node) and a transient error (the node is retried with backoff). See Plugin Runtime.
- HTTP nodes have a per-request timeout; connection errors fail the node.
- A Join with n of m fails the run if fewer branches than required complete.
- Runs execute in the background after the event is drained; long delays keep the run open, so prefer scheduling on the platform side (enrollment schedules) for waits measured in days.
Good practices
- Keep one responsibility per workflow and chain through events rather than building very large graphs.
- Give nodes stable, descriptive keys; they are how later nodes and run records refer to them.
- Put credentials in the vault and reference them; never paste secrets into mappings.
- Test with simulation, then enable the subscription with a narrow filter, then widen it.