Multimethod-based event dispatch + pure orchestration for hive-events.
Two complementary subsystems in one namespace:
Registration:
(register-handler! event-id handler-fn) — register handler (no interceptors)(register-handler! event-id interceptors handler-fn) — with interceptors(remove-handler! event-id) — remove a registered handlerDispatch:
(dispatch-sync event) — synchronous dispatch + fx execution(event-id event) — extract event-id (dispatch fn)Query:
(handler-registered? event-id) — check if handler existsHandler signature: (fn [coeffects event] effects-map)
Compile a batch of cross-tool operations into a wave-based execution plan. Pure functions — no side effects, no IO, no requiring-resolve.
Pipeline: normalize → validate → expand-batch → assign-waves → compile-spec
(normalize-op op) — normalize string keys, auto-generate ID(validate-ops ops) — validate IDs, deps, cycles (Kahn's algorithm)(expand-batch-macro ops) — expand 'batch' command into fan-out sub-ops(assign-waves ops) — topological sort into dependency-ordered wave groups(compile-multi-spec ops) — end-to-end: validate → expand → waves → FSM spechive-events (this ns) — pure compilation (validate, sort, waves)
↓
hive-mcp tools/multi — bridge (resolve handlers, execute ops, format)
↓
hive-mcp consolidated — MCP facade (JSON schema, tool def)
SOLID: OCP — Open for extension via defmethod handlers SOLID: SRP — Pure orchestration logic, no side effects CLARITY: L — Layered: pure compilation separate from execution
Multimethod-based event dispatch + pure orchestration for hive-events. Two complementary subsystems in one namespace: ## 1. Handler Dispatch (multimethod-based) Registration: - `(register-handler! event-id handler-fn)` — register handler (no interceptors) - `(register-handler! event-id interceptors handler-fn)` — with interceptors - `(remove-handler! event-id)` — remove a registered handler Dispatch: - `(dispatch-sync event)` — synchronous dispatch + fx execution - `(event-id event)` — extract event-id (dispatch fn) Query: - `(handler-registered? event-id)` — check if handler exists Handler signature: `(fn [coeffects event] effects-map)` ## 2. Pure Orchestration (multi-op compilation) Compile a batch of cross-tool operations into a wave-based execution plan. Pure functions — no side effects, no IO, no requiring-resolve. Pipeline: normalize → validate → expand-batch → assign-waves → compile-spec - `(normalize-op op)` — normalize string keys, auto-generate ID - `(validate-ops ops)` — validate IDs, deps, cycles (Kahn's algorithm) - `(expand-batch-macro ops)` — expand 'batch' command into fan-out sub-ops - `(assign-waves ops)` — topological sort into dependency-ordered wave groups - `(compile-multi-spec ops)` — end-to-end: validate → expand → waves → FSM spec ## Architecture ``` hive-events (this ns) — pure compilation (validate, sort, waves) ↓ hive-mcp tools/multi — bridge (resolve handlers, execute ops, format) ↓ hive-mcp consolidated — MCP facade (JSON schema, tool def) ``` SOLID: OCP — Open for extension via defmethod handlers SOLID: SRP — Pure orchestration logic, no side effects CLARITY: L — Layered: pure compilation separate from execution
(assign-waves ops)Assign operations to execution waves based on dependencies.
Independent ops run in the same wave (parallel). Dependent ops are assigned to later waves. Uses Kahn's topological sort.
Returns ops with :wave key added, sorted by wave number.
Pure function — no side effects.
Example: ;; Independent ops → same wave (assign-waves [{:id "a" :depends_on []} {:id "b" :depends_on []}]) ;; => [{:id "a" :wave 1 ...} {:id "b" :wave 1 ...}]
;; Chain → sequential waves (assign-waves [{:id "a" :depends_on []} {:id "b" :depends_on ["a"]}]) ;; => [{:id "a" :wave 1 ...} {:id "b" :wave 2 ...}]
Assign operations to execution waves based on dependencies.
Independent ops run in the same wave (parallel). Dependent ops
are assigned to later waves. Uses Kahn's topological sort.
Returns ops with :wave key added, sorted by wave number.
Pure function — no side effects.
Example:
;; Independent ops → same wave
(assign-waves [{:id "a" :depends_on []} {:id "b" :depends_on []}])
;; => [{:id "a" :wave 1 ...} {:id "b" :wave 1 ...}]
;; Chain → sequential waves
(assign-waves [{:id "a" :depends_on []}
{:id "b" :depends_on ["a"]}])
;; => [{:id "a" :wave 1 ...} {:id "b" :wave 2 ...}](compile-multi-spec ops)Compile a multi-op specification from raw operations.
End-to-end pipeline: normalize → validate → expand-batch → assign-waves.
Returns:
The :ops vector contains fully normalized, wave-assigned operations. The :waves map groups ops by wave number for execution.
Pure function — no side effects, no IO.
Example: (compile-multi-spec [{:tool "memory" :command "add" :content "hello" :type "note"} {:tool "kg" :command "edge" :from "a" :to "b" :depends_on []}]) ;; => {:valid true ;; :ops [{:id "op-..." :tool "memory" ... :wave 1} ;; {:id "op-..." :tool "kg" ... :wave 1}] ;; :waves {1 [{...} {...}]} ;; :wave-count 1}
Compile a multi-op specification from raw operations.
End-to-end pipeline: normalize → validate → expand-batch → assign-waves.
Returns:
- On success: {:valid true :ops [...] :waves {1 [...] 2 [...]} :wave-count N}
- On failure: {:valid false :errors [...]}
The :ops vector contains fully normalized, wave-assigned operations.
The :waves map groups ops by wave number for execution.
Pure function — no side effects, no IO.
Example:
(compile-multi-spec [{:tool "memory" :command "add" :content "hello" :type "note"}
{:tool "kg" :command "edge" :from "a" :to "b"
:depends_on []}])
;; => {:valid true
;; :ops [{:id "op-..." :tool "memory" ... :wave 1}
;; {:id "op-..." :tool "kg" ... :wave 1}]
;; :waves {1 [{...} {...}]}
;; :wave-count 1}(dispatch-sync event)Dispatch an event synchronously through its handler chain.
Returns the final context with :coeffects and :effects. Throws if no handler is registered for the event.
Dispatch an event synchronously through its handler chain. 1. Looks up handler via multimethod 2. Builds interceptor chain (registered interceptors + handler interceptor) 3. Executes chain via hive.events.interceptor/execute 4. Executes effects via hive.events.fx/do-fx Returns the final context with :coeffects and :effects. Throws if no handler is registered for the event.
(event-id event)Extract the event-id (first element) from an event vector. This is the dispatch function for the handle multimethod.
Extract the event-id (first element) from an event vector. This is the dispatch function for the handle multimethod.
(expand-batch-macro ops)Expand batch command operations into fan-out sub-operations.
A batch operation has :command ["batch" "actual-cmd"]. Array parameters (excluding known-array-params) are fanned out into individual sub-operations. Scalar parameters are broadcast to all sub-ops.
Pure function — no side effects.
Rules:
Example: (expand-batch-macro [{:id "op-1" :tool "memory" :command ["batch" "add"] :content ["note1" "note2"] :type "note"}]) ;; => [{:id "op-1.0" :tool "memory" :command "add" :content "note1" :type "note"} ;; {:id "op-1.1" :tool "memory" :command "add" :content "note2" :type "note"} ;; {:id "op-1" :tool :noop :command :join :depends_on ["op-1.0" "op-1.1"]}]
Expand batch command operations into fan-out sub-operations.
A batch operation has :command ["batch" "actual-cmd"].
Array parameters (excluding known-array-params) are fanned out
into individual sub-operations. Scalar parameters are broadcast
to all sub-ops.
Pure function — no side effects.
Rules:
- Non-batch ops pass through unchanged
- Only ONE array param can be fanned out per batch op (ambiguity error otherwise)
- Empty fan-out array is an error
- A join operation is appended that depends on all sub-ops
Example:
(expand-batch-macro [{:id "op-1" :tool "memory" :command ["batch" "add"]
:content ["note1" "note2"] :type "note"}])
;; => [{:id "op-1.0" :tool "memory" :command "add" :content "note1" :type "note"}
;; {:id "op-1.1" :tool "memory" :command "add" :content "note2" :type "note"}
;; {:id "op-1" :tool :noop :command :join :depends_on ["op-1.0" "op-1.1"]}]Multimethod for event handling. Dispatches on (first event).
Methods are installed via register-handler!, not directly via defmethod.
This allows attaching interceptors alongside the handler function.
Each method returns: {:handler fn, :interceptors [...]} where handler is (fn [coeffects event] effects-map).
Multimethod for event handling. Dispatches on (first event).
Methods are installed via `register-handler!`, not directly via `defmethod`.
This allows attaching interceptors alongside the handler function.
Each method returns: {:handler fn, :interceptors [...]}
where handler is (fn [coeffects event] effects-map).(handler-registered? event-id)Check if a handler is registered for the given event-id.
Check if a handler is registered for the given event-id.
(normalize-op op)Normalize a single operation map for multi-op compilation.
Pure function — no side effects.
Example: (normalize-op {"tool" "memory" "command" "add" "id" "op-1"}) ;; => {:tool "memory" :command "add" :id "op-1" :depends_on []}
(normalize-op {:tool "kg" :command "edge"}) ;; => {:tool "kg" :command "edge" :id "op-<uuid>" :depends_on []}
Normalize a single operation map for multi-op compilation.
- Converts string keys to keywords
- Auto-generates :id if missing
- Normalizes :depends_on to vector of strings
Pure function — no side effects.
Example:
(normalize-op {"tool" "memory" "command" "add" "id" "op-1"})
;; => {:tool "memory" :command "add" :id "op-1" :depends_on []}
(normalize-op {:tool "kg" :command "edge"})
;; => {:tool "kg" :command "edge" :id "op-<uuid>" :depends_on []}(register-handler! event-id handler-fn)(register-handler! event-id interceptors handler-fn)Register an event handler with optional interceptors.
Two arities:
Handler signature: (fn [coeffects event] effects-map) Interceptors: vector of interceptor maps (from ->interceptor)
Register an event handler with optional interceptors. Two arities: - (register-handler! event-id handler-fn) - (register-handler! event-id interceptors handler-fn) Handler signature: (fn [coeffects event] effects-map) Interceptors: vector of interceptor maps (from ->interceptor)
(remove-handler! event-id)Remove a registered event handler.
Remove a registered event handler.
(validate-ops ops)Validate an operations vector for multi-op execution.
Returns {:valid true} or {:valid false :errors [...]}.
Checks:
Pure function — no side effects, no IO.
Example: (validate-ops [{:id "op-1" :tool "memory" :command "add"} {:id "op-2" :tool "kg" :command "edge" :depends_on ["op-1"]}]) ;; => {:valid true}
(validate-ops [{:id "op-1" :tool "memory" :depends_on ["op-2"]} {:id "op-2" :tool "kg" :depends_on ["op-1"]}]) ;; => {:valid false :errors ["Circular dependency detected among: op-1, op-2"]}
Validate an operations vector for multi-op execution.
Returns {:valid true} or {:valid false :errors [...]}.
Checks:
- All ops have :id and :tool
- IDs are unique
- :depends_on references exist (no dangling refs)
- No self-dependencies
- No circular dependencies (Kahn's algorithm)
Pure function — no side effects, no IO.
Example:
(validate-ops [{:id "op-1" :tool "memory" :command "add"}
{:id "op-2" :tool "kg" :command "edge" :depends_on ["op-1"]}])
;; => {:valid true}
(validate-ops [{:id "op-1" :tool "memory" :depends_on ["op-2"]}
{:id "op-2" :tool "kg" :depends_on ["op-1"]}])
;; => {:valid false :errors ["Circular dependency detected among: op-1, op-2"]}cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |