Summary#
A workflow clock measures SLA time by default: it advances only inside the run's ServiceHours windows, and only
while the run sits in a state named by Accrues. That is right for a promise ("first response within 4 hours") and
wrong for an operational timer ("close it 7 days after it was resolved") — seven days there means seven real days,
and the state a resolved ticket sits in accrues no promise at all.
Accrues = false says which kind a clock is, in the declaration itself.
Signature#
// the scalar form is unchanged and means SLA ticks
Expire = TimeSpan.FromHours(4);
// the block form states the gate
Expire { Within = TimeSpan.FromDays(7); Accrues = false; }
Deadline { Within = TimeSpan.FromDays(30); Accrues = false; }
Assigned {
Within = TimeSpan.FromHours(2);
Accrues = false; // a real-time nudge, not a service promise
Remind Ping(After = TimeSpan.FromMinutes(10), ThenEvery = TimeSpan.FromMinutes(10), Accrues = false) { … }
}Description#
Accrues = false exempts one clock from both SLA gates:
- The service-hours schedule. A wall-time clock walks real time, so a 7-day budget lands 7×24 hours later regardless of nights, weekends or holidays. Re-pointing the run at a different schedule mid-flight does not retroactively change what its remaining budget means.
- The per-state accrual pause. A state outside the workflow's
Accrueslist pauses every SLA clock on the run. A wall-time clock keeps running through it.
That second exemption is the point. An ordinary Expire in a state outside Accrues cannot fire at all — the
accrual pause runs on the very state-entry that armed the timer, so the clock is suspended before it is ever swept.
The compiler refuses that shape and names the two ways out:
state 'Resolved' declares an `Expire` but is not in this workflow's `Accrues` list, so its SLA clock is paused the
moment the run enters the state and the timer can never fire. If this is a wall-time timer (an auto-close, a cadence),
say so: `Expire { Within = <time>; Accrues = false; }`. If it is an SLA promise, add the state to `Accrues`.It is declared, never inferred. A state moving in or out of Accrues is a change to what the run promises; it
must not also, silently, change what an existing timer measures. So the gate is written on the clock, and a clock
that says nothing is an SLA clock — the reading it has always had.
Every clock-declaring setting takes it, and they mean the same thing everywhere: the workflow-level Expire and
Deadline, a per-state Expire, a milestone's Within, and a Remind cadence. A per-state Expire overrides the
workflow-level one together with its gate — the flag travels with whichever budget won, so an inherited default
never picks up a state's flag.
Examples#
One support flow carrying both kinds of clock. Working accrues the promise, so its Expire and its milestone
Within are SLA time. Resolved accrues nothing — the promise is already met — yet still auto-closes after seven
real days, and the nudge cadence is in real minutes even though the milestone it hangs off is a service promise:
enum TicketStatus { Working, Resolved, Closed, Escalated }
entity Ticket {
[Required, MaxLength(200)] string Title;
TicketStatus Status = TicketStatus.Working;
}
workflow SupportTicket {
Tracks = Ticket.Status;
Autostart = false;
Initial = Working;
Accrues = [Working]; // Resolved is deliberately NOT here
event Resolve();
event Review();
state Working {
Expire = TimeSpan.FromHours(4); // an SLA clock: service hours, paused whenever the run parks
on Expire { goto Escalated; }
subscribe Review() as Reviewer {
Assigned {
Within = TimeSpan.FromHours(4); // 4 SERVICE hours to pick it up…
Remind Ping(After = TimeSpan.FromMinutes(10), ThenEvery = TimeSpan.FromMinutes(10), Accrues = false) {
Log.Information("still unassigned"); // …nudged every 10 REAL minutes
}
Unassigned { goto Escalated; }
}
}
subscribe Resolve();
on Resolve { goto Resolved; }
}
state Resolved {
Expire { Within = TimeSpan.FromDays(7); Accrues = false; } // seven REAL days
on Expire { goto Closed; }
}
terminal success Closed { }
terminal error Escalated { Message = "escalated"; }
}Drop the Accrues = false from Resolved and the app stops compiling — that timer could never have fired.
See also#
- ServiceHours (SLA-accrual windows) — the schedule an SLA clock accrues within, and which a wall-time clock ignores
- Assigned / Finished (milestones) —
Within, the SLA a slot must be reached inside - Remind (milestone reminders) — the reminder whose cadence this most often applies to