Liking cljdoc? Tell your friends :D

hive.events.fsm

Deterministic FSM workflow engine for hive-events.

Adapted from yogthos/maestro — state machine runner for expressing workflows as data. Integrated with hive-events fx/cofx system.

Usage

(require '[hive.events.fsm :as fsm])

;; Define FSM spec
(def my-workflow
  {:fsm {::fsm/start {:handler    (fn [resources data]
                                    (assoc data :step :ready))
                       :dispatches [[::process (constantly true)]]}
         ::process    {:handler    (fn [resources data]
                                    (assoc data :result ((:compute resources) data)))
                       :dispatches [[::fsm/end (fn [{:keys [result]}] (some? result))]
                                    [::fsm/error (constantly true)]]}}
   :opts {:max-trace 100}})

;; Compile and run
(-> (fsm/compile my-workflow)
    (fsm/run {:compute my-fn} {:data {:input 42}}))

Privileged States

  • ::start — initial state
  • ::end — terminal success, returns data
  • ::halt — pausable, returns FSM state for resume
  • ::error — terminal failure

Integration with hive-events

  • Handlers are pure: (fn [resources data] new-data)
  • Handlers may return fx-enhanced results: (fn [resources data] {:data new-data, :fx [[:effect-id params]]}) Effects are processed through the hive.events.fx pipeline after transition.
  • Subscriptions can dispatch events on state path changes
  • Pre/post hooks for interceptor-style concerns
  • EDN specs compiled with SCI
  • Sub-FSM composition via run-sub-fsm and make-sub-fsm-handler

Based on yogthos/maestro v0.2.1 (MIT License).

Deterministic FSM workflow engine for hive-events.

Adapted from yogthos/maestro — state machine runner for expressing
workflows as data. Integrated with hive-events fx/cofx system.

## Usage

```clojure
(require '[hive.events.fsm :as fsm])

;; Define FSM spec
(def my-workflow
  {:fsm {::fsm/start {:handler    (fn [resources data]
                                    (assoc data :step :ready))
                       :dispatches [[::process (constantly true)]]}
         ::process    {:handler    (fn [resources data]
                                    (assoc data :result ((:compute resources) data)))
                       :dispatches [[::fsm/end (fn [{:keys [result]}] (some? result))]
                                    [::fsm/error (constantly true)]]}}
   :opts {:max-trace 100}})

;; Compile and run
(-> (fsm/compile my-workflow)
    (fsm/run {:compute my-fn} {:data {:input 42}}))
```

## Privileged States
- `::start` — initial state
- `::end`   — terminal success, returns data
- `::halt`  — pausable, returns FSM state for resume
- `::error` — terminal failure

## Integration with hive-events
- Handlers are pure: `(fn [resources data] new-data)`
- Handlers may return fx-enhanced results:
  `(fn [resources data] {:data new-data, :fx [[:effect-id params]]})`
  Effects are processed through the hive.events.fx pipeline after transition.
- Subscriptions can dispatch events on state path changes
- Pre/post hooks for interceptor-style concerns
- EDN specs compiled with SCI
- Sub-FSM composition via `run-sub-fsm` and `make-sub-fsm-handler`

Based on yogthos/maestro v0.2.1 (MIT License).
raw docstring

compileclj/s

(compile spec)
(compile spec handlers-map)

Compile an FSM spec into a runnable state machine.

Two arities:

  • (compile spec) — handlers are inline functions
  • (compile spec handlers-map) — handlers are keyword references resolved from the map (for EDN specs)

The compiled FSM should be passed to run.

Compile an FSM spec into a runnable state machine.

Two arities:
- `(compile spec)` — handlers are inline functions
- `(compile spec handlers-map)` — handlers are keyword references resolved
  from the map (for EDN specs)

The compiled FSM should be passed to `run`.
sourceraw docstring

make-sub-fsm-handlerclj/s

(make-sub-fsm-handler compiled-child)
(make-sub-fsm-handler compiled-child
                      {:keys [data-fn merge-fn result-key resources-fn error-key
                              fx?]
                       :or {data-fn identity
                            merge-fn merge
                            resources-fn identity
                            error-key :error
                            fx? false}})

Create a parent FSM handler that delegates to a compiled sub-FSM.

The returned handler:

  1. Extracts child initial data from parent data via data-fn
  2. Runs the child FSM via run-sub-fsm
  3. Merges child result into parent data via merge-fn

Args: compiled-child — Compiled child FSM opts — Options map: :data-fn — (fn [parent-data] child-initial-data) Default: identity (pass full parent data) :merge-fn — (fn [parent-data child-result] merged-data) Default: merge (shallow merge child into parent) :result-key — If provided, assoc child result under this key instead of using merge-fn (convenience shorthand) :resources-fn — (fn [parent-resources] child-resources) Default: identity (share parent resources) :error-key — Key to store error message when sub-FSM fails Default: :error

Returns: (fn [resources data] data') — a standard FSM handler.

Example:

;; Delegate to context-gather sub-FSM, store under :context
(def my-spec
  {:fsm {::fsm/start
         {:handler (make-sub-fsm-handler
                     compiled-context-gather
                     {:data-fn    #(select-keys % [:agent-id :directory])
                      :result-key :context})
          :dispatches [[::next (constantly true)]]}}})
Create a parent FSM handler that delegates to a compiled sub-FSM.

The returned handler:
1. Extracts child initial data from parent data via `data-fn`
2. Runs the child FSM via `run-sub-fsm`
3. Merges child result into parent data via `merge-fn`

Args:
  compiled-child — Compiled child FSM
  opts           — Options map:
    :data-fn     — (fn [parent-data] child-initial-data)
                   Default: identity (pass full parent data)
    :merge-fn    — (fn [parent-data child-result] merged-data)
                   Default: merge (shallow merge child into parent)
    :result-key  — If provided, assoc child result under this key
                   instead of using merge-fn (convenience shorthand)
    :resources-fn — (fn [parent-resources] child-resources)
                    Default: identity (share parent resources)
    :error-key   — Key to store error message when sub-FSM fails
                   Default: :error

Returns:
  (fn [resources data] data') — a standard FSM handler.

Example:
  ```clojure
  ;; Delegate to context-gather sub-FSM, store under :context
  (def my-spec
    {:fsm {::fsm/start
           {:handler (make-sub-fsm-handler
                       compiled-context-gather
                       {:data-fn    #(select-keys % [:agent-id :directory])
                        :result-key :context})
            :dispatches [[::next (constantly true)]]}}})
  ```
sourceraw docstring

runclj/s

(run compiled-fsm)
(run compiled-fsm resources)
(run {fsm-graph :fsm
      {:keys [max-trace subscriptions pre post]
       opts-run-id :run-id
       :or {max-trace 1000
            pre (fn [fsm _resources] fsm)
            post (fn [fsm _resources] fsm)}}
        :opts}
     resources
     {init-trace :trace
      init-data :data
      init-state-id :current-state-id
      init-run-id :run-id
      :or {init-state-id :hive.events.fsm/start init-trace [] init-data {}}})

Execute a compiled FSM.

Three arities:

  • (run fsm) — empty resources and state
  • (run fsm resources) — empty initial state
  • (run fsm resources initial-state) — full control

Resources: map of external dependencies (DB conns, config, etc.) Initial state keys:

  • :data — initial data map
  • :current-state-id — resume from specific state
  • :trace — existing trace log

Returns the final data map (on ::end) or halted FSM (on ::halt).

Execute a compiled FSM.

Three arities:
- `(run fsm)` — empty resources and state
- `(run fsm resources)` — empty initial state
- `(run fsm resources initial-state)` — full control

Resources: map of external dependencies (DB conns, config, etc.)
Initial state keys:
- `:data`             — initial data map
- `:current-state-id` — resume from specific state
- `:trace`            — existing trace log

Returns the final data map (on ::end) or halted FSM (on ::halt).
sourceraw docstring

run-asyncclj

(run-async compiled-fsm)
(run-async compiled-fsm resources)
(run-async compiled-fsm resources initial-state)

Execute FSM on a separate thread, return a promise.

Useful for long-running workflows that shouldn't block the caller.

Returns a future that yields the FSM result.

Execute FSM on a separate thread, return a promise.

Useful for long-running workflows that shouldn't block the caller.

Returns a future that yields the FSM result.
sourceraw docstring

run-sub-fsmclj/s

(run-sub-fsm compiled-child resources initial-data)

Execute a compiled sub-FSM within a parent handler context.

Runs the child FSM to completion and returns its result data, which the parent handler can merge into its own data map.

Args: compiled-child — Compiled FSM (from compile) resources — Resources map (shared or child-specific) initial-data — Initial data for the child FSM

Returns: The child FSM result (final data map on ::end).

Error handling: Catches child FSM exceptions and returns an error map: {:sub-fsm/error true :sub-fsm/message str :sub-fsm/cause ex} This lets the parent handler decide how to handle child failures without crashing the parent FSM.

Example:

(defn handle-with-context [resources data]
  (let [ctx-result (run-sub-fsm compiled-context-fsm
                                resources
                                (select-keys data [:agent-id :directory]))]
    (if (:sub-fsm/error ctx-result)
      (assoc data :error (:sub-fsm/message ctx-result))
      (merge data ctx-result))))
Execute a compiled sub-FSM within a parent handler context.

Runs the child FSM to completion and returns its result data,
which the parent handler can merge into its own data map.

Args:
  compiled-child — Compiled FSM (from `compile`)
  resources      — Resources map (shared or child-specific)
  initial-data   — Initial data for the child FSM

Returns:
  The child FSM result (final data map on ::end).

Error handling:
  Catches child FSM exceptions and returns an error map:
  `{:sub-fsm/error true :sub-fsm/message str :sub-fsm/cause ex}`
  This lets the parent handler decide how to handle child failures
  without crashing the parent FSM.

Example:
  ```clojure
  (defn handle-with-context [resources data]
    (let [ctx-result (run-sub-fsm compiled-context-fsm
                                  resources
                                  (select-keys data [:agent-id :directory]))]
      (if (:sub-fsm/error ctx-result)
        (assoc data :error (:sub-fsm/message ctx-result))
        (merge data ctx-result))))
  ```
sourceraw docstring

run-sub-fsm-fxclj/s

(run-sub-fsm-fx compiled-child resources initial-data)

Execute a compiled sub-FSM and return both data and accumulated effects.

Like run-sub-fsm but captures FX from child handlers instead of executing them immediately. The parent handler can then include these effects in its own :fx vector, surfacing them to the parent FSM's FX pipeline.

This is essential for composability — child FSMs should not execute side effects independently when embedded in a parent context. The parent must control effect execution order.

Args: compiled-child — Compiled FSM (from compile) resources — Resources map (shared or child-specific) initial-data — Initial data for the child FSM

Returns: {:data result-data, :fx [...]} — child data + accumulated effects. On error: {:sub-fsm/error true, :sub-fsm/message str, :sub-fsm/cause ex}

Example:

(defn handle-with-effects [resources data]
  (let [{child-data :data child-fx :fx :as result}
        (run-sub-fsm-fx compiled-child resources
                        (select-keys data [:input]))]
    (if (sub-fsm-error? result)
      (assoc data :error (:sub-fsm/message result))
      {:data (merge data child-data)
       :fx (vec child-fx)})))
Execute a compiled sub-FSM and return both data and accumulated effects.

Like `run-sub-fsm` but captures FX from child handlers instead of
executing them immediately. The parent handler can then include these
effects in its own `:fx` vector, surfacing them to the parent FSM's
FX pipeline.

This is essential for composability — child FSMs should not execute
side effects independently when embedded in a parent context. The
parent must control effect execution order.

Args:
  compiled-child — Compiled FSM (from `compile`)
  resources      — Resources map (shared or child-specific)
  initial-data   — Initial data for the child FSM

Returns:
  `{:data result-data, :fx [...]}` — child data + accumulated effects.
  On error: `{:sub-fsm/error true, :sub-fsm/message str, :sub-fsm/cause ex}`

Example:
  ```clojure
  (defn handle-with-effects [resources data]
    (let [{child-data :data child-fx :fx :as result}
          (run-sub-fsm-fx compiled-child resources
                          (select-keys data [:input]))]
      (if (sub-fsm-error? result)
        (assoc data :error (:sub-fsm/message result))
        {:data (merge data child-data)
         :fx (vec child-fx)})))
  ```
sourceraw docstring

set-transition-tap!clj/s

(set-transition-tap! f)

Register a per-transition observer (fn [run-token fsm]); nil clears it.

Register a per-transition observer (fn [run-token fsm]); nil clears it.
sourceraw docstring

stepclj/s

(step {:keys [fsm current-state-id data] :as halted-fsm} resources)

Execute a single state transition (for debugging/testing).

Takes a running FSM state (as returned by ::halt handler) and executes one step, returning the new FSM state.

Useful for step-through debugging of workflows.

Execute a single state transition (for debugging/testing).

Takes a running FSM state (as returned by ::halt handler) and
executes one step, returning the new FSM state.

Useful for step-through debugging of workflows.
sourceraw docstring

sub-fsm-error?clj/s

(sub-fsm-error? result)

Check if a sub-FSM result is an error map (from run-sub-fsm).

Check if a sub-FSM result is an error map (from run-sub-fsm).
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close