Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Workflow

Wall-time clocks (Accrues = false)

Expire { Within = <TimeSpan>; Accrues = false; }

A timer that measures REAL time instead of SLA time. Written as a block on any clock-declaring setting — `Expire`, `Deadline`, a milestone's `Within`, a `Remind` cadence — `Accrues = false` exempts that one clock from BOTH SLA gates: the run's service-hours schedule and the per-state accrual pause. It is what lets an auto-close fire in a state that carries no service promise.

stable1 example compiled by CIworkflowsyntax

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 Accrues list 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#

Related

ServiceHours (SLA-accrual windows)

A schedule the SLA clock accrues within — the platform WALKS its weekly windows (and holiday exceptions) to advance…

Assigned / Finished (milestones)

A milestone puts an SLA on a slot's progress — Assigned (someone must PICK IT UP within Within) and Finished (it must…

Remind (milestone reminders)

A reminder scheduled off a milestone. Its SCHEDULE is config in the header parens — `After` is the first fire (once, at…