Skip to content

re-frame.machines

Use a state machine when a feature moves through named states (idle, submitting, locked out) and the events it accepts depend on the state it is in. You write the transition table as data and register it with rf/reg-machine. The machine is then an ordinary event handler: dispatching [machine-id event] runs the table, which picks a transition, updates the machine's snapshot and returns effects through the normal event pipeline. Tracing, time-travel and overrides work for machines as they do for any other handler.

Machines ship in the optional day8/re-frame2-machines artefact. Add it alongside core at the same version. Require re-frame.machines once, from a boot or feature namespace, to load it; without it, rf/reg-machine throws :rf.error/machines-artefact-missing.

(:require [re-frame.core     :as rf]            ;; rf/reg-machine, rf/defmachine
          [re-frame.machines :as rf.machines])  ;; everything else on this page
(rf/reg-machine :auth.login/flow
  {:initial :idle
   :states  {:idle       {:on {:auth.login/submit :submitting}}
             :submitting {:on {:auth.login/success :authed
                               :auth.login/failure :idle}}
             :authed     {}}})

(rf/reg-view login-button []
  ;; The snapshot is nil until the machine handles its first event.
  (let [{:keys [state]} @(subscribe [:rf/machine :auth.login/flow])]
    (case state
      :submitting [:p "Signing in…"]
      :authed     [:p "Signed in"]
      [:button {:on-click #(dispatch [:auth.login/flow [:auth.login/submit]])}
       "Sign in"])))

A :status keyword in app-db, checked by ordinary event handlers, is enough while there are two or three states and each check is one line. A machine pays off when those checks spread: several events are legal only in some states, a state needs a timeout or must cancel work when it is left, or you want to test (state, event) → next state + effects as a pure function with machine-transition. For fetching and caching server data, use resources instead. When not to use a machine has the fuller table.

A registered machine runs as one instance per frame, addressed by its machine id. To run several copies of one machine at once (one per upload, one per open socket), or a child machine that lives only while its parent is in one state, spawn actors: each actor has its own id and snapshot, and you dispatch to its id the same way. See :rf.machine/spawn.

reg-machine and defmachine are called on the re-frame.core facade. Everything else is called on this namespace as rf.machines/…, or addressed by keyword. Use the rf.machines alias and keep the bare machines alias for your application's own namespaces.

The machines guide teaches the model, starting from The table.

Registration

reg-machine

  • Kind: macro
  • Signature:
    (reg-machine machine-id machine-spec)
    (reg-machine machine-id opts machine-spec)
    
  • Description: Registers machine-spec as the event handler for machine-id. Dispatching [machine-id event] runs the transition table. Called as rf/reg-machine.
    • opts, the optional middle slot, is a registration-metadata map. Its :schema validates the dispatched outer event vector at the :where :event boundary; any other keys are stored on the registration metadata.
    • Returns machine-id. Registering it again replaces the definition, while a live snapshot is retained and reconciled on the next event.
    • The macro walks the literal spec at expansion time. It attaches source ({:fn .. :source-coords .. :source-code ..}) to each :guards and :actions entry, and a reference-site :source-coords to each state node and transition map under :states. Xray uses these to go from a snapshot to the guard, action or state definition. The call site's own coordinates are on handler-meta.
    • Pass either a literal spec or a value defined with defmachine. A value bound with plain def carries no source.
    • The snapshot lives in the frame's runtime-db (not app-db) at [:rf.runtime/machines :snapshots machine-id]. Its shape is {:state … :data …} plus framework-managed slots for :after timer epochs and tags. Read it with the [:rf/machine machine-id] subscription, or once with subscribe-once.
  • Errors: every refusal throws at registration, not on first dispatch; Registration errors below lists them.
  • Example:
    (rf/reg-machine :session
      {:initial :anonymous
       :data    {:credentials nil}
    
       :actions
       {:capture-credentials
        ;; Remember who's signing in so the snapshot carries it through the flow.
        (fn [{[_ creds] :event}]
          {:data {:credentials creds}})
    
        :issue-auth
        ;; Fire the login request; the reply loops back as :auth-ok / :auth-fail.
        (fn [{[_ creds] :event}]
          {:fx [[:rf.http/managed
                 {:request    {:method :post :url "/api/login" :body creds
                               :request-content-type :json :sensitive? true}
                  :request-id :session/login
                  :decode     :json
                  :on-success [:session [:auth-ok]]
                  :on-failure [:session [:auth-fail]]}]]})}
    
       :states
       {:anonymous      {:on {:login {:target :authenticating
                                      :action :capture-credentials}}}
        :authenticating {:entry :issue-auth
                         :after {500 {:target :timeout}}      ;; ms — auth taking too long
                         :on    {:auth-ok   {:target :authenticated}
                                 :auth-fail {:target :anonymous}}}
        :authenticated  {:on {:logout {:target :anonymous}}}
        :timeout        {:on {:retry  {:target :anonymous}}}}})
    
    ;; The machine is an event handler: dispatch a wrapped event at its id.
    (rf/dispatch [:session [:login {:user "alice" :pass "correct-horse"}]])
    

Registration errors

reg-machine and reg-machine* check the whole spec before anything is registered, most of it through validate-machine!. Each refusal is an ex-info whose ex-data carries the id under :rf.error/id, and whose message says what to write instead. The machines guide explains each one on the page that teaches the feature, in that page's Troubleshooting table.

Error id Thrown when
:rf.error/machines-artefact-missing rf/reg-machine runs without re-frame.machines loaded.
:rf.error/invalid-machine-opts opts is not a map.
:rf.error/machine-reserved-meta-in-opts opts carries :rf/machine? or :rf/machine, which the registration sets itself.
:rf.error/machine-bad-structure :states is not a map, or a state node is not a map.
:rf.error/machine-unknown-node-key A state or transition map carries an unknown bare key, such as the XState spellings :invoke and :cond; a root-only key sits below the root; or a leaf declares :on-done.
:rf.error/machine-root-slot-not-supported The root carries a key only a state takes (:always, :choice, :final?, :spawn-all …), or :regions without :type :parallel.
:rf.error/machine-compound-state-missing-initial A state with :states has no :initial.
:rf.error/machine-bad-on-clause :on is not a map, or a transition in it is not a keyword, path, map, candidate vector or nil.
:rf.error/machine-bad-always :always is not a transition or a candidate vector.
:rf.error/machine-bad-target A :target is not a keyword or a path vector.
:rf.error/machine-unresolved-target A target names no declared state.
:rf.error/machine-unresolved-guard A guard id has no entry in :guards.
:rf.error/machine-unresolved-action An action id has no entry in :actions.
:rf.error/machine-bad-guard-form A :guard is neither an id nor a fn.
:rf.error/machine-bad-action-form An :entry, :exit or :action is not one id or one fn, a vector of them say.
:rf.error/machine-always-self-loop An :always candidate targets its own declaring state.
:rf.error/machine-always-unguarded-targetless An :always candidate has neither :guard nor :target, so it could never settle.
:rf.error/machine-bad-after-spec :after is not a map of delay to transition.
:rf.error/machine-bad-after-delay A literal :after delay is not a positive integer or an ISO-8601 duration string ("5s", 0 and -1 are refused).
:rf.error/machine-bad-timeout-duration A :timeout is not a positive integer or an ISO-8601 duration string.
:rf.error/machine-timeout-without-on-timeout A state or spawn spec has :timeout but no :on-timeout.
:rf.error/machine-on-timeout-without-timeout A state or spawn spec has :on-timeout but no :timeout.
:rf.error/machine-timeout-after-collision A :timeout resolves to the same delay as an :after key on the same state.
:rf.error/machine-non-parallel-root-after-not-supported :after or :timeout sits on a flat or compound root, or on a parallel region body.
:rf.error/machine-bad-choice :choice is a fn, empty, or otherwise not a candidate vector.
:rf.error/machine-choice-missing-choice A state has :type :choice but no :choice.
:rf.error/machine-choice-without-type A state has :choice but not :type :choice.
:rf.error/machine-choice-no-default Every :choice candidate is guarded.
:rf.error/machine-choice-self-loop A :choice candidate targets the choice state itself.
:rf.error/machine-choice-extra-keys A choice state also declares waiting-state keys such as :on, :entry or :after.
:rf.error/machine-final-state-has-transitions A :final? state declares :on, :always, :after, :spawn or :spawn-all.
:rf.error/machine-final-state-compound A :final? state has child :states.
:rf.error/machine-output-key-without-final A state without :final? declares :output-key.
:rf.error/machine-error-flag-without-final A state without :final? declares :error?.
:rf.error/machine-history-misplaced A history node has no enclosing compound state.
:rf.error/machine-history-extra-keys A history node carries a key other than :type, :deep? and :default-target.
:rf.error/machine-history-duplicate One compound declares two history nodes.
:rf.error/machine-history-bad-default-target A history node's :default-target does not resolve.
:rf.error/machine-parallel-bad-shape A parallel root also has :initial or :states, a region has no :initial, or two regions declare a spawn at the same in-region path.
:rf.error/machine-parallel-nested-not-supported A region declares :type :parallel.
:rf.error/machine-parallel-root-on-bad-target A parallel root's :on uses a bare keyword target instead of a region-qualified path.
:rf.error/machine-parallel-on-done-target A parallel root's :on-done has a :target.
:rf.error/machine-parallel-region-order-required :regions has more than eight entries and there is no :region-order.
:rf.error/machine-parallel-region-order-mismatch :region-order does not name every region exactly once.
:rf.error/machine-spawn-bad-shape A spawn spec does not have exactly one of :machine-id and :definition, or an inline :definition has neither :id-prefix nor :fixed-actor-id.
:rf.error/machine-unknown-spawn-key A spawn spec carries an unknown bare key, or a :spawn-all child declares :on-error.
:rf.error/machine-bad-on-done-clause An :on-done has the wrong form: a spawn's must be a fn or a transition, and a :spawn-all child's must be a fn.
:rf.error/machine-bad-on-error-clause A spawn's :on-error is not a transition.
:rf.error/machine-spawn-all-bad-shape A :spawn-all block has an unknown key, a :join other than :all or :any, no :on-all-complete for :all or :on-some-complete for :any, or :children that is not a vector of specs.
:rf.error/machine-spawn-all-duplicate-id Two :spawn-all children share an :id.
:rf.error/machine-spawn-all-with-spawn One state declares both :spawn and :spawn-all.
:rf.error/spawn-timeout-ms-removed A :spawn or :spawn-all carries :timeout-ms.
:rf.error/machine-bad-tags :tags is not a set of keywords, or a tag is in the reserved :rf/* or :rf.*/* namespaces.
:rf.error/machine-bad-schemas :schemas is not a map.
:rf.error/machine-bad-schemas-key :schemas carries a key other than :data, :output, :events, :tags and :meta, :input included.
:rf.error/machine-bad-internal-events :internal-events is not a set of keywords, or has a wildcard member.
:rf.error/machine-internal-event-reserved An :internal-events member is a reserved :rf/* id.
:rf.error/machine-cofx-requires-inline :rf.cofx/requires appears anywhere but a named :guards or :actions entry map.
:rf.error/cofx-request-invalid An entry in a :rf.cofx/requires vector is malformed.
:rf.error/cofx-name-collision Two :rf.cofx/requires entries on one callback use the same :as name.
:rf.error/invalid-machine-classification :sensitive or :large is not a vector of paths into the snapshot.

Development-only registration warnings do not prevent registration:

Warning Meaning and remedy
:rf.warning/machine-source-unstamped The definition has no captured source. Use defmachine or a literal reg-machine map for source navigation.
:rf.warning/machine-cofx-consume-undeclared A named callback reads a :rf.cofx key absent from its requirements. Declare the fact it needs.
:rf.warning/machine-cofx-ambient-durable A named action requests an ambient coeffect. Prefer a recorded fact for decisions or data that must replay; see coeffect grades.

defmachine

  • Kind: macro
  • Signature:
    (defmachine name machine-spec)
    (defmachine name docstring machine-spec)
    
  • Description: Defines name as a machine-spec value and captures its per-element source, so a later reg-machine of that value keeps it. Use it in place of def for a named spec. Called as rf/defmachine.
    • It walks the literal spec the same way reg-machine does and stores the source on the value. (rf/handler-meta {:source :store :kind :machine-guard :id [machine-id guard-id]}) and Xray's machine source view then work for the registered value as they do for an inline spec.
    • With (def m {…}) and (reg-machine :id m), reg-machine sees only the symbol and captures nothing. In development the registration warns :rf.warning/machine-source-unstamped, once per machine id.
    • Production builds remove the development-only :source-* slots.
  • Example:
    (rf/defmachine door-machine
      "A door that locks."
      {:initial :locked
       :states  {:locked {:on {:unlock {:target :closed}}}
                 :closed {:on {:open {:target :open}
                               :lock {:target :locked}}}
                 :open   {:on {:close {:target :closed}}}}})
    
    (rf/reg-machine :door/main door-machine)
    

Machine spec

A machine spec is one map: the root state node plus the machine's own blocks. Every map in it takes a closed set of bare keys. An unknown bare key throws :rf.error/machine-unknown-node-key, and the message names the valid keys; namespaced keys pass, so put your own annotations under a namespaced key or under :meta. Spawn specs are described under :rf.machine/spawn, a :spawn-all block in Fan-out and join, and the snapshot a machine produces under [:rf/machine machine-id]. The machines guide teaches the grammar, starting from The table.

Machine-root keys

Beside :initial and :states, a machine spec's root takes the keys below. The runtime reads them only at the root: on a nested state they throw :rf.error/machine-unknown-node-key. The root is a state node too, so it also takes the state node keys, apart from those it refuses with :rf.error/machine-root-slot-not-supported.

Root key What it takes and does
:doc Optional description retained with the definition for tools. Registration metadata can also carry :doc in the middle opts map.
:schema Accepted on the root, but does not install event validation there. Put the outer-event schema in the middle opts map of reg-machine; put private-data validation under :schemas :data.
:data The machine's initial private data, a map. It must survive pr-str and read-string, so it holds no functions, atoms or host objects.
:guards, :actions Maps from an id to a callback: a fn, or an entry map {:fn f :rf.cofx/requires […]} that declares coeffects. A parallel machine's regions use the root's maps. See Callbacks.
:regions With :type :parallel, a map from region name to region body. It makes a parallel machine, whose root takes no :initial or :states. See Parallel regions.
:schemas A map whose keys are among :data, :output, :events, :tags and :meta, each a schema. :data validates the snapshot's :data; see validate-machine-data!. :output is the next row. :events, :tags and :meta are accepted and not checked. Any other key, :input included, throws :rf.error/machine-bad-schemas-key, and a non-map throws :rf.error/machine-bad-schemas.
[:schemas :output] A schema for the value a root-level :final? leaf reports through :output-key, which is nil when the leaf has none. It is checked once, as the machine finishes, in development builds only. A failing value emits :rf.error/schema-validation-failure with :where :machine-output, :phase :completion and :rollback? false, and nothing is rolled back: the machine has already finished, so it is still destroyed and a parent's :on-done still receives the value. A schema the validator throws on emits :rf.error/malformed-schema with :where :machine-output, and completion proceeds the same way. See Schemas in the machines guide.
:sensitive, :large A vector of paths into the snapshot, such as [[:data :payment :token]]. They classify those slots of every instance: each actor's paths are registered when it is spawned, first boots or is restored, and removed when it is destroyed. Trace and tool projections redact sensitive slots and size-mark large values. The SSR hydration payload redacts sensitive :data slots but preserves large values in full, because the client needs them to resume. In-process snapshots remain unchanged. A malformed declaration throws :rf.error/invalid-machine-classification at registration. A :sensitive? prop inside [:schemas :data] does not classify the snapshot; it only redacts a failed validation's trace. See Classify subsystem data on the subsystem.
:internal-events A set of keywords, such as #{:tick}, naming events the machine raises for itself. [:raise event-vec] says what an external dispatch of one does. A vector, a non-keyword member or a wildcard member such as :tick/* throws :rf.error/machine-bad-internal-events, and a reserved :rf/* id throws :rf.error/machine-internal-event-reserved. See Raise and internal events in the machines guide.
:region-order A vector naming a :type :parallel machine's regions in the order their actions run. It is required when :regions has more than eight entries, which a map does not keep in written order; without it registration throws :rf.error/machine-parallel-region-order-required, and an order that does not name every region exactly once throws :rf.error/machine-parallel-region-order-mismatch. See Parallel regions in the machines guide.
:always-depth-limit An integer, 16 by default. It bounds the :always transitions the machine takes while it settles after an event. Exceeding it aborts the whole macrostep with :rf.error/machine-always-depth-exceeded, and no snapshot or effects commit.
:raise-depth-limit An integer, 16 by default. It bounds the raised events one macrostep handles; [:raise event-vec] has the rule. See Run to completion in the machines guide.

State node keys

These are the bare keys a state takes:

Key Meaning Taught in
:on Transitions taken on an event Transition forms
:entry, :exit Action run on entering or leaving the state Entry, exit, and transition actions
:initial, :states Child states, and the one entered first Hierarchical states
:on-done Transition taken when a child :final? state is reached Nested final states
:always Eventless transitions Automatic transitions
:after Delayed transitions Delayed :after
:timeout, :on-timeout A deadline and the transition it takes :timeout and :on-timeout
:type :choice or :history on a state; :parallel on the root Choice states, History, Parallel regions
:choice A choice state's candidate vector Choice states
:deep?, :default-target A history pseudo-state's options History
:spawn, :spawn-all Child actors that live while the state is active Actors, Fan-out and join
:tags A set of labels projected onto the snapshot Tags
:final?, :output-key, :error? A finishing leaf and what it reports Final states
:meta Your own static metadata, such as {:terminal? true} Final states

A history pseudo-state takes only :type, :deep? and :default-target. A choice state only routes, so it refuses the keys of a state that waits (:on, :entry, :after and the rest). Registration errors lists both refusals.

Key placement

The root and parallel region bodies have narrower lifecycle rules than ordinary states:

Location Accepted lifecycle and transition keys Put these on a contained state instead
Flat or compound machine root :entry, :exit, :tags, :on, :spawn :always, :choice, :after, :timeout, :on-timeout, :on-done, :spawn-all, final/history keys
Parallel machine root The same keys, plus :after, :timeout / :on-timeout, and targetless :on-done :always, :choice, :spawn-all, final/history keys
Parallel region body :entry, :exit, :tags, :on, :on-done, alongside its :initial and :states :always, timers, :spawn, :spawn-all, final/history keys

Unsupported timer placement raises :rf.error/machine-non-parallel-root-after-not-supported. Other unread root or region slots raise :rf.error/machine-root-slot-not-supported. Wrap a region's states in one compound when a child must live across those states.

Transitions

Wherever the grammar takes a transition (an :on value, an :after value, :always, :choice, :on-timeout, an :on-done, a spawn's :on-error), it takes one of these forms:

Form Meaning
:same-state The declaring state itself. A leaf stays active; a compound resets its descendants to :initial. At a flat or compound root it resets the active configuration.
:settings A target keyword, sugar for {:target :settings}. It names a sibling of the declaring state.
[:authenticated :settings] A path target, absolute from the root. At a parallel root it is region-qualified, and a vector of such paths targets several regions.
{:target … :guard … :action …} A transition map, with the keys below.
[{…} {…}] A candidate vector: the first candidate whose guard passes is taken. :choice always takes this form.
{} or nil As an :on value, a forbidden transition: it consumes the event, so no ancestor's transition for it runs.
Transition key Meaning
:target Where to go: a keyword or a path. Without it the transition is targetless and runs only its action, with no exit or entry.
:guard A guard id from :guards, or an inline fn. The transition is taken only when it returns truthy.
:action One action id from :actions, or one inline fn.
:reenter? Defaults to false. true exits and re-enters the declaring state on a self-target or a target inside that state, restarting its timers and children. A targetless transition does not exit or enter.
:meta Your own static metadata.

A path target inside a parallel region is relative to that region's root; it cannot target a sibling region. Only the parallel root can use region-qualified targets. :always accepts the transition forms above (a candidate vector is usual), but cannot be an unguarded targetless step or target its own declaring state. :choice specifically requires a non-empty candidate vector with an unguarded default.

Declarative :spawn and :spawn-all

A state's :spawn map accepts these application keys:

Key Value and default
:machine-id / :definition Exactly one: a registered machine id, or an inline machine map.
:data A replacement initial-data map, or (fn [{:keys [snapshot event]}] data-map) evaluated on state entry after the transition action. Omit it to use the definition's :data.
:id-prefix Keyword prefix for generated actor ids. Defaults to :machine-id; inline definitions need this or :fixed-actor-id.
:fixed-actor-id Explicit keyword address. Replaces an existing actor at that address, running its exits first.
:start Optional first trigger vector; defaults to [:rf.machine.spawn/spawned]. Initial entry runs before this trigger.
:on-done Optional transition, or (fn [{:keys [data result]}] new-data) that returns the parent's whole next data map. See completion.
:on-error Optional transition for child failure. Its event payload is the failure value, rather than the success {:result …} wrapper.
:timeout, :on-timeout Optional positive integer milliseconds or ISO-8601 duration and its transition; both required together. These arm a deadline on the spawning state.

The :spawn-all block has a separate closed grammar:

Key Value and default
:children Required non-empty vector of child spawn maps. Every child adds a unique keyword :id. Its :on-done, if supplied, must be a data-fold fn. Child :on-error is rejected. Put a join deadline on the parent state.
:join :all (default) or :any. There is no quorum or predicate form.
:on-all-complete Event vector required for :all, dispatched when every child succeeds.
:on-some-complete Event vector required for :any, dispatched on the first success.
:on-any-failed Optional event vector that resolves either join immediately on the first failure. Omit it to keep waiting for successes; add a parent deadline if the join could become unsatisfiable.

Each resolution event appends the logical child :id and its result to the configured trigger vector. Resolution always cancels remaining children. Without :on-any-failed, an impossible success condition reports :rf.warning/spawn-all-join-unsatisfiable and the parent keeps waiting. Fan-out and join teaches both join policies.

Callbacks

Every guard and action, :entry and :exit included, receives one context map:

Key Value
:data The machine's :data. A guard sees it before the transition's actions run; each action sees the writes of the action slots that ran before it.
:event The trigger vector. It is nil in an :always step, and [:rf.machine/start] for the initial :entry.
:state The state the machine was in before this transition, in every slot.
:meta The snapshot's :meta, which starts as the root's :meta.
:tags, :all-state Inside a parallel region only: the machine-wide tag union and the region-to-active-state map, frozen for each selection round.
:rf.cofx The coeffects a named callback declares in :rf.cofx/requires.

An action returns a map, or nil for no effects:

Key Meaning
:data Merged into the snapshot's :data, top-level keys only.
:fx An ordinary effects vector, plus the machine-only :raise.

A returned :db is dropped with :rf.error/machine-action-wrote-db. An :after delay fn is the one callback with a different context: it receives {:snapshot …}. Guards and actions in the guide teaches callbacks.

Keyword surfaces

These are the subscriptions and effects the machines artefact registers. They are included in every image whatever its :select-ns selection, so a frame loaded from an image resolves them the same way the default frame does.

[:rf/machine machine-id]

  • Kind: subscription
  • Payload: machine-id, a registered machine id or a spawned actor's id.
  • Description: Returns the machine's snapshot {:state :data}, plus the framework-managed :tags. :state is a keyword for a flat machine; for a compound machine it is the vector path from the root to the active leaf, so a root-level leaf reads [:idle]; for a :type :parallel machine it is a map from each region to that region's keyword or path. Returns nil for an unknown machine, and for a registered machine that has not yet handled its first event. To give views narrower values, register subscriptions that take this one as an input, as shown in The table.
    • A view that renders before the first event must handle nil. To create the snapshot at startup instead, dispatch the reserved trigger [machine-id [:rf.machine/start]]: it runs the initial state's :entry actions and arms its :after timers, and matches no :on transition.
  • Example:
    (let [{:keys [state data]} @(rf/subscribe [:rf/machine :auth.login/flow])]
      [:div "State: " (if state (name state) "not started")])
    
    ;; Or start the machine at boot, so the snapshot exists before any view reads it.
    (rf/dispatch [:auth.login/flow [:rf.machine/start]])
    

[:rf.machine/has-tag? machine-id tag]

  • Kind: subscription
  • Payload: machine-id and tag.
  • Description: Returns true when the :tags set in the machine's current snapshot contains tag, and false otherwise, including for an unknown or not-yet-started machine. It reads the tag directly rather than through :rf/machine, so a view that reads only this re-renders only when the answer changes.
  • Example:
    @(rf/subscribe [:rf.machine/has-tag? :auth.login/flow :auth/busy])  ;; => true / false
    

[:rf.machine/spawn spawn-spec]

  • Kind: effect (reserved fx-id)
  • Payload: a spawn-spec map with exactly one of :machine-id (a registered machine to instantiate) or :definition (an inline spec map), plus the optional keys below.
  • Description: Starts a new instance of a machine, called an actor. Emit it from any event handler's :fx, including a machine action's. A declarative :spawn state node emits it for you.
    • Choose by lifetime. When a child should live exactly as long as one state of a parent machine, put :spawn on that state: leaving the state, or destroying the parent, destroys the child. Emit this effect yourself when the actor's lifetime is not one state, such as a logger started with the session. Nothing tracks an actor you spawn this way; it lives until a :rf.machine/destroy names it.
    • A supplied :data map replaces the machine's initial :data; omitting it uses the definition's data. The runtime adds the actor's own id to it as :rf/self-id.
    • :id-prefix sets the prefix of the actor's id, which is the deterministic <prefix>#<n> from a per-type counter. The prefix defaults to :machine-id.
    • :fixed-actor-id gives the actor an explicit id instead. Use it when the spawner needs to hold the child's address: choose a fresh keyword, store it in ordinary :data, and pass it here. Spawning at a :fixed-actor-id that a live actor already holds destroys that actor first (its :exit actions run) and then installs the new one.
    • :start is an event vector dispatched to the new actor as [<spawned-id> <start>]. Without it, the runtime dispatches [<spawned-id> [:rf.machine.spawn/spawned]]. Either way the actor's initial :entry actions run first.
    • A declarative :spawn state node accepts the same keys plus :on-done, :on-error, :timeout and :on-timeout, and stores the child's id in the parent's :data at [:rf/spawned <invoke-id>]. See Actors.
  • Errors:
    • :rf.error/machine-spawn-unregistered-type: :machine-id names no registered machine and there is no :definition. Nothing is spawned.
    • :rf.error/machine-spawn-bad-shape: an inline :definition names neither :id-prefix nor :fixed-actor-id, so the actor would have no id. The effect handler throws, the effect runner reports it as :rf.error/fx-handler-exception, and nothing is spawned.
    • :rf.error/machine-spawn-all-duplicate-id: the generated <prefix>#<n> id is already held by a live actor, as when two parent machines spawn one type without distinct :id-prefix values. Nothing is spawned.
  • Example:
    (rf/reg-event :session/start-logger
      (fn [_ _]
        {:fx [[:rf.machine/spawn
               {:machine-id     :machines/log-shipper
                :fixed-actor-id :logger      ;; a well-known address the app holds
                :data           {:buffer []}
                :start          [:logger/connect]}]]}))
    
    ;; Address the actor by the id you chose.
    (rf/reg-event :session/flush-logs
      (fn [_ _]
        {:fx [[:dispatch [:logger [:logger/flush]]]]}))
    

[:rf.machine/destroy actor-id]

  • Kind: effect (reserved fx-id)
  • Payload: actor-id.
  • Description: Stops an actor. It runs the :exit actions of the actor's active states, cancels its pending :after timers and removes its snapshot from [:rf.runtime/machines :snapshots actor-id] in runtime-db.
    • It also aborts the actor's in-flight :rf.http/managed requests and releases any resources the actor owns. Hold anything else the actor uses, such as a websocket or an interval, in a custom effect handler keyed by the actor's id, and have the actor's :exit action return the close effect: :exit runs, and its effects execute, on every destroy path.
    • It ends the instance, not the machine's registration. A singleton's reg-machine registration survives, so registrations and handler-meta still report it, and its next event starts it again from :initial. To remove the registration as well, call clear: (rf/clear :event <machine-id>). A spawned actor has no registration of its own: it exists for as long as its snapshot does.
    • Destroying an actor that is already gone does nothing. A payload that is not an actor-id keyword emits :rf.error/machine-destroy-bad-arg and destroys nothing.
    • A declarative child rarely needs it: the runtime destroys it when its parent leaves the spawning state or is destroyed, and when it enters a root-level :final? state (see Final states). An actor you started with :rf.machine/spawn has no parent state to end it, so emit this effect when you are done with it, unless it finishes by entering a root-level :final? state.
  • Example:
    (rf/reg-event :session/stop-logger
      (fn [_ _]
        {:fx [[:rf.machine/destroy :logger]]}))
    

[:rf.machine/update-snapshot patch]

  • Kind: effect (reserved fx-id)
  • Payload: {:rf/machine-id <id> :rf/patch {:state … :meta … :data {…}}}, each :rf/patch key optional.
  • Description: Writes a machine's snapshot directly, without taking a transition. A machine action can return only :data and :fx; emit this from the action's :fx, or from any event handler, when you must also set :state or :meta in the same atomic write. Prefer a transition where one will do: this effect does not check that a patched :state exists in the machine's definition.
    • :state and :meta replace the snapshot's values. :data is merged into the existing :data, as an action's :data return is, so the runtime's own :rf/* keys in it survive.
    • This is a direct write: it does not run exits, entries, :always, spawn reconciliation or tag recomputation. Use transitions for normal lifecycle changes and frame-state installation for restore.
    • Other :rf/patch keys are ignored. A :db key emits :rf.error/machine-action-wrote-db and is dropped; the rest of the patch is still written.
    • Does nothing when the machine has no snapshot (not started, or destroyed).
    • The :data patch is validated against the machine's [:schemas :data] schema before it is written, by validate-update-snapshot-data!. A patch that fails is not written, so this effect is subject to the :where :machine-data boundary like a transition.
  • Example:
    ;; Move :session to :anonymous and reset a counter in one write.
    {:fx [[:rf.machine/update-snapshot {:rf/machine-id :session
                                        :rf/patch      {:state :anonymous
                                                        :data  {:retries 0}}}]]}
    

[:raise event-vec]

  • Kind: effect (reserved fx-id, machine actions only)
  • Payload: event-vec, an event for the same machine.
  • Description: Sends event-vec back into the same machine within the current macrostep (the machine's handling of one dispatched event, including every raised event and :always step it leads to), before the snapshot commits. Only a machine action's :fx can use it; there is no :raise effect handler outside machines. See Raise and internal events.
    • Use it when an action decides the machine's next step. [:dispatch [machine-id event]] handles the event later, as a separate event, after this one's snapshot has committed. A raised event is handled within this macrostep, before anything else, and the snapshot commits once.
    • An event listed in the machine's :internal-events can only be raised. Dispatching one from outside emits :rf.error/machine-internal-event-external-dispatch and changes nothing.
    • Raises are depth-bounded: 16 by default, or the machine spec's :raise-depth-limit. Exceeding the bound aborts the whole macrostep with :rf.error/machine-raise-depth-exceeded.
  • Example:
    {:actions {:kick (fn [_] {:fx [[:raise [:tick]]]})}}
    

Final states and :on-done

A machine finishes by entering a :final? leaf that is a direct child of its root. The runtime then destroys it, so you do not emit :rf.machine/destroy. A singleton's registration survives: only its snapshot is removed, and its next event starts it again from :initial. Use :final? for work that ends, such as a request actor. A resting state that views still read, such as :authed, stays a plain leaf.

A child started by a :spawn or :spawn-all state reports to its parent by finishing; it dispatches nothing itself. (An actor started with a hand-emitted :rf.machine/spawn has no parent to report to.) The runtime sends the parent a :rf.machine.spawn/done event, handled in the parent's ordinary macrostep: the parent receives the result through :on-done, and can also transition on it with a transition-shaped :on-done, an :always, or :on {:rf.machine.spawn/done …}. A child that fails arrives as :rf.machine.spawn/error instead.

A :final? leaf nested inside a compound state finishes only that compound. The machine keeps running, and the compound's own :on-done names the state to move to; see nested final states. A :type :parallel machine finishes when every region's active state is :final?, unless the root declares :on-done: that action runs and the all-final snapshot is retained.

State-node key What it does
:final? Marks a leaf state as terminal. Directly under the root, entering it finishes and destroys the machine; nested in a compound state, it finishes that compound.
:error? Requires :final?, or registration throws :rf.error/machine-error-flag-without-final. Marks the terminal state as a failure: the parent's :spawn :on-error transition fires instead of :on-done, and under a :spawn-all join the child counts as failed.
:output-key Requires :final?, or registration throws :rf.error/machine-output-key-without-final. Names the child's :data slot that is reported to the parent's :on-done.

On a parent's :spawn map, :on-done fires when the child enters a non-error :final? state, and is applied on the parent's next macrostep rather than inside the child's teardown. result is the child's :data slot named by the final state's :output-key, or nil. It takes one of two forms:

  • An :on-shaped transition that moves the parent: {:target :loaded :action …}, a keyword or path target, or guarded candidates (XState's invoke onDone). It resolves at the spawning state's level, like :on-error, and the result is at (:result (nth ev 2)).
  • A function (fn [{:keys [data result]}] new-data) that folds the result into the parent's :data. On a :spawn-all child spec, this is the only form accepted.

Any other value throws :rf.error/machine-bad-on-done-clause at registration. See Final states in the guide.

:on-error takes the transition forms only. On its event, (nth ev 2) is the failure payload itself rather than a {:result …} map: the error leaf's :output-key slot, or the exception details when a child action threw.

Cross-machine messaging

To send an event to another machine, dispatch it at that machine's id: [:dispatch [<actor-id> <event>]]. There is no separate name registry.

A child spawned declaratively has its id stored in the parent's :data at [:rf/spawned <invoke-id>], so the parent reads the address from its own snapshot. A hand-emitted :rf.machine/spawn has no invoke-id, so the spawner chooses a fresh keyword, passes it as :fixed-actor-id and stores it in ordinary :data.

Querying registered machines

There is no machines or machine-meta function. A machine is an :event registration carrying :rf/machine? true, so you list machines and read their specs with the generic registrar queries, registrations and handler-meta.

  • To list every registered machine id, filter the :event registrations on :rf/machine?:
    (keys (into {} (filter (fn [[_ m]] (:rf/machine? m)))
                (rf/registrations {:source :store :kind :event})))
    ;; → (:session :auth.login/flow …)
    
    This lists registered machines. A spawned actor has no registration of its own, so read live actors from the snapshots map at [:rf.runtime/machines :snapshots] in the frame's runtime-db.
  • To read one machine's spec, take the :rf/machine key of its registration. It holds the transition table, :doc, :schemas and per-element source coordinates, and is nil unless the :event registration is a machine:
    (:rf/machine (rf/handler-meta {:source :store :kind :event :id :session}))
    
    ;; Just the declared :data schema:
    (get-in (rf/handler-meta {:source :store :kind :event :id :session})
            [:rf/machine :schemas :data])
    

Plain-function registration and testing

re-frame.machines/reg-machine*

  • Kind: function
  • Signature:
    (re-frame.machines/reg-machine* machine-id machine-spec)
    (re-frame.machines/reg-machine* machine-id opts machine-spec)
    
  • Description: Registers a machine like reg-machine, as a plain function that captures no source. Use it when the spec is built at runtime: generated code, the REPL, test harnesses.
    • The 3-arity takes the same opts map as reg-machine, with the same :schema behaviour and the same errors.
    • Because the spec carries no source, development builds warn :rf.warning/machine-source-unstamped once per machine id.
  • Example:
    (rf.machines/reg-machine* :traffic-light
      {:initial :red
       :states  {:red   {:on {:go {:target :green}}}
                 :green {:on {:go {:target :red}}}}})
    

re-frame.machines/make-machine-handler

  • Kind: function
  • Signature:
    (re-frame.machines/make-machine-handler spec) → event-handler fn
    
  • Description: Compiles a transition table into the event-handler function that reg-machine would register, and returns it without registering it.
    • A spec with a [:schemas :data] schema throws :rf.error/machine-schema-requires-reg-machine. This path does not record the :rf/machine registration metadata the schema check reads, so the schema would validate nothing. Register such a machine with reg-machine or reg-machine*.
  • Example:
    ;; Build the handler fn without registering it (e.g. to inspect or compose it).
    (def handler
      (rf.machines/make-machine-handler
        {:initial :idle
         :states  {:idle    {:on {:start {:target :running}}}
                   :running {}}}))
    

re-frame.machines/machine-transition

  • Kind: function
  • Signature:
    (re-frame.machines/machine-transition definition snapshot event)
    → {:status :ok    :snapshot next-snapshot :fx [effect …] :handled? boolean}
    | {:status :error :error {:kind error-id …}}
    
  • Description: Runs one transition as a pure function: given a machine definition, a current snapshot and an event, it returns a plain map. Use it to unit-test a transition table. It runs on the JVM and needs no frame; re-frame.machines is the only namespace to require.
    • :status :ok carries the new :snapshot and the ordered effects vector :fx. The effects are described, never run. :fx also holds the runtime's own effects, such as one [:rf.machine/after-schedule …] for each :after timer the new state arms, so assert on the entries you care about rather than on the whole vector. An event that no transition matches returns :ok with the snapshot unchanged and :fx []. :handled? is true when the event selected a transition, even a targetless one that changed nothing, and false when nothing took it, so a test can tell a declined event from an accepted no-op.
    • :status :error reports a failed macrostep. Either a guard, action or :data function threw (:kind :rf.error/machine-action-exception, with :exception and the throwing ref), or a depth limit tripped (:kind :rf.error/machine-always-depth-exceeded or :rf.error/machine-raise-depth-exceeded). A failure carries no snapshot, because the macrostep is atomic.
    • The supplied snapshot is the starting point. This function does not create a singleton, run host effects, or apply registered schema validation; use a test frame for those boundaries.
    • Mistakes in the input, such as a malformed :state or a guard or action ref with no entry, throw the same :rf.error/* ex-info the registration checks throw rather than returning a result.
  • Example: login-flow is the definition built in the guide's first machine.
    (require '[clojure.test :refer [is]]
             '[re-frame.machines :as rf.machines])
    
    (let [{:keys [status snapshot fx]}
          (rf.machines/machine-transition login-flow
                                       {:state :idle :data {}}
                                       [:auth.login/submit {:email "a@b.com" :password "secret"}])]
      (is (= :ok status))
      (is (= :submitting (:state snapshot)))
      (is (= :rf.http/managed (ffirst fx))))   ;; the :submitting :entry fired the request
    

Runtime diagnostics

Registration failures are listed above. During a run, distinguish a rejected definition from a failed callback or an ignored event:

Diagnostic Behavior and remedy
:rf.error/machine-action-exception A guard, action or spawn-data fn threw. The macrostep rolls back; fix that callback. A child's failure can drive its parent's :on-error.
:rf.error/machine-always-depth-exceeded, :rf.error/machine-raise-depth-exceeded The whole macrostep rolls back. Make the cycle terminate, or raise the corresponding limit for intentionally longer work.
:rf.error/machine-action-wrote-db The illegal :db write is dropped; other action or patch values proceed. Return machine :data, or dispatch an application event.
:rf.error/machine-state-not-in-definition, :rf.error/machine-snapshot-version-mismatch The next event restarts an incompatible snapshot from :initial. Migrate persisted state when continuity matters.
:rf.error/machine-bad-state-form A supplied snapshot has a malformed state. Supply a keyword, path vector or region map matching the definition.
:rf.error/machine-after-sub-threw, :rf.error/machine-after-fn-threw Dynamic delay evaluation failed; no timer is armed. Fix the subscription or delay fn.
:rf.error/machine-bad-after-delay A dynamic delay is invalid and the timer is skipped. Return positive milliseconds.
:rf.error/machine-after-watch-failed A delay subscription could not be watched. Its current timer is armed, but delay changes will not reschedule it; inspect the subscription adapter.
:rf.error/machine-spawn-all-bad-child-id A completion names a child outside the join; it is ignored. Let a child's final state report completion instead of constructing runtime completion events.
:rf.error/machine-parallel-output-key-conflict Final regions name different output keys; the first region's key wins. Use one output key across the machine.

Spawn/destroy failures are documented with their effects, and schema failure behavior with machine-root keys. Shared missing or invalid coeffects use the ordinary coeffect diagnostics. An unhandled event is a benign :rf.machine.event/unhandled-no-op trace, not an error.

Framework integration

Not for application code — used by adapters, tools and the test harness.

Tooling (JVM)

These two functions are JVM-only aliases on re-frame.machines for functions in re-frame.machines.tooling. ClojureScript tools require re-frame.machines.tooling and call them there. re-frame.machines does not require the tooling namespace on ClojureScript, so an app that loads no tools drops that code from its build.

The static and live machine views that Xray draws have no public accessor: tools require re-frame.machines.tooling directly. The framework has no machine->xstate-json, machine->mermaid or Stately exporter; those are in the separate day8/re-frame2-machines-viz library.

re-frame.machines/machine-selector?

  • Kind: function
  • Signature:
    (re-frame.machines/machine-selector? sub-id) → boolean
    
  • Description: Returns true when the subscription registered under sub-id reads a machine: an ordinary reg-sub whose literal :inputs include a [:rf/machine …] or [:rf.machine/has-tag? …] query vector. Such subscriptions are ordinary :derivation subscription nodes; this lets a graph tool mark them. JVM-only.
  • Example:
    (rf.machines/machine-selector? :session/summary)  ;; => true / false
    

re-frame.machines/machine-selector-targets

  • Kind: function
  • Signature:
    (re-frame.machines/machine-selector-targets sub-id) → #{machine-id …}
    
  • Description: Returns the set of machine ids that the subscription registered under sub-id reads, taken from the second element of each [:rf/machine machine-id …] or [:rf.machine/has-tag? machine-id …] literal :inputs entry. A graph tool uses it to draw the edges machine-selector? only detects. JVM-only.
  • Example:
    (rf.machines/machine-selector-targets :session/summary)  ;; => #{:session}
    

Effect handlers

These are the handlers this namespace registers for the reserved :rf.machine/* effect ids. Application code emits the effect (see Keyword surfaces) and does not call these. Each takes (fx-ctx args), and the frame is the fx context's :frame. Because they are registered here, an app that does not use the artefact carries none of their code or trace strings.

re-frame.machines/spawn-fx

  • Kind: function
  • Signature:
    (re-frame.machines/spawn-fx fx-ctx spawn-spec)
    
  • Description: The handler for :rf.machine/spawn. Installs the new actor's snapshot at [:rf.runtime/machines :snapshots <spawned-id>] in the spawning frame's runtime-db.
    • It stores the machine type in the snapshot under :rf/machine-type, so the runtime and epoch restore can rebuild the actor from runtime-db alone. The actor has no event-handler registration of its own; it exists while that snapshot does.
    • A :machine-id that names no registered machine, with no inline :definition, emits :rf.error/machine-spawn-unregistered-type and installs nothing.

re-frame.machines/spawn-all-init-fx

  • Kind: function
  • Signature:
    (re-frame.machines/spawn-all-init-fx fx-ctx args)
    
  • Description: The handler for :rf.machine/spawn-all-init, which the runtime emits beside the per-child :rf.machine/spawn effects when a :spawn-all state is entered. It seeds the join state at [:rf.runtime/machines :spawned <parent> <invoke-id>] as {:children {…} :done #{} :failed #{} :resolved? false :spec …}. When a child reaches a final state, :error? true or not, the result is folded into the join state and resolves the join; children dispatch nothing to the parent themselves.

re-frame.machines/destroy-machine-fx

  • Kind: function
  • Signature:
    (re-frame.machines/destroy-machine-fx fx-ctx args)
    
  • Description: The handler for :rf.machine/destroy. It chooses the teardown from the shape of args: a single actor (the keyword form, or a single :spawn), or every child of a :spawn-all. It runs the actor's :exit actions and clears its [:rf.runtime/machines :snapshots <actor-id>] slot. A singleton's reg-machine registration survives.

re-frame.machines/after-schedule-fx

  • Kind: function
  • Signature:
    (re-frame.machines/after-schedule-fx fx-ctx args)
    
  • Description: The handler for :rf.machine/after-schedule, which the runtime emits once per :after entry when a state with :after is entered. It resolves the delay and schedules a wall-clock timer through the clock abstraction.
    • The delay can be a literal pos-int?, an ISO-8601 duration string such as "PT5S", a subscription vector, or (fn [{:keys [snapshot]}] ms). For a subscription delay it also adds a watch that cancels and reschedules the timer when the subscription's value changes.
    • On expiry it dispatches [<parent-id> [:rf.machine.timer/after-elapsed <delay-key> <epoch> <decl-path>]], which takes effect only if the scheduling state is still active and the epoch matches.

re-frame.machines/after-cancel-fx

  • Kind: function
  • Signature:
    (re-frame.machines/after-cancel-fx fx-ctx args)
    
  • Description: The handler for :rf.machine/after-cancel. Cancels a previously scheduled :after timer for a machine state.

Validators

These run the registration-time checks and the :data schema checks. The three :data validators run only in development builds; in production builds they skip validation and return true.

re-frame.machines/validate-machine!

  • Kind: function
  • Signature:
    (re-frame.machines/validate-machine! machine)
    
  • Description: Runs every registration-time check on a machine definition and throws on a violation. make-machine-handler calls it first, so every registration runs it.
    • It checks the whole machine spec: the closed key sets, transition shapes and targets, guard and action refs, timers, choice, final, history, parallel and spawn rules.
    • :regions is accepted only on a :type :parallel node, because regions only run on a :type :parallel root. On a flat or compound root it throws :rf.error/machine-root-slot-not-supported before any other check reads the root; the error's :offending-keys lists every root key the runtime does not read there. On a state it throws :rf.error/machine-unknown-node-key.
    • It throws the grammar ids listed under Registration errors. The opts, artefact, :region-order, :rf.cofx/requires and :sensitive / :large checks run in reg-machine around it, so this function alone does not throw those.
    • The conformance corpus tests its registration errors against this function.

re-frame.machines/validate-machine-data!

  • Kind: function
  • Signature:
    (re-frame.machines/validate-machine-data! runtime-db event-id frame-id) → boolean
    (re-frame.machines/validate-machine-data! runtime-db event-id frame-id continue?)
    
  • Description: Validates the :data of every snapshot under [:rf.runtime/machines :snapshots] in runtime-db against its machine's [:schemas :data] schema. This is the :where :machine-data boundary: the router runs it with validate-app-schema! against the candidate runtime-db, before commit.
    • Returns true when every snapshot conforms or has no schema or validator, and false otherwise. It validates every snapshot without stopping at the first failure, so each failing machine emits its own trace. On false the router rejects the whole candidate, as it does for a :where :app-db failure.
    • It finds the schema for a registered machine through the :rf/machine registration, and for a spawned actor through the snapshot's :rf/machine-type.
    • The 4-arity takes continue?, a predicate that is true while the owning frame is still current; the 3-arity builds it from the frame. If the owning frame is destroyed or replaced during validation, the function returns :rf/stale-incarnation instead of a boolean.

re-frame.machines/validate-spawn-data!

  • Kind: function
  • Signature:
    (re-frame.machines/validate-spawn-data! spawned-id spec snapshot) → boolean
    (re-frame.machines/validate-spawn-data! spawned-id spec snapshot continue?)
    
  • Description: Validates a new actor's initial snapshot :data against its machine's [:schemas :data] schema before :rf.machine/spawn installs it. Returns true on conform, no schema or no validator. Returns false on failure, and the spawn installs nothing. Nothing was committed, so the failure trace has :phase :spawn and :rollback? false. The 4-arity continue? and the :rf/stale-incarnation return work as for validate-machine-data!.

re-frame.machines/validate-update-snapshot-data!

  • Kind: function
  • Signature:
    (re-frame.machines/validate-update-snapshot-data! machine-id merged-snapshot) → boolean
    
  • Description: Validates the :data of the snapshot that :rf.machine/update-snapshot would produce, against the machine's [:schemas :data] schema, before the effect writes it. Returns true on conform, no schema or no validator, and the effect writes the patch. Returns false on failure, and the effect skips the write.

Runtime and lifecycle

re-frame.machines/install-machine-runtime!

  • Kind: function
  • Signature:
    (re-frame.machines/install-machine-runtime!)
    
  • Description: Registers the machine effects and subscriptions again, in both the registrar and the framework-standard registry, so an image-loaded frame can resolve [:rf.machine/spawn …] and [:rf/machine …]. Idempotent.
    • It works from descriptors captured when the namespace loaded, so it restores them even after a test fixture has cleared the registrar. It does for machines what the standard re-seed does for :rf/set-db.
    • It runs when the namespace loads and from the shared reset fixture. Tests that clear the registrar call it directly.

re-frame.machines/reset-timers!

  • Kind: function
  • Signature:
    (re-frame.machines/reset-timers!)
    (re-frame.machines/reset-timers! frame-id)
    
  • Description: Cancels in-flight :after timers.
    • The 0-arity clears every frame's timers. Test teardown uses this form: the fixture built by make-reset-runtime-fixture, and the per-artefact test fixtures.
    • The 1-arity clears one frame's timers. Destroying a frame calls this form, releasing that frame's host clock handles and subscription watches without touching other frames.
    • Spawn-id counters reset with the registrar and frame reset, so this function only clears the per-frame timer table.

re-frame.machines/owning-actor-id

  • Kind: function
  • Signature:
    (re-frame.machines/owning-actor-id frame-id event-id) → actor-id or nil
    
  • Description: Returns event-id when it is the address of a spawned actor whose snapshot is installed at [:rf.runtime/machines :snapshots <event-id>] in frame-id, and nil otherwise, meaning the event belongs to an ordinary handler or a registered machine.
    • Membership is decided by :rf/machine-type at the snapshot root, so it covers declarative :spawn / :spawn-all actors and actors started with [:rf.machine/spawn …].
    • Managed HTTP uses it to find which actor owns a request's originating event, so it can abort the request when that actor is destroyed, without requiring this artefact. Without the machines artefact, HTTP treats every request as unowned.

See also