Skip to content

Deadlines, reminders and escalation

Rungs never reads the system clock. You pass clock, and time-based actions happen when you call rungs.tick(). That keeps behavior reproducible: the same state, command and time always give the same result, and tests can move time forward.

let now = new Date("2026-03-02T09:00:00.000Z");
const rungs = createRungs({
store: memoryStore(),
clock: () => now, // Rungs never reads the time itself
ids: () => crypto.randomUUID(),
roles: (role) => (role === "director" ? ["u_dir"] : []),
onEvent: (event) => {
if (event.type === "task.reminder") console.log("remind", event.data.assigneeId);
},
});

Runs in CI:examples/docs-snippets/src/concepts/deadlines.ts

A step with a deadline says what happens when nobody decides in time:

{
id: "manager",
approvers: { type: "users", ids: ["u_mgr"] },
deadline: "P2D", // two days after the step starts
remindBefore: "P1D", // one reminder a day before
onTimeout: "escalate", // or "remind", "auto-approve", "auto-reject"
escalateTo: { type: "role", role: "director" },
},

Runs in CI:examples/docs-snippets/src/concepts/deadlines.ts

onTimeout At the deadline
"remind" (default) Reminds the approvers, then every remindOverdueEvery (default one day)
"escalate" Gives the open tasks to escalateTo, with a new deadline
"auto-approve", "auto-reject" Decides the step; never overrides a rejection already cast

Durations are ISO 8601 ("PT8H", "P2D", "P1W"); months and years are not allowed because their length varies. For business days, pass a calendar (EC-30).

// Run tick on a schedule, for example every minute. It applies whatever is due at `now`.
now = new Date("2026-03-03T10:00:00.000Z");
ticks.push(await rungs.tick()); // remind u_mgr
now = new Date("2026-03-04T10:00:00.000Z");
ticks.push(await rungs.tick()); // escalated: the task moves to u_dir

Runs in CI:examples/docs-snippets/src/concepts/deadlines.ts

tick is safe to run twice or from several workers at once: each due action is applied once (EC-27). Run it on a schedule, for example every minute.

Escalation never lets one person replace several votes: in an all or quorum step that still needs more than one approval, a single escalation target is not enough and Rungs reports request.escalation_failed instead (EC-28).