Skip to main content

SiftEngine

fabula::engine -- pattern registration, batch evaluation, incremental matching, gap analysis, and pattern lifecycle management.

SiftEngine<N, L, V, T>​

The sift engine, generic over four independent type parameters. Maintains registered patterns and partial match state. Decoupled from DataSource -- the engine stores patterns and partial matches using the type parameters directly. Methods that need graph access take &impl DataSource as a parameter.

// Explicit type parameters:
let _engine: SiftEngine<String, String, MemValue, i64> = SiftEngine::new();

// Or use the SiftEngineFor alias (extracts types from a DataSource):
let mut engine: SiftEngineFor<MemGraph> = SiftEngine::new();

engine.register(
PatternBuilder::new("example")
.stage("e", |s| {
s.edge("e", "type".into(), MemValue::Str("harm".into()))
})
.build(),
);

Type parameters​

ParameterBounds (lifecycle methods)Bounds (evaluation methods)Description
NEq + Hash + Clone + DebugsameNode ID type
LEq + Hash + Clone + DebugsameEdge label type
VPartialEq + PartialOrd + Clone + Debug + HashsameValue type
TOrd + Clone + Debug + Hash+ Sub<Output=T> + NumericTimeTime type

Lifecycle methods (register, tick, enable/disable) require the lighter bounds. Evaluation methods (evaluate, on_edge_added, why_not) additionally require T: Sub<Output=T> + NumericTime for metric gap computation.

SiftEngineFor<DS> alias​

Convenience type alias that extracts type parameters from a DataSource implementation:

pub type SiftEngineFor<DS> = SiftEngine<
<DS as DataSource>::N,
<DS as DataSource>::L,
<DS as DataSource>::V,
<DS as DataSource>::T,
>;

Use this when you have a specific DataSource type and want terser declarations.


Lifecycle methods​

These methods require only the lighter type bounds (no Sub or NumericTime).

SiftEngine::new​

Creates a new empty engine with no patterns and no partial matches.

pub fn new() -> Self

Returns: SiftEngine<N, L, V, T>


register​

Registers a pattern. Returns its index in the internal pattern list.

pub fn register(&mut self, pattern: Pattern<L, V>) -> usize
ParameterTypeDescription
patternPattern<L, V>The compiled pattern to register.

Returns: usize -- the pattern's index (used by pattern_metrics, set_pattern_enabled, etc.).


patterns​

Returns a slice of all registered patterns.

pub fn patterns(&self) -> &[Pattern<L, V>]

partial_matches​

Returns a slice of all partial matches (including completed and dead ones, until drained/cleaned).

pub fn partial_matches(&self) -> &[PartialMatch<N, V, T>]

active_matches_for​

Returns active partial matches for a specific pattern (by name).

pub fn active_matches_for(&self, name: &str) -> Vec<&PartialMatch<N, V, T>>
ParameterTypeDescription
name&strPattern name to filter by.

drain_completed​

Removes all completed matches from internal storage and returns them.

pub fn drain_completed(&mut self) -> Vec<Match<N, V, T>>

Returns: Vec<Match<N, V, T>> -- completed matches removed from the engine.


stats​

Returns a reference to cumulative operation counters.

pub fn stats(&self) -> &EngineStats

reset_stats​

Zeroes all operation counters.

pub fn reset_stats(&mut self)

tick​

Advance the tick counter by one. Call once per simulation step. Used for staleness detection. Does NOT produce a delta summary -- use end_tick for the happy path, or tick_delta with manually collected events.

pub fn tick(&mut self)

end_tick​

End the current tick: increments the tick counter, scans for expired partial matches (deadline exceeded), builds a TickDelta from accumulated events, and clears the accumulators.

This is the happy-path API for GM consumers. Call on_edge_added() for each edge in the tick (events accumulate internally), then call end_tick() to get the summary and any expiry events.

pub fn end_tick(&mut self, stale_threshold: u64) -> (TickDelta, Vec<SiftEvent<N, V>>)
ParameterTypeDescription
stale_thresholdu64Ticks since last advancement to consider a pattern "stalled."

Returns: (TickDelta, Vec<SiftEvent<N, V>>) -- the tick summary and any SiftEvent::Expired events for partial matches that exceeded their pattern's deadline_ticks.

The expiry scan runs before the delta is built: for each active PM whose pattern has a deadline_ticks, if current_tick - pm.created_at_tick > deadline_ticks, the PM is killed and an Expired event is emitted. The expired PM's pattern name is included in TickDelta.expired.

let mut engine: SiftEngineFor<MemGraph> = SiftEngine::new();
engine.register(
PatternBuilder::new("offer_accept")
.stage("e1", |s| {
s.edge("e1", "type".into(), MemValue::Str("offer".into()))
})
.stage("e2", |s| {
s.edge("e2", "type".into(), MemValue::Str("accept".into()))
})
.deadline(10)
.build(),
);

let mut graph = MemGraph::new();
graph.add_str("evt1", "type", "offer", 1);
graph.set_time(20);

// Feed edges and end the tick
let interval = Interval::open(1);
engine.on_edge_added(
&graph,
&"evt1".to_string(),
&"type".to_string(),
&MemValue::Str("offer".into()),
&interval,
);
let (delta, expired_events) = engine.end_tick(50);
if !delta.stalled.is_empty() { /* alert GM about stale plants */ }
for ev in &expired_events {
if let SiftEvent::Expired {
pattern,
stage_reached,
ticks_elapsed,
..
} = ev
{
println!(
"{} expired at stage {} after {} ticks",
pattern, stage_reached, ticks_elapsed
);
}
}

current_tick​

Returns the current tick counter value.

pub fn current_tick(&self) -> u64

set_pattern_enabled​

Enable or disable a pattern. Disabled patterns are skipped during evaluate() and on_edge_added(). When disabling, all active PMs for the pattern are killed immediately (Rete convention).

pub fn set_pattern_enabled(&mut self, idx: usize, enabled: bool)
ParameterTypeDescription
idxusizePattern index (from register()).
enabledboolWhether to enable or disable.

is_pattern_enabled​

Check if a pattern is currently enabled.

pub fn is_pattern_enabled(&self, idx: usize) -> bool

deregister​

Soft-delete a pattern. Disables it and kills all its PMs. The pattern stays in the Vec (index stability) but will never match again.

pub fn deregister(&mut self, idx: usize)

pattern_metrics​

Per-pattern lifecycle metrics. Returns None if the index is out of bounds.

pub fn pattern_metrics(&self, idx: usize) -> Option<PatternMetrics>

stale_patterns​

Find patterns that have not advanced for at least threshold ticks but still have active partial matches (stale plants).

pub fn stale_patterns(&self, threshold: u64) -> Vec<usize>
ParameterTypeDescription
thresholdu64Minimum ticks since last advancement.

Returns: Vec<usize> -- indices of stale patterns.


register_plant_payoff​

Register a plant/payoff pair for Chekhov's gun tracking. The plant pattern is narrative setup; the payoff pattern is the resolution. shared_binding optionally constrains the pair: the payoff only counts as resolving the plant if both share a binding with this variable name pointing to the same entity.

pub fn register_plant_payoff(
&mut self,
plant_idx: usize,
payoff_idx: usize,
shared_binding: Option<String>,
)
ParameterTypeDescription
plant_idxusizePattern index of the plant (setup).
payoff_idxusizePattern index of the payoff (resolution).
shared_bindingOption<String>Variable that must match across the pair (e.g., same character).

plant_payoff_pairs​

Returns all registered plant/payoff pairs.

pub fn plant_payoff_pairs(&self) -> &[PlantPayoffPair]

plant_status​

Status of all plant/payoff pairs. Shows which setups are unresolved, stale, or paid off.

pub fn plant_status(&self, stale_threshold: u64) -> Vec<PlantStatus>
ParameterTypeDescription
stale_thresholdu64Ticks threshold for staleness detection.

Returns: Vec<PlantStatus>


Evaluation methods​

These methods require the full bounds: T: Sub<Output=T> + NumericTime. They take &impl DataSource as a parameter -- the engine is not coupled to any specific data source.

evaluate​

Batch evaluation: finds all complete matches in the current graph state. Does not modify engine state.

pub fn evaluate(&self, ds: &(impl DataSource<N=N, L=L, V=V, T=T> + ?Sized)) -> Vec<Match<N, V, T>>
ParameterTypeDescription
ds&impl DataSourceThe data source to evaluate against.

Returns: Vec<Match<N, V, T>> -- all complete matches found.


on_edge_added​

Incremental evaluation: processes a newly added edge. Executes in four phases:

  1. Negation check -- tests existing partial matches for negation kills.
  2. Initiation -- tries to start new partial matches (first stage).
  3. Advancement -- tries to advance existing active partial matches.
  4. Cleanup -- removes dead partial matches.
pub fn on_edge_added(
&mut self,
ds: &(impl DataSource<N=N, L=L, V=V, T=T> + ?Sized),
source: &N,
label: &L,
value: &V,
interval: &Interval<T>,
) -> Vec<SiftEvent<N, V>>
ParameterTypeDescription
ds&impl DataSourceThe data source (for secondary clause validation).
source&NSource node of the new edge.
label&LLabel of the new edge.
value&VTarget value of the new edge.
interval&Interval<T>Validity interval of the new edge.

Returns: Vec<SiftEvent<N, V>> -- events produced by this edge.


why_not​

Gap analysis: clause-by-clause analysis of why a pattern has not matched. Stops at the first unmatched stage.

pub fn why_not(
&self,
ds: &(impl DataSource<N=N, L=L, V=V, T=T> + ?Sized),
pattern_name: &str,
) -> Option<GapAnalysis>
ParameterTypeDescription
ds&impl DataSourceThe data source to analyze against.
pattern_name&strName of the pattern to analyze.

Returns: Option<GapAnalysis> -- None if no pattern with that name exists.


tick_delta​

Compute a delta summary from externally collected events. Pass the events returned by on_edge_added() and a staleness threshold.

pub fn tick_delta(
&self,
events: &[SiftEvent<N, V>],
stale_threshold: u64,
) -> TickDelta
ParameterTypeDescription
events&[SiftEvent<N, V>]Events from on_edge_added() calls this tick.
stale_thresholdu64Ticks threshold for staleness detection.

Returns: TickDelta


Trait implementations​

TraitNotes
DefaultEquivalent to SiftEngine::new().
CloneManual impl. Creates an independent copy of all state. Tick accumulators are intentionally empty in the clone (forked engine starts fresh).

Match<N, V, T>​

A complete match -- all stages satisfied, temporal constraints met, negation windows clear.

pub struct Match<N: Debug, V: Debug, T> {
pub pattern: String,
pub pattern_idx: Option<usize>,
pub bindings: HashMap<String, BoundValue<N, V>>,
pub intervals: HashMap<String, Interval<T>>,
pub metadata: HashMap<String, String>,
}
FieldTypeDescription
patternStringName of the matched pattern.
pattern_idxOption<usize>Index of the pattern in the engine's pattern list. Some for engine-produced matches, None for manually constructed ones.
bindingsHashMap<String, BoundValue<N, V>>Variable name to bound value.
intervalsHashMap<String, Interval<T>>Intervals of matched stage anchors (keyed by anchor variable name). Same as PartialMatch::intervals.
metadataHashMap<String, String>Metadata copied from the pattern at match time. Lets consumers inspect pattern tags without looking up the pattern by name.

BoundValue<N, V>​

A value bound to a variable in a match.

pub enum BoundValue<N: Debug, V: Debug> {
Node(N),
Value(V),
}
VariantDescription
Node(N)A graph node (traversable as a source in subsequent clauses).
Value(V)A data value (string, number, boolean -- not traversable).

Trait implementations: Debug, Clone, Hash.


PartialMatch<N, V, T>​

A partial match -- some stages satisfied, waiting for more events.

pub struct PartialMatch<N: Debug + Clone, V: Debug + Clone, T: Clone> {
pub pattern_idx: usize,
pub bindings: HashMap<String, BoundValue<N, V>>,
pub intervals: HashMap<String, Interval<T>>,
pub next_stage: usize,
pub state: MatchState,
pub id: usize,
pub created_at: T,
pub created_at_tick: u64,
pub fingerprint: u64,
pub repetition_count: u32,
pub matched_stages: u64,
}
FieldTypeDescription
pattern_idxusizeIndex of the pattern in the engine's pattern list.
bindingsHashMap<String, BoundValue<N, V>>Variables bound so far.
intervalsHashMap<String, Interval<T>>Intervals of matched stage anchors (keyed by anchor variable name).
next_stageusizeIndex of the next stage to match (0-indexed).
stateMatchStateCurrent state of this partial match.
idusizeUnique identifier for tracking.
created_atTTimestamp when this partial match was first initiated. Set from the initiating edge's interval start; inherited from parent on fork. Only meaningful in incremental mode.
created_at_ticku64Engine tick when this partial match was first initiated. Inherited from parent on advancement (not reset). Used by end_tick() for deadline expiry checks: current_tick - created_at_tick > deadline_ticks.
fingerprintu64Precomputed dedup hash of (pattern_idx, next_stage, bindings, intervals, repetition_count, matched_stages). Computed once at creation using order-independent XOR hashing.
repetition_countu32Number of completed sub-pattern occurrences for repeat-range patterns. Incremented each time the PM loops back to the repeat segment start. Zero for non-repeating patterns.
matched_stagesu64Bitmask tracking which stages in an unordered group have been satisfied. Bit i is set when stage i has been matched. Used by the engine to determine which unordered group stages remain and when the group is complete (all bits set). Zero for patterns without unordered groups.

MatchState​

State of a partial match.

pub enum MatchState {
Active,
Complete,
Dead,
}
VariantDescription
ActiveWaiting for the next stage to match.
CompleteAll stages matched.
DeadKilled by a negation window.

Trait implementations: Debug, Clone, Copy, PartialEq, Eq.


SiftEvent<N, V>​

Events emitted by incremental matching via on_edge_added and deadline expiry via end_tick.

pub enum SiftEvent<N: Debug, V: Debug> {
Advanced {
pattern: String,
match_id: usize,
stage_index: usize,
metadata: HashMap<String, String>,
},
Completed {
pattern: String,
match_id: usize,
bindings: HashMap<String, BoundValue<N, V>>,
metadata: HashMap<String, String>,
},
Negated {
pattern: String,
match_id: usize,
clause_label: String,
trigger_source: N,
metadata: HashMap<String, String>,
},
Expired {
pattern: String,
match_id: usize,
bindings: HashMap<String, BoundValue<N, V>>,
stage_reached: usize,
ticks_elapsed: u64,
metadata: HashMap<String, String>,
},
}
VariantFieldsDescription
Advancedpattern, match_id, stage_index, metadataA partial match advanced (new stage satisfied).
Completedpattern, match_id, bindings, metadataA pattern fully matched.
Negatedpattern, match_id, clause_label, trigger_source, metadataA partial match was killed by a negation.
Expiredpattern, match_id, bindings, stage_reached, ticks_elapsed, metadataA partial match exceeded its pattern's deadline_ticks. Emitted by end_tick(), not on_edge_added(). stage_reached is the index of the last matched stage. ticks_elapsed is current_tick - created_at_tick.

All variants carry metadata copied from the pattern at event creation time.


EngineStats​

Cumulative operation counters for performance analysis. Incremented during on_edge_added(). Read with engine.stats(), reset with engine.reset_stats().

FieldTypeDescription
total_on_edge_addedu64Number of on_edge_added() calls.
total_fingerprintsu64Fingerprint work: initial dedup set builds + per-candidate checks.
total_negation_checksu64Negation checks attempted (once per active PM per call).
peak_active_pmsusizeHigh-water mark of active partial matches.

Trait implementations: Debug, Clone, Default.


PatternMetrics​

Per-pattern lifecycle metrics. Returned by pattern_metrics().

FieldTypeDescription
enabledboolWhether the pattern is enabled for matching.
last_advanced_ticku64Last tick at which any PM for this pattern advanced or completed.
completion_countu64Total completions (cumulative).
advancement_countu64Total stage advancements (cumulative).
negation_countu64Total negation kills (cumulative).
active_pm_countusizeNumber of currently active partial matches.

Trait implementations: Debug, Clone, Default.


TickDelta​

Summary of what changed in one tick. Returned by end_tick() or tick_delta(). The GM uses this to assess narrative progress: which patterns are advancing, completing, dying, or stalling.

FieldTypeDescription
advancedVec<String>Patterns that had at least one PM advance this tick.
completedVec<String>Patterns that completed this tick.
negatedVec<String>Patterns that had PMs negated this tick.
expiredVec<String>Patterns that had PMs expire (deadline exceeded) this tick.
stalledVec<String>Patterns with active PMs that haven't advanced for stale_threshold ticks.
active_pm_countusizeTotal active PM count across all patterns.

Trait implementations: Debug, Clone, Default.


PlantPayoffPair​

A registered plant/payoff pair for Chekhov's gun tracking. The plant pattern is narrative setup; the payoff pattern is the resolution.

FieldTypeDescription
plant_idxusizePattern index of the plant (setup).
payoff_idxusizePattern index of the payoff (resolution).
shared_bindingOption<String>Variable that must match across the pair (e.g., same character).

Trait implementations: Debug, Clone.


PlantStatus​

Status of a single plant from plant_status().

FieldTypeDescription
plant_patternStringPlant pattern name.
payoff_patternStringPayoff pattern name.
active_plantsusizeNumber of active plant PMs (unresolved setups).
payoff_completionsu64Number of payoff completions (resolved setups).
ticks_since_plant_advancedu64Ticks since the plant pattern last advanced. High = Chekhov's gun gathering dust.
staleboolWhether the plant is stale (no advancement + active PMs).

Trait implementations: Debug, Clone.


Gap analysis types​

GapAnalysis​

Result of why_not -- clause-by-clause analysis of why a pattern did not match.

pub struct GapAnalysis {
pub pattern: String,
pub stages: Vec<StageAnalysis>,
}
FieldTypeDescription
patternStringName of the analyzed pattern.
stagesVec<StageAnalysis>Per-stage analysis. Stops at the first unmatched stage.

StageAnalysis​

Analysis of a single stage.

FieldTypeDescription
anchorStringStage anchor variable name.
statusStageStatusOverall status of this stage.
clausesVec<ClauseAnalysis>Per-clause analysis.

StageStatus​

Overall status of a stage in gap analysis.

VariantDescription
MatchedAll clauses in this stage matched.
PartiallyMatched { matched, total }Some clauses matched. matched out of total.
UnmatchedNo clauses matched.

ClauseAnalysis​

Analysis of a single clause within a stage.

FieldTypeDescription
descriptionStringHuman-readable clause description.
matchedboolWhether this clause matched.
reasonOption<String>Explanation of why the clause failed, or None if it matched.