Skip to content

Effects and purity

Fire does not talk to the outside world. It takes the current instance and an event, computes the next state, and returns everything that happened as data:

type FireResult[S comparable] struct {
NewState S
Effects []Effect
Trace Trace
Err error
}

No effect is performed inside Fire. If a transition’s action says “publish an OrderPaid message” or “charge the card”, Fire records that intent as an Effect in the result and returns. The host (your handler, consumer, or test) inspects res.Effects and dispatches them:

res := inst.Fire(ctx, Pay)
if res.Err != nil {
return res.Err
}
for _, eff := range res.Effects {
if err := dispatch(ctx, eff); err != nil { // publish / store / RPC
return err
}
}
// res.NewState is now committed; res.Trace explains how we got there.

Because the machine performs no IO, the same *Machine runs unchanged in wildly different hosts:

  • A unit test fires events and asserts on NewState and Effects, with no brokers, no databases, no mocks of the kernel.
  • An HTTP handler fires the event for an incoming request, then dispatches the effects to its real publishers and stores.
  • An event consumer fires the event off the wire and dispatches effects back onto the bus.

The decision of what should happen lives in the machine; the decision of how to make it happen lives in the host. That separation is what keeps the kernel pure, the behavior testable, and the same definition portable across every place it runs.

The Trace rounds this out: it is an ordered record of the transitions, guards, and regions that Fire walked, so you can explain, log, or replay any step after the fact, again entirely as data.

That for _, eff := range res.Effects loop is exactly the seam the crucible/sink module fills: a Manifold fans each effect out to every wired destination (SQL, a message bus, a metric), fire and forget, with neither kernel importing the other. The state-to-sink bridge makes “fan every transition out” a one-liner. The kernel stays a pure decision core; sink is one ready-made way for the host to act on what it decides.