Skip to main content

Patterns from First Principles

A pattern is a template with holes. A match is a filled template. This page builds the mental model piece by piece.


Edges: the atomic unit

Everything in fabula starts with an edge. An edge connects a source node to a target through a labeled relationship, valid over a time interval.

source --[label]--> target   during [start, end)

A graph is a collection of edges. A login event might be three edges sharing a source node:

login1 --[type]-----> "login"       during [1, ∞)
login1 --[user]-----> @alice during [1, ∞)
login1 --[location]--> "new_york" during [1, ∞)

The source (login1) is the event node. The labels (type, user, location) describe its properties. The targets are values ("login", "new_york") or node references (@alice).

Stages: ordered event slots

A pattern has one or more stages, each anchored to an event variable. Each stage says: "find an event that looks like this."

pattern one_stage {
stage e1 {
e1.type = "login"
}
}

This pattern matches every event with type = "login". The stage variable e1 binds to the event node.

Multiple stages are time-ordered. Stage 1 must happen before stage 2:

pattern two_stages {
stage e1 { e1.type = "login" }
stage e2 { e2.type = "logout" }
}

This matches any login followed by any logout. Not very useful yet — it doesn't require the same user.

Variables: the glue

Variables bind to nodes or values during matching. When the same variable appears in multiple stages, it creates a join — the engine forces the variable to bind to the same entity everywhere.

Loading playground...

The variable ?user appears in both stages. Stage 1 binds it to a node; stage 2 requires the same node. Alice logged in (time 1) and logged out (time 3) — match. Bob logged in (time 2) but never logged out — no match.

Without the shared variable, the pattern would match any login followed by any logout, even by different people. The variable is what makes the pattern meaningful.

Clauses: constraints within a stage

Each stage has one or more clauses. Each clause constrains which edges must exist at the event node.

Clause typeDSL syntaxMeaning
Literal matche1.type = "login"Edge with label type must have value "login"
Bindinge1.user -> ?userEdge with label user — bind the target to ?user
Value constrainte1.severity > 3Edge value must satisfy the numeric constraint
Node referencee1.actor -> aliceEdge target must be the specific node alice
Negated clause! e1.type = "admin"Edge with this label/value must NOT exist
Cross-stage comparisone2.score > ?prev_scoreValue must be greater than a previously bound variable

The first clause in a stage is the trigger — it must match the incoming edge. Remaining clauses are verified against the data source using accumulated bindings.

Negation: the exception clause

A negation window says "these clauses must NOT match between two events."

Loading playground...

Alice logged in twice (time 1, time 3) with no logout between — match. Bob logged in (time 2), logged out (time 4), then logged in again (time 5) — the logout between kills the match.

Three negation forms:

  • unless between e1 e2 — no match between two stages
  • unless after e1 — no match after a stage (open-ended)
  • unless (global) — no match anywhere in the pattern's span

Intervals: when things happen

Every edge has a time interval. Stages are implicitly time-ordered: stage 1's start time must be strictly less than stage 2's start time.

For most patterns, implicit ordering is all you need. Explicit Allen constraints are for special cases — "event A happened during event B" or "events A and B overlapped":

temporal inner during outer

See the Allen Visualizer to explore all 13 temporal relations interactively.

Putting it together

Every pattern uses the same five building blocks:

Building blockWhat it does
StagesOrdered event slots — "first this, then that"
ClausesConstraints on what fills each slot
VariablesJoins across stages — "same entity"
NegationException clauses — "unless this happened"
IntervalsTemporal ordering — implicit or explicit

These five primitives compose to express patterns across every domain: narratives, compliance, observability, process mining, cybersecurity.

Where to go next