Parallel tasks and retries
Control task concurrency, retries, timeouts and what happens after a failure.
In this example, flaky and lint start in parallel. The first task fails once, waits 100 ms and succeeds on its next attempt. Its entry in result.tasks then reports attempts: 2.
Run tasks in parallel
Section titled “Run tasks in parallel”start({ concurrency }) sets how many tasks run at once. The default is 1: tasks run one after another. A task still waits for every task in its after list.
Retry a failing task
Section titled “Retry a failing task”A task runs once unless you give it a retry policy.
API reference: WorkflowOptions.
Each retry emits a retry event with its delayMs (see Follow progress). Retries count against budget.attempts when you set a budget.
Honour Retry-After
Section titled “Honour Retry-After”Model providers copy a valid Retry-After header into OutpostError.details.retryAfterMs. The retry then waits at least that long, even beyond maxDelayMs and whatever the jitter. Your own code can throw an OutpostError with details.retryAfterMs in milliseconds to get the same behaviour.
Set timeouts
Section titled “Set timeouts”API reference: TaskOptions and DispatchOptions.
Set separate deadlines for each task attempt and the whole workflow. In this example, the first attempt expires after 200 ms, and the workflow deadline interrupts the retry at 300 ms.
The first attempt times out after 200 ms, the second is cancelled by the workflow deadline at 300 ms. Both values are positive integers of at most 2,147,483,647 ms. Each resumed start() gets a fresh deadline; the time between calls does not count.
Choose what a failure stops
Section titled “Choose what a failure stops”A task fails when its last attempt fails. What happens next depends on stopOnError.
| Other tasks | stopOnError: true (default) | stopOnError: false |
|---|---|---|
| Running | Their signal aborts; they end cancelled. | Continue. |
| Not started | End cancelled. | Dependents of the failed task end skipped; the others run. |
Workflow status | "failed" | "failed" |
unwrap() throws a WorkflowFailure unless status is "done". Read result.tasks for each task’s status, attempts and error, and result.errors for the failures themselves.
Skip a task with a condition
Section titled “Skip a task with a condition”condition runs once, before the first attempt. When it returns false, the task ends skipped and so do the tasks that depend on it.
A skipped task has no value: result.value(tests) throws.
Cancel a run
Section titled “Cancel a run”Pass an AbortSignal as start({ signal }). It aborts every running task’s context.signal, and the workflow ends with status: "cancelled".
Agent, command and isolated tasks forward context.signal for you. In defineTask(), pass it to every command, request and wait your code starts.
Limits
Section titled “Limits”- Cancellation is cooperative: code that ignores
context.signalkeeps running until it returns, even past the workflow deadline; its value is then discarded. - A retry runs the whole task again and can repeat its side effects. Deduplicate them with
context.idempotencyKey, which stays the same across retries: see Job queues and workers. - Each
start()call, such as a checkpoint resume or a resume after a quota pause, allowsretry.attemptsagain and restarts the backoff fromdelayMs; attempt numbers stay cumulative in a checkpoint. - With quota pauses enabled, a quota error pauses the task instead of retrying it.
- Retry settings, task timeouts and the presence of a condition are part of the checkpoint identity: changing them rejects an existing checkpoint (see Durable runs).
API: defineTask · Retry · TaskOptions · WorkflowOptions · WorkflowResult · OutpostError