Execution & Triggering
What starts a pipeline
| Trigger | Node | Fires when |
|---|---|---|
| Schedule | Inject | An interval elapses or a cron expression matches |
| Startup | Inject | The pipeline starts |
| Tag change | Industrial I/O (subscribe) | A subscribed tag changes |
| Alarm event | Alarm (subscribe) | An alarm is raised, cleared or acknowledged |
| HTTP request | REST API (ingress) | A matching request arrives |
| Another pipeline | Link (ingress) | A linked egress node sends |
| Manual | — | The Run button |
A pipeline must be running for its triggers to be armed. Manual execution runs the graph once regardless.
Message flow
- A node runs when a message arrives on its input.
- Multiple outbound wires fan out — each downstream branch receives the message. They run independently; one failing does not stop the others.
- Multiple inbound wires mean the node runs once per arriving message, not once for all of them. Use Join when you need to wait for several branches.
- A node returning nothing (
None/null) ends that branch — a clean way to filter.
Execution context
Each execution carries an identity used for logging, correlation and lifetime:
| Field | Meaning |
|---|---|
| Execution ID | One traversal of the graph |
| Pipeline ID / project | Where it ran |
| Trigger | What started it |
| Start / end time, status | Outcome |
Per-node results are recorded against the execution, which is what makes a failed run reconstructable after the fact.
Timeouts
| Scope | Setting |
|---|---|
| Python node | timeout in node configuration, default 60 s |
| REST node | timeout, default 30 s |
| Database transaction | timeoutMs, server-side auto-rollback |
| REST ingress route | Route timeout, default 30 s |
A node that exceeds its timeout fails, which is a failure like any other: the branch stops unless the node's error output is wired, and a Catch reports it either way. Set timeouts shorter than the caller's patience — an HTTP client that gave up long ago does not benefit from a node still running.
Errors
By default a failing node stops its branch: the error is recorded against the execution, the other branches carry on, and the pipeline stays running for the next trigger.
That default is the right one for a fault nobody planned for. For the ones you did plan for, there are two features, and they answer different questions.
| Catch node | Error output port | |
|---|---|---|
| Answers | Tell somebody this broke | Carry on without it |
| Shape | A separate node, no input, dispatched after a failure | A second socket on the node that failed |
| Scope | Every node in the pipeline, or a chosen few | That one node |
| The failed branch | Cannot be resumed — the handler is a new branch | Continues, down the error wire |
| Enabled | By adding the node | Per node, on the Resilience (Retry) tab |
They compose. A pipeline can report every failure through one Catch and recover from a specific one in flow.
The error output port New in 1.0.4
Open a node's property panel, go to Resilience (Retry), and switch Error Output on. A second socket appears on the node, drawn in red. It fires when that node has finally failed — after its retries are exhausted, not on each attempt.
┌─ out ──────────────────────────┐
Inject → fetch-price ──┤ ├→ compute → write-db
└─ error → Python (cached value) ┘
Wiring the error branch back into compute runs compute once, with whichever edge delivered —
the same convergence rule that governs any other node with several inbound wires. That is the whole
point of the port: recovery without duplicating the rest of the pipeline underneath a handler.
The message on the error wire is the same envelope a Catch emits —
error, source and the input that caused the failure.
Four behaviours worth knowing:
- The node reports
WARNING, notERROR. It draws amber on the canvas and stays in the run log, because a handled failure is still a failure — but it is no longer an outage, and the branch it feeds is allowed to run. - A routed error does not also fire a Catch. You wired the failure somewhere, so it went there.
- An error output switched on but left unwired fails normally. Routing an error into empty space would swallow it, which is worse than the behaviour it replaces.
- Switch has no error output. A router publishes its own ports; the two are not combined.
What a failure looks like in the log
| Level | Means |
|---|---|
ERROR | The node failed and nothing handled it. The branch stopped |
WARN 1.0.4 | Something went wrong and was handled — a node whose failure left through its error output, or another recoverable fault |
RETRYING | An attempt failed and another one is coming |
WARN is filterable in the system log toolbar, which is the point of it: a handled fault is neither
routine chatter nor an outage, and it used to be recorded as one or the other.
Writes
Failures on the way out to a device or a database are covered separately by store-and-forward: transport failures buffer and retry, data errors quarantine.
Debugging
1. Attach a Debug node. The fastest way to see what a node actually produced, in the Debug panel.
2. Trigger a single node.
Use Trigger node on the node itself (needs Run pipelines). It runs one node with its current input, without running the whole graph — the pipeline equivalent of stepping.
3. Read the execution log.
Executions, individual executions and per-node results all require View execution logs.
4. Watch node status on the canvas. Nodes show live state as messages pass through, pushed over the telemetry bus.
5. Lint Python before running. The editor lints as you type, so syntax errors surface while typing rather than at 03:00.
Performance
| Symptom | Cause | Fix |
|---|---|---|
| Pipeline lags behind its trigger | The graph takes longer than the interval | Lengthen the interval, or split the work across pipelines |
| High CPU on the Python worker | Per-message Python for high-rate data | Filter with RBE first; move set-based work into SQL |
| Database saturated | One write per message | Batch with Join, or aggregate before writing |
| Memory growth | Very large arrays held in memory | Use Array Iterator to stream instead of materialising |
| Slow tag reads | A loop of single reads | Use one multi-tag read (readAll) |
Two rules cover most cases: filter early (drop what you do not need at the first opportunity) and batch late (combine before the expensive downstream step).
Concurrency
- Executions of the same pipeline can overlap if the trigger fires faster than the graph completes. Design nodes to be re-entrant, or lengthen the interval.
- Batch Transform processes items in parallel; ordering across items is not guaranteed.
- Variables are shared across pipelines. Use
system.vars.incrfor counters — it is atomic; read-modify-write is not. → Variables
Starting and stopping
Starting and stopping a pipeline — individually or all of them in the project — requires Start and stop pipelines. Executing once requires Run pipelines.
Disabling a project stops its pipelines. Disabling a pipeline keeps it out of "start all" — the right way to shelve one without deleting it.