Documentation
Workflows and steps
How workflows execute as dependency graphs, how steps map their input, and what happens when one fails.
A workflow is what runs when a trigger fires. It is not a list of steps executed top to bottom — it is a directed graph, and that difference is most of the reason to use one.
Steps run because the graph says so
You do not order steps. You declare what each one depends on, and RunDis sorts the graph and runs it. Steps with no unmet dependencies run together; a step with two dependencies waits for both.
Employee offboarding is the shape that shows it:
Workday termination
├── Disable Slack ─────────────┐
└── Disable Office 365 ──┐ │
▼ │
Forward email │
│ │
▼ ▼
Notify managerDisable Slack and Disable Office 365 have nothing to do with each other, so they run at the same time. Forward email waits for Office 365 because it needs the mailbox. Notify manager waits for both branches and runs once, when they have converged.
Where a trigger enters the graph
In the diagram, Workday termination is drawn feeding Disable Slack and Disable Office 365, and that is literally what the canvas records: a line from a trigger to a step makes that step an entry point for the trigger. When the trigger fires, RunDis runs the entry steps and everything downstream of them, and nothing else. A step on another branch is recorded in the run as skipped, with the reason not an entry point of the trigger that fired, nor downstream of one, so the run history says why it did not run.
Two triggers can share one workflow and enter it at different steps, and one step can be the entry point of several triggers. A trigger with no line to any step runs every step, which is also what a manual run does.
A linear tool cannot express this. You end up either serialising work that had no reason to be serial, or running the notification twice.
Steps and actions
A step is a node in that graph. It points at an action — the reusable executor that does the actual work — and adds everything about this use of it: what it depends on, what input it gets, whether it should run at all, and what should happen if it fails.
The action is shared; the step is where it is configured for the job at hand. See Actions for the executors and their fields.
Mapping input into a step
A step's input is assembled from two places: the event that fired the trigger, and the output of steps that already ran.
Each mapping is a source path and a target key. The target is the name the action receives; the source is a dot-path with one of two prefixes:
| Source | Reads from |
|---|---|
trigger.event.data.… |
The event payload that fired the trigger (trigger.… works too) |
steps.{step name}.output.… |
An earlier step's output |
trigger.event.data.employee.email → recipient_email
steps.Disable Office 365.output.id → mailbox_idStep outputs are parsed as JSON when they are JSON, so you can reach into them by path. When a step's output is not JSON, the whole output is available as a string.
A mapping whose source does not resolve is skipped rather than failing the step, so the action runs with that key absent. If an action behaves as though a value were missing, an unresolvable source path is the first thing to check.
You often don't need a mapping at all. Email subjects and bodies, Slack messages
and webhook body templates can read the trigger's payload and earlier outputs
directly, as {{ .trigger.employee.email }} and {{ .steps.lookup.status }}. See
Actions.
Conditions on a step
A step can carry its own condition group, using the same operators and the same AND/OR trees as a trigger. It is evaluated when the step is reached; if it is false, the step does not run.
This is how you branch inside a workflow without building a second one — notify the manager only if the employee had reports, evaluated against the same data the trigger matched on. Rule operators applies unchanged.
When a step fails
Every step carries a failure policy. This is a case you configure, not a run that stops — unless you cancel it (see Cancelling a run).
| Policy | What happens |
|---|---|
must_succeed |
The step's failure fails the workflow. This is the default |
can_fail |
The failure is recorded and the workflow carries on |
retry_then_skip |
The step is retried; if it still fails, it and everything downstream of it are skipped |
retry_then_skip takes a retry count and a backoff strategy:
| Strategy | Delay between attempts |
|---|---|
fixed |
2 seconds, every time |
linear |
2 seconds, then 4, then 6, growing by 2 each attempt |
exponential |
2 seconds, then 4, then 8, doubling, capped at 60 |
exponential is the default. Note that skipping is downstream — a step that
skips takes its dependents with it, because they were waiting on something that
never produced a result, but leaves the branches that did not depend on it
running.
Reading a run
Every run is recorded step by step: what each one did, what it returned, and how long it took. Nothing is summarised away.
The part worth knowing about is how steps that did not run are reported. When a step does not run, RunDis records which kind of not-running it was:
- Skipped because its condition was false — the workflow worked exactly as configured, and this step was not meant to run for this event
- Skipped because a dependency failed — something upstream broke and this step never got its chance
- Cancelled before it ran — a person stopped the run first; and a step that was running when the run was cancelled is cancelled while running, with whatever its action returned before it was interrupted
Most tools collapse both into "did not run", which is the difference between a workflow behaving correctly and a workflow that is broken. Keeping them apart is the same idea as the rule engine's reason strings, one level up: when something does not happen, the run should say why.
Runs outlive what made them. A run whose trigger has since been deleted is still listed, with that column empty; a run whose workflow has been deleted is listed as "Deleted workflow". History is history.
Posting back into RunDis
A step can post to one of your own inbound webhooks and fire another workflow. Every such request carries the run it came from and how deep the chain is, and a chain deeper than the re-entry cap is refused — the step fails and says so. See providers.
Runs started by triggers are bounded in how many run at once per API instance
(RUNDIS_EXECUTOR_MAX_CONCURRENT, 16 by default); a burst of matches queues
and runs as slots free, and nothing is dropped. A run you start by hand is not
queued behind them.
The canvas on a phone
A phone opens a workflow read-only: the same canvas, auto-laid out, pinch to zoom, and under it the nodes as a list — the trigger first, then the steps in the order they depend on each other. Tapping a node says what it is: the action and its type, what happens on failure, how many inputs are mapped, the conditions it runs under. Run workflow is the one action. Building and editing the canvas is done on a larger screen; a phone never opens the builder.
Running one by hand
A workflow can be executed directly with an input payload of your choosing, without waiting for a trigger. It is the quickest way to test what a step does with a given shape of data before pointing a real provider at it.
A run that has already happened can be run again from its own page (every run has
one, /runs/<id>, which is where a link to a run lands): Run again executes the
same workflow once more with the payload its trigger matched on, or as a manual run
if it was one, after a confirmation — every step runs again, including anything it
posts or sends. On a phone the button sits above the tabs. A run whose workflow has
since been deleted cannot be run again; it stays readable.
A run that fails can also tell you: a member who turned on Notifications on an installed phone gets a push with a link to the run, sent from the process that ran it the moment it fails (see the quickstart's phone section).
A run by hand is returned the moment it starts, and its page shows the steps as they land. Run again re-sends what the run was started with: the payload its trigger matched on, or, for a manual run, the input it was given, which the run's page shows. The run's page also links to its workflow and to the trigger that fired it.
Cancelling a run
A run that is still running can be cancelled from its page — Cancel run, beside Run again, behind a confirmation; on a phone it sits above the tabs. The run stops before its next step, and the step in flight is interrupted where its action allows: an HTTP request or a script is cut off, and the step records what the action had returned so far; an email already being handed to the mail server completes, since that protocol offers no way to take it back. The steps after it do not run and are recorded as cancelled before they ran; what had already happened stays in the run.
A cancel is a request, not a guarantee of timing. A run that had finished its work by the time the request reached it keeps its real status — completed or failed — and its page says a cancel was requested but came too late. A cancel that arrives while the executor is waiting between retries is honoured at once. Cancelling needs the member role, the same as running.