Signals and queries: the workflow as an actor
A running workflow is a long-lived actor you can message: a signal is a fire-and-forget input recorded as an event that can change the workflow's path, and a query is a read-only peek at current state that must never mutate or schedule anything.
A signal can divert ORDER #1001 away from shipping — but 'divert' is easy to say and hard to do correctly. We've already charged the card and reserved the Widget. We can't ship, and we can't just stop and leave the customer paying for nothing. The completed steps have to be UNDONE, in the right order. How does a workflow safely back out of work it already committed?
Scene 08
Signals and queries: the workflow as an actor
- Watch
- Try it
- Predict
- Capture
ORDER #1001 isn't finished — it's a LIVE workflow, paused on its next step (ShipPackage), waiting. That's the first surprise: a running workflow isn't a closed box you fire and forget. It's an addressable thing, sitting there with a mailbox, that you can still talk to. Watch the getOrderStatus arrow on the left peek IN: it reads the current state — "reserved, about to ship" — and a value comes back OUT. Now look at the event-history strip along the bottom: nothing new landed. The read left no trace. That trace-free, read-only peek into a running workflow's current state is called a query: it asks "what is the order's status right now?" and gets an answer without changing or recording anything. It runs against replayed state, so it must stay read-only.
Highlighted lines are the ones running in the diagram right now.
def run(order):charge_card(order) # recorded as eventsreserve_inventory(order)# the actor parks here, reachable, until wokenif self.cancelled:return run_compensation(order) # divertship_package(order)send_email(order)
# delivered to the running workflow's mailboxdef cancelOrder():# lands as SignalReceived in the event historyself.cancelled = True# next step sees it and diverts the path
# runs against REPLAYED statedef getOrderStatus():# reads current state, returns it, no event appendedreturn self.status # 'reserved, about to ship'# FORBIDDEN here: mutate state or schedule work,# replay would diverge -> non-determinism error
Where this sits in Build a workflow engine (Temporal / Airflow / Cadence style)
Scene 08 of 13, in the Time & actors act — Durable timers and the workflow as an actor.. A running workflow is an addressable actor: a signal delivers external input durably and can change its path; a query reads its state without mutating it.
Up next. Diverting mid-order means undoing committed work — but you can't wrap a charge, a reservation, and a shipment in one database transaction across separate services. Instead each step gets a semantic undo (a refund undoes a charge) and on failure you run those undos in reverse order. That pattern is the saga.
All 13 scenes in Build a workflow engine (Temporal / Airflow / Cadence style) · Every curriculum