Skip to main content

The Background Execution Reconciler

The reconciler is a background service running inside the API process that periodically inspects running executions and reconciles platform state with the workflow engine's actual state. It's the platform's answer to an unavoidable reality: callbacks can be missed, engines can crash, and a run can otherwise be left stranded as running forever. It's also the clearest expression of the platform's most important operating rule.

The Truthfulness Invariant

The reconciler exists to uphold one invariant: the platform must never mark an execution terminal while the engine may still be running. A platform-terminal state over a still-running engine is a critical lie; a stale running state after the engine exited is wrong but operationally safer. Every decision it makes is biased toward that safer error.

What it reconciles, and how it decides

On each tick it scans running executions and classifies each by three questions — does it have an external_execution_id, has it breached a limit, and has the engine finished? — then acts deterministically:

External ID?Limit breached?Engine finished?Action
yesnotrueterminate locally (failed_transient), no remote stop
yesyesfalseterminate with remote stop
yesyestrueterminate locally (failed_transient)
noanyunknownleave running; log for operator intervention

Two rules keep it honest. The engine-finished condition is stoppedAt != null AND status is terminalboth, because n8n's Wait nodes set stoppedAt while merely paused, and a naive check would kill waiting runs. And it only ever produces failed_transient, never completed: engine status = success does not authorise the platform to claim completion, because completion needs the result payload that only /complete delivers. Without an external ID it can neither verify nor stop the engine, so it leaves the run alone and flags it for a human.

Single-runner by design

The reconciler must not double-process when several API instances share a database, so each tick acquires a PostgreSQL advisory lock; if the lock is busy, the tick is skipped. The loop is strictly sequential — tick, sleep, tick — with no overlap, and it releases the lock in a finally (destroying the client if unlock fails, which ends the session and frees the lock anyway). Normal endpoints are never blocked by this lock. It's always enabled, runs on a configurable interval (default 120s), and emits telemetry under a reconciler domain — the modest, robust design you'd want from a component whose whole job is to be the thing that's still correct when everything else has already gone wrong.