Events
Helix has two ways to make something happen when the operation moves. They are easy to confuse, and picking the wrong one is the usual reason a process does not fire.
- An action asks a person to do something. It lands in the Actions queue for a qualified operator to pick up — rebook this job, check whether the fault could be fixed remotely, look for voluntary work while the resource is still on its previous stop.
- An event does something itself, at a time, with nobody watching. Move the stop to its next state, mark a resource off duty at the shift boundary, send the customer the update you promised them.
If a human has to decide, it is an action. If the system already knows what to do, it is a step — and an event is the timing machinery that fires it. In the process vocabulary, a process is composed of steps and actions; the event queue is how a step’s moment arrives.
The three kinds of event
Section titled “The three kinds of event”One engine drives all of them, but they do not all run in the same conditions — which matters more than anything else on this page.
| What it does | Live | Replay | |
|---|---|---|---|
| The simulator | Walks stops through their lifecycle with no engineer present | No | Yes |
| Roster boundaries | STBY / REST entry and exit at shift edges | Yes | Yes |
| State-change hooks | Runs your code when a stop changes state | Yes | Yes |
The simulator
Section titled “The simulator”The reason a scenario plays at all. Three built-in events move each stop along — despatched to heading, heading to arrived, arrived to done — timed from Spiral’s own plan with deterministic jitter, so a replay is repeatable. A domain can override any single clause of them; the eventObject reference has the shape.
This is replay only, by design. In live operation the states come from the field, and a simulator would be arguing with reality.
Roster boundaries
Section titled “Roster boundaries”Shift edges are ordinary operational timing, so they run everywhere. The off-duty periods on a resource’s head roster stop schedule themselves from the shift start, and fire whether or not a scenario is running. The standby callout’s answer window is the same machinery: if nobody answers within the contract term, the timeout fires and the call stands down.
State-change hooks — where your processes go
Section titled “State-change hooks — where your processes go”The one to reach for when you want your process to run in a live operation. A stop type declares a matrix of transitions, and each cell names a function in your client module:
"onState": { "ARVD": { "DONE": "tellCustomerComplete()" }, "*": { "REBK": "tellCustomerRebooking()" }}The keys are clientState values — the states your domain displays, not Spiral’s raw ones — and *
matches any. The function runs with this bound to the stop, and receives the transition it fired on.
Reacting to a state change
Section titled “Reacting to a state change”The worked example: tell the customer when their job is done.
Core ships the hook and the moment, never a sender. Helix tells you precisely when the operation reached the point you care about; what leaves the building is yours.
1. Say when, in the domain file. On the stop type the customer cares about:
"helix": { "onState": { "ARVD": { "DONE": "tellCustomerComplete()" } }}The keys are clientState values and * matches any. Resolution is most-specific first: the exact cell,
then the * row, then the * column.
2. Say what, in your client crud module. this is the stop, and the hook is handed the transition:
exports.tellCustomerComplete = async function (info) { const to = this.ext?.customerPhone; if (!to) return; // nothing to tell, nothing to do await mySmsGateway.send(to, 'Your job is complete.');};A hook that throws is caught and never blocks the stop from being written — a failed message must not stall the plan. The corollary is that it also fails silently, so anything you need to know about has to be logged or retried by your own code.
Plugging a vendor into Helix’s own notifications
Section titled “Plugging a vendor into Helix’s own notifications”Separately from your own sending, core has a notification queue with retry, delivery tracking and a
token-authed inbound route for replies. It ships console, webhook and smtp adapters, which need no
accounts. A client adds SMS, WhatsApp or voice by exporting a notifyChannels adapter map from its
crud module — core then uses it for the notifications it raises, the standby callout above all.
That queue is currently core’s to use: a client registers channels for it and can override the callout’s
own message hooks, but does not queue its own messages through it. For a process of your own, send from
the onState hook as above.
A delivery that fails is not silent
Section titled “A delivery that fails is not silent”When a notification on core’s queue exhausts its retries, Helix raises an action in the dispatcher’s queue. That is the two mechanisms working together as intended: the event tried to handle it automatically, and when it could not, it asked a person. A message you send yourself from a hook is outside that loop — raise your own signal if it matters.
Extending this for another process
Section titled “Extending this for another process”- Decide which mechanism. Does a person need to judge something? Action. Does the system already know what to do? Event.
- Decide whether it must run live. If yes, it is an
onStatehook or a roster boundary — not a simulator event. - Author the trigger declaratively where you can. A state-gated action needs no code at all; an
onStatecell needs one small function. - Keep the function boring. Read what you need off the stop, do the one thing, return. These run inside the write path for every stop change, so they must be quick, and anything slow or unreliable belongs behind a queue of your own rather than inline.
Related
Section titled “Related”- Actions — the other mechanism, and how operators work the queue
- eventObject — the
raise/when/if/thencontract - actionObject — the action schema
- Notifications & Integration — channels, retries, replies
- Scenarios — injections and the Event Log