Causal State Machine
The Causal State Machine (CSM) is the connector between a Causaloid’s verdict and an effect on the outside world. It lives in deep_causality/src/types/csm_types and is built around two ideas: a state is “active” when its Causaloid evaluates to an active effect; an action is a function that runs when its paired state is active.
What it is
Section titled “What it is”A CSM holds a thread-safe map of (CausalState, CausalAction) pairs:
pub struct CSM<I, O, C>where I: Default + Clone, O: CsmEvaluable + Default + Debug + Clone, C: Clone,{ state_actions: Arc<RwLock<CSMMap<I, O, C>>>,}The constructor takes a slice of pairs. The map sits behind Arc<RwLock<...>>, so the CSM is shareable across threads and can grow or shrink at runtime through add_state, remove_state, and update_state, each implemented in its own submodule.
States and actions
Section titled “States and actions”A CausalState<I, O, C> is small. It carries:
- an integer
id; - a
versionfor tracking iterations of the same logical state; - a
PropagatingEffect<I>of pre-bound data; - the
Causaloidthat decides whether this state is active; - an optional
UncertainParameterfor states whose Causaloid emits an uncertain effect.
A CausalAction is smaller still:
pub struct CausalAction { action: fn() -> Result<(), ActionError>, description: &'static str, version: usize,}action.fire() invokes the function pointer. The action surface is kept minimal on purpose: the conditional logic lives in the Causaloid, not in the action.
Evaluation
Section titled “Evaluation”Two entry points are exposed:
eval_single_state(id, data)runs the Causaloid for one state against caller-supplied data.eval_all_states()walks every registered state and runs its Causaloid against the data already bound to the state.
In both cases the effect returned by the Causaloid is inspected:
let is_active = match effect.value() { Some(val) => val .is_active(state.uncertain_parameter().as_ref()) .map_err(CsmError::Causal)?, // No value (a None effect, a command, or an error) is inactive for action firing. None => false,};
if is_active { self.fire_action_with_ethos_check(state, action, effect)?;}is_active is delegated to the CsmEvaluable trait. A deterministic effect resolves to true or false directly; an uncertain effect runs its hypothesis test against the state’s UncertainParameter. Anything that is not a value (relay, error, none) is treated as inactive and fires nothing.
Read fire_action_with_ethos_check as a name the refactor left behind. The helper fires the action directly; the Effect Ethos moved to its own crate, so an application that wants a policy gate calls evaluate_action itself before firing.
When to reach for it
Section titled “When to reach for it”A CSM fits whenever the question “should this fire?” has to follow a causal verdict rather than a fixed threshold. Common shapes:
- sensor monitoring with thresholds that depend on context, not on the latest reading alone;
- alert routing where the alert depends on a pattern across signals;
- control loops in which an action should run only when an uncertain condition clears a confidence bar.
The CSM does not own a scheduler. The host application decides when to call eval_*. That keeps it cheap to embed in async runtimes, batch jobs, or hard real-time loops.
See also
Section titled “See also”- Example: sensor monitoring with CSM, backed by
examples/csm_examples/csm_basic. - Concept: Causaloid, the unit that supplies the verdict each state acts on.
- Concept: Effect Ethos, for policy checks that should gate an action before it fires.
- Concept: Uncertainty, for the hypothesis tests applied when a state’s Causaloid emits an uncertain effect.