Appearance
Workflows - stages, approvals, deadlines and parallel work
What it is for
A workflow runs a business process over days and across people: a leave request that needs a manager, a purchase that needs two approvals if it is large, a document that waits for a signature. The platform keeps the state, remembers who must act, reminds and escalates when they do not, and records everything.
Use a workflow when a process spans people or time. Use a rule to run something at one moment of a record's life (before or after a save), and a validation to refuse a bad value. See Workflows, rules and validations for choosing between them.
The pieces
| Piece | What it is |
|---|---|
| Definition | The design: stages, tasks and transitions. It has versions; only a published version can start new runs. |
| Instance | One running copy, with its own data (its context), current stage and history. |
| Stage | A state the instance can be in, such as request, manager, approved. |
| Task | Work inside a stage. A human task waits for people to decide, a service task runs a node (create a record, send an e-mail), a subworkflow task runs another workflow. |
| Transition | A move from one stage to the next, optionally guarded by a condition. |
| Context | The instance's data. Tasks read it as ${context.name} and write results back under their task key, ${context.<taskKey>.<field>}. |
Conditions
A transition without a condition always matches. With one, the first matching transition wins (unless it is marked parallel, below).
json
{"field": "amount", "op": "gt", "value": 500}
{"all": [ {"field": "amount", "op": "gt", "value": 500}, {"field": "department", "op": "eq", "value": "IT"} ]}
{"any": [ ... ]}
{"not": {"field": "approved", "op": "eq", "value": true}}Operators: eq neq gt lt gte lte contains startsWith endsWith in notIn between isEmpty isNotEmpty. in and notIn take a list, between takes [low, high] (inclusive), and isEmpty and isNotEmpty take no value.
Human approvals
A human task lists its candidate approvers. Three ways to name them, and you can mix them:
| Write | Meaning |
|---|---|
"FACILITIES_MANAGER" | Everyone holding that role |
"${context.managerEmail}" | Whoever this instance's data names, decided when the instance starts. If the value is missing the literal text stays, which shows up as an obvious authoring mistake |
"${rule.pick-approver}" | A rule written in code that returns one or many people (for example "the department head of the requester") |
json
{"taskKey": "managerApproval", "stage": "manager", "kind": "human",
"payload": {"candidateApprovers": ["${context.managerEmail}", "HR_OPERATIONS"], "approvalMode": "first-response"}}Approval styles (approvalMode):
| Mode | The task is done when |
|---|---|
first-response | The first approver decides |
all-must-approve | Everyone approves (one rejection rejects) |
quorum | quorumCount approvers approve |
majority | More than half approve |
percentage | approvalPercent of the approvers approve, rounded up |
sequential | Approvers decide one after another in the listed order; only the next one may decide |
At run time an approver can add another approver to an open task, and a person with the right can insert an extra approval step into the current stage. The stage does not move on until that extra task is done too.
In the inbox, approvers can decide many at once. One bad item (already decided, not your task) fails alone and does not stop the rest.
Deadlines and escalation
- An SLA gives a stage a time limit in minutes. When it lapses, the pending tasks are escalated: reassigned to the fallback people you named.
onBreachStagealso moves the instance to another stage, so a process cannot sit forever on an absent approver. The abandoned stage's open tasks are closed so a late decision cannot undo the move.- An escalation ladder has several levels, each with its own delay and target:
json
"slasJson": "[{\"stage\": \"manager\", \"durationMinutes\": 2880, \"onBreachStage\": \"head\"}]",
"escalationsJson": "[{\"stage\": \"finance\", \"escalateTo\": \"FINANCE_HEAD\",
\"levels\": [{\"afterMinutes\": 1440, \"escalateTo\": \"FINANCE_MANAGER\"}, {\"afterMinutes\": 2880, \"escalateTo\": \"FINANCE_HEAD\"}]}]"Only the stage's own breach (level 0) forces the instance to onBreachStage; ladder levels reassign.
Parallel work
Mark two or more transitions from the same stage "parallel": true and every one whose condition matches fires at once. Declare where they come together:
json
"transitionsJson": "[{\"fromStage\": \"start\", \"toStage\": \"legal\", \"parallel\": true},
{\"fromStage\": \"start\", \"toStage\": \"finance\", \"parallel\": true}]",
"metadataJson": "{\"joins\": [{\"stage\": \"final\", \"requiredBranches\": 2}]}"The join stage starts exactly once, when the required number of branches has arrived. A definition that never uses parallel behaves exactly as before.
Subworkflows and loops
- A subworkflow task starts another workflow and the parent waits for it. The child gets the parent's data unless the task's
payload.contextoverrides it. - For each (
loop.foreach) starts a workflow once per item in a list. See Node reference.
Errors and retries
Any service task can retry and say what happens when attempts run out:
json
{"taskKey": "call", "stage": "s", "taskType": "http", "kind": "service", "payload": {},
"maxAttempts": 4,
"retry": {"backoff": "exponential", "intervalSeconds": 5, "maxDelaySeconds": 300},
"onError": {"action": "goto", "stage": "cleanup"}}onError.action is fail (the instance fails, the default), continue (the error is saved as ${context.<taskKey>.error} and the process carries on), or goto a stage. A node that is only waiting for something outside itself (a timer, a reply, a call that has not ended) is put back in the queue without using up an attempt, so waits survive restarts and many workers.
Testing before you publish
- Simulate runs a definition in memory with sample data and shows the path taken, forks included. No records are written.
- Problems lists what stops publishing, for example a node missing a required field.
- Review: a design can carry a review status and comments pinned to a stage or task. If the team switches on required review, publishing needs status
approved.
Seeing what happened
- History records every move, decision and failure. The Runs tab shows counts by state and a step-by-step timeline with the attempt number of each failure. Values whose key looks secret (password, token, key, credential, authorization) are masked.
- The inbox shows each person's open tasks.
- Analytics endpoints give counts and durations per definition.
Changing a design while instances run
Publishing creates a new version; running instances stay on their version. To move a running instance across, use migrate (needs the migrate right). It succeeds only when the instance can carry on exactly where it is: it is running, the target is a published version of the same workflow, the stage exists there, every open task has the same key, stage, kind and type, and it is not split into parallel branches. Use dryRun first. A refusal changes nothing and lists the reasons.
Make it with AI
Generate with AI turns a sentence into a draft with stages, tasks and transitions, using approver roles that exist in your workspace. It opens in the designer for you to review. It is never published for you.
