Pattern subscriptions¶
Behaviors fire on event types by default (@behavior(on=["object.created"])).
For richer triggers — match an event against the current graph and
fire only when a specific structural pattern holds — behaviors
subscribe to a pattern instead.
Pattern subscriptions are a first-class activation primitive, alongside
event-type subscriptions and where= filters. A behavior can use any
combination of the three; all three conditions must hold for the
behavior to fire.
Syntax¶
Patterns are written in a strict subset of Cypher:
@behavior(
name="risk_escalator",
pattern="(c:claim)-[:supports]->(e:evidence) WHERE c.confidence > 0.7",
)
def risk_escalator(event, graph, ctx):
for match in ctx.matches:
claim = match.bindings["c"]
evidence = match.bindings["e"]
...
ctx.matches is a list of Match objects, one per distinct binding
combination that satisfies the pattern. Iteration is the developer's
responsibility — the framework does not collapse matches into a single
fire-per-event; each match is exposed and the behavior body decides
what to do with them.
What the v0.7 subset supports¶
- Node patterns:
(var:type {prop: value, ...}). Properties are equality-only; comparisons go inWHERE. - Relationship patterns:
(a)-[var:rel_type]->(b)and(a)<-[var:rel_type]-(b). Direction is required. - Multi-hop:
(a)-[:r1]->(b)-[:r2]->(c). WHEREclauses: comparisons (=,<>,<,<=,>,>=),AND,NOT,NOT EXISTS { ... }.
The full grammar is enforced by the parser in
activegraph/runtime/patterns.py. Anything outside the subset raises
UnsupportedPatternError
at behavior-registration time, not at match time — the parser
validates the pattern when the decorator runs.
What the subset deliberately refuses¶
The subset is small on purpose. A fuzzy superset of Cypher would let patterns appear to match input they did not actually match, which would corrupt the audit trail that pattern-driven behaviors are designed to preserve. Specifically refused (each with a documented workaround in the error message that fires):
- OR in WHERE clauses. Register two behaviors, one per branch of the disjunction.
RETURN,WITH, multipleMATCH. Patterns observe; they don't compose pipelines. Express the pipeline as multiple behaviors chained through emitted events.- Variable-length paths (
-[*]-). Unbounded match cost. Express as N separate one-hop patterns if the lengths are bounded. OPTIONAL MATCH. No null binding. Register a second behavior whose pattern is the optional sub-pattern.- Aggregation,
UNWIND,UNION. Iterate in the behavior body instead —ctx.matchesis the iteration surface. CREATE,MERGE,SET,DELETE,DETACH. Patterns observe; they don't mutate. Mutations go in the behavior body viagraph.add_object,graph.patch_object,graph.remove_object.
CONTRACT v0.7 #8 locked the subset and is the canonical reference for why each refusal stands.
Composition with event-type and where= subscriptions¶
Pattern subscriptions combine with the other activation conditions:
@behavior(
name="contradiction_detector",
on=["object.created"],
where={"object.type": "claim"},
pattern="(c:claim)-[:contradicts]->(other:claim)",
)
This behavior fires when all three conditions hold: an
object.created event occurred, the new object's type is claim,
and the new claim has an outgoing contradicts edge to another
claim. The pattern is evaluated against the graph at the time the
event fires; the new object is present in the match if the pattern
references it.
When to use the relationship variable¶
(a)-[r:type]->(b) binds r to the relation object so the behavior
body can read its properties. (a)-[:type]->(b) binds nothing; the
relation is part of the match but its properties aren't available.
Use the variable form when the behavior needs to read the relation;
omit it when the relation is just a structural constraint.
Tracing pattern fires¶
Each behavior fire produced by a pattern subscription emits a
pattern.matched event ahead of the behavior.started event. The
trace shows how many matches the pattern produced for that fire:
[pattern.matched] evt_042 contradiction_detector matches=2
[behavior.started] contradiction_detector
The match count is also in the event's payload for downstream code that wants to subscribe to pattern matches without owning the behavior.
Related¶
UnsupportedPatternError— what fires when a pattern uses syntax outside the subset.behaviors— the broader behavior model. Pattern subscriptions are one of three activation conditions.failure-model— the framework's stance on what counts as a recoverable failure. The "refuse rather than fuzzy-match" choice for the pattern subset is one application of the broader principle.