castomFLOW

Runs you can trust

Nobody notices an automation.Until it sends something twice.

Then everybody notices. The four mechanisms on this page exist because the failures of this kind of software are always the same four failures, and they are all avoidable by design rather than by care.

Simulate before you publish

Push a real record through a workflow that is switched off, and read back exactly what would have happened: the path taken, every value at every step, the wording of every message, and the request that would have gone to each external system.

Nothing leaves the building. No e-mail is sent, no invoice raised, no charge attempted, no row written. External calls are recorded as intentions, with the payload that would have been sent, so you can see whether the mapping is right before it becomes somebody's problem.

Simulations are how you answer the question that stops people switching an automation on: what will this actually do?

Simulation · order 88 214Nothing sent
  • 1Validate addressMatched. Would have written 6 fields.
  • ?ConditionOrder over 1 000 zł? No — 289,00 zł. Short path taken.
  • 3InvoiceWould POST to the invoicing system. Payload shown in full.
  • !Caught hereVAT rate mapped from the wrong field. Found in a simulation rather than in an accountant's inbox.

Sent: nothingCharged: nothing

A run walks the version it entered on

Publishing a change to a workflow does not touch the runs already inside it. Each run is pinned to the version it started on and finishes that version, however long it takes and however much you rewrite in the meantime.

So you can restructure a workflow while ten thousand people are halfway through it, and every one of them completes the flow they were promised. The alternative — the design where the whole downstream path is materialised at entry — is the reason editing a live automation feels dangerous in most tools. Here it is unremarkable.

New runs get the new version from the moment you publish. Which version a run is on is written on the run, so a question about behaviour in March has an answer in September.

Editing while livev4 → v5
  • 1Before2 918 runs in flight, all on v4. Some in a two-day wait.
  • 2Publish v5Two steps reordered, one message rewritten.
  • 3AfterThose 2 918 finish v4. Everything starting from 11:40 runs v5.
  • §Nobodyreceived half of one workflow and half of another.

Version recorded on every runOld versions kept

Retries that cannot double-send

A step is a pure function of its inputs, and its effects commit together with the run's own advance to the next step. There is no window in which the message has gone out but the run does not know it.

That is the whole trick, and it is a structural property rather than a promise. A worker killed mid-step — a deploy, a crash, a machine that vanishes — leaves the run exactly where it was. Another worker picks it up and re-runs the step. Because the effect and the advance commit as one, either both happened or neither did.

Outbound calls to systems that support idempotency keys carry one, derived from the run and the step, so the other side can recognise the repeat as well.

Worker killed mid-sendRecovered
  • 1Step 5 beginsSend the shipping notice.
  • ✕Worker diesDeploy. Nothing had committed.
  • 2Picked upAnother worker finds the run still on step 5.
  • ✓Sent onceThe customer received one e-mail, not two.

Effects commit with the advanceIdempotency key per run and step

A log written for a person to read

Every run, every step, the branch it took and the value that decided it. Timings per step. The request and response of each external call, with credentials redacted. The version the run was on. Where a run is waiting and until when.

When somebody asks what happened to the order placed on the fourteenth, there is an answer, and finding it takes a minute rather than an afternoon of correlating three systems' logs.

Runs that fail collect in one place with the reason grouped, so “forty-one failures overnight” resolves to “one supplier returned 503 for nine minutes” rather than forty-one separate investigations.

Overnight, grouped41 failures
  • 1One causeSupplier API returned 503 between 02:41 and 02:50.
  • 2What happenedAll 41 retried; 39 succeeded on the second attempt.
  • 3Left overTwo stopped and are waiting for a person. Both are listed with the row that failed.
  • §Not forty-oneinvestigations. One, and a decision about two records.

Credentials redactedVersion recorded

What we do not claim.

We do not publish an uptime figure on a marketing page, because a number without a measurement window and a definition of “up” is decoration. If you need one contractually, ask and we will talk about what we can actually commit to.

We also cannot make somebody else's API reliable. If a provider is down, a workflow that depends on it waits, retries and eventually stops with a clear reason. What we can promise is that the failure is visible, that it does not corrupt anything, and that resuming does not send the message a second time.

On waits: a run waiting for two days is a wake_at on the run, not a job sitting in a queue. That is why a workflow can hold a hundred thousand runs in a three-week wait without any of it costing anything, and why the number of runs currently waiting is not something you have to think about.

You never think about a run.
That means it worked.

Illustrations by GetIllustrations.com (Blues pack).