Serial execution

By default a workflow's runs go in parallel: trigger the same workflow three times and three runs start at once. Mark it serial and its runs go one at a time, in arrival order. A trigger that arrives while a run is active waits its turn in a durable queue and starts on its own the moment the run ahead of it finishes.

Serial mode is a property of the whole workflow. The one-at-a-time guarantee holds no matter what starts it: the CLI, an HTTP call, a schedule, or another workflow.

When you need it

Use serial mode when two copies of the same workflow must never run at the same time. For example:

  • It writes to a shared resource that two runs would corrupt.
  • It must process events in the exact order they arrived.
  • A second copy would double-process work the first is already doing.

If runs are independent and safe to overlap, leave the workflow on the default (concurrent) and skip this page.

Mark a workflow serial

Set executionMode: 'serial' on the workflow in stackbone.config.ts. This is the one case where you declare a workflow in the config file instead of letting the convention scan find it.

stackbone.config.ts
import { defineWorkspace } from '@stackbone/sdk';

export default defineWorkspace({
  agents: [],
  workflows: [
    {
      name: 'reconcile-ledger',
      module: 'workflows/reconcile-ledger.workflow.ts',
      export: 'reconcileLedgerWorkflow',
      executionMode: 'serial', // one run at a time, in arrival order
    },
  ],
});

The workflow itself does not change. You still write reconcile-ledger.workflow.ts with its 'use workflow' function and its inputSchema / outputSchema exports, as in Overview.

When this config file is present, its workflows list replaces the convention scan. List every workflow you want the runtime to see, not only the serial one. Give the rest no executionMode (or 'concurrent'), so they keep running in parallel:

workflows: [
  { name: 'reconcile-ledger', module: 'workflows/reconcile-ledger.workflow.ts', export: 'reconcileLedgerWorkflow', executionMode: 'serial' },
  { name: 'send-digest', module: 'workflows/send-digest.workflow.ts', export: 'sendDigestWorkflow' }, // stays concurrent
],

See project configuration for the full stackbone.config.ts reference.

What serial mode does

A serial workflow runs one execution at a time. While a run is active, the runtime adds each new trigger to a durable per-workflow waiting list with its own input. The next trigger starts on its own, in arrival order, as soon as the active run reaches a terminal state.

A run is terminal when it completes, fails, or is cancelled. All three release the lock and let the next trigger start, so a run that throws or gets cancelled never wedges the queue.

The waiting list is durable, so it survives a runtime restart. Triggers queued before a crash are still there when the runtime comes back, in the same order.

What you see while runs are queued

A queued trigger shows up as a run with the status waiting. You see these in stackbone dev and in the Studio runs view, so you can spot a backed-up queue. A waiting row becomes a normal running run the moment its turn comes up.

Triggering a serial workflow

Triggering works the same as for any workflow, with two differences:

  • stackbone.workflows.start(name, input) returns { status: 'started', runId } when it starts a run right away. If the workflow is serial and a run is already holding the lock, it returns { status: 'queued' } with no runId yet. The trigger is on the waiting list and will start on its own.
  • stackbone.workflows.startAndWait(name, input) refuses a serial workflow. A queued trigger has no run to await, so the call fails fast with an error instead of skipping the queue. Use start and let the queue drain.
import { stackbone } from '@stackbone/sdk';

const handle = await stackbone.workflows.start('reconcile-ledger', { day });
if (handle.status === 'queued') {
  // No run yet: it is waiting behind an active run and will start automatically.
} else {
  // handle.runId is the run that just started.
}

What's next

BUILT WITH ❤️ FROM CANADA AND SPAIN