Rule Semantics¶
Evaluation Pipeline¶
- Parse Locy program.
- Build dependency graph.
- Validate types/schema compatibility.
- Stratify rules.
- Evaluate each stratum to fixpoint.
- Execute command phase (
QUERY,DERIVE,EXPLAIN,ABDUCE,ASSUMEbody).
Two-Phase Execution¶
Locy execution is split into two distinct phases with different execution engines:
Phase 1 — Strata Evaluation (DataFusion)
Rules compile to DataFusion LogicalPlan nodes. The query engine runs them through a fixpoint loop per stratum. Expression functions (similar_to(), etc.) have full access to storage, schema, and the Xervo embedding runtime.
Phase 2 — Command Dispatch (Row-Level)
After strata converge, commands (QUERY, DERIVE, ABDUCE, ASSUME) execute on materialized Vec<Row> facts. WHERE filters use a lightweight row-level evaluator. This path supports vector cosine similarity but not auto-embedding, FTS, or multi-source fusion.
| Context | Execution | Vector | Auto-Embed | FTS |
|---|---|---|---|---|
Rule MATCH ... WHERE/YIELD |
DataFusion | ✓ | ✓ | ✓ |
Rule ALONG / FOLD / post-FOLD WHERE |
DataFusion | ✓ | ✓ | ✓ |
DERIVE ... WHERE |
In-memory | ✓ | ✗ | ✗ |
ABDUCE ... WHERE |
In-memory | ✓ | ✗ | ✗ |
ASSUME ... WHERE |
In-memory | ✓ | ✗ | ✗ |
Semi-Naive Evaluation¶
Within a recursive stratum, Locy only re-evaluates rules using newly derived facts (the delta) rather than all known facts each iteration. This provides exponential speedup for transitive closures:
Iteration 0: delta₀ = base facts from MATCH
Iteration 1: delta₁ = evaluate(rules, delta₀) − known_facts
Iteration 2: delta₂ = evaluate(rules, delta₁) − known_facts
...
Iteration n: deltaₙ = ∅ → fixpoint reached
One exception: a rule whose folded value is read by a self-reference in the same stratum publishes a full per-KEY snapshot rather than a delta, because an aggregate is a whole-relation quantity — see What a self-reference reads below.
Overloaded Rules¶
Multiple CREATE RULE clauses sharing one name define one logical relation. Clauses can be prioritized where supported.
Negation Rules¶
IS NOT requires stratification-safe dependencies. Cyclic negation is rejected at compile time.
If the referenced rule exposes a PROB column, IS NOT becomes probabilistic complement (1 - p) rather than Boolean anti-join. Rules without a PROB column keep standard Boolean negation.
Monotonic Recursion¶
Recursive aggregation requires monotonic operators. Non-monotonic recursive shapes are rejected at compile time with NonMonotonicInRecursion.
Monotonicity is decided by the aggregate registry: the compiler reads the aggregate's monotone_join semilattice flag, falling back to the six built-in M* names (MSUM, MMAX, MMIN, MCOUNT, MNOR, MPROD) only when the registry has no entry for the name. So MIN, MAX, COUNT, COUNT(*) and COLLECT are legal inside recursive strata too; SUM and AVG are non-monotone and rejected.
Monotone does not imply convergent. COUNT, COLLECT, MSUM and MCOUNT are monotone but unbounded — they have no top element. For the ones the fixpoint loop tracks row by row (COUNT, MSUM, MCOUNT) that means a recursive fold can run to max_iterations instead of reaching a fixed point; the iteration cap is the backstop. COLLECT is the exception: it has no row-level accumulator and is assembled after the fixpoint, so it does not itself keep the loop iterating.
What a self-reference reads¶
A rule inside a recursive stratum can reference itself. What that reference binds depends on whether the rule aggregates:
- The rule carries
FOLD— the reference sees one row per KEY, carrying that KEY's folded value as of the previous iteration. A rollup therefore composes one level at a time: a parent folds its children's values, and each child has already folded its own. - The referencing clause carries
ALONG— it sees the pre-fold rows, one per derivation.prev.xaccumulates a value along a single path, and a per-KEY aggregate is not defined per path. This is decided per clause, so a sibling clause of the same rule that folds an inherited value still reads the folded view. - A reference to a lower stratum always reads that rule's published, folded facts. It always has.
CREATE RULE build AS
MATCH (p:Part) WHERE p IS NOT assembly
YIELD KEY p, 0.5 AS b
CREATE RULE build AS
MATCH (p:Part)-[:CONTAINS]->(c:Part)
WHERE c IS build
FOLD b = MPROD(b)
YIELD KEY p, b
For TOP → MID → {L1, L2} with both leaves at 0.5: MID = 0.5 × 0.5 = 0.25,
and TOP folds MID's single value, so TOP = 0.25 too.
The distinction is invisible for an associative aggregate over a bare inherited
column — folding a child's rows and folding its folded value agree for MPROD
and MSUM. It is visible for MCOUNT, which counts a node's children, not
its leaves, and for any fold whose argument is a computed expression.
Convergence follows the value, not just the row count: for these rules the fixpoint has settled when no KEY has been added and no value has moved.
MNOR and MPROD are monotonic and bounded, therefore legal inside recursive strata. They assume independent derivations unless exact_probability is enabled.
Plugin-registered aggregates. Because the verdict comes from the registry, an aggregate registered by a plugin participates in the check on the same terms as a built-in: declaring monotone_join: true makes it usable inside a recursive stratum. Load the plugin first and this holds for rules compiled through session.locy(...) and for rules registered with db.rules().register(...).
It does not extend to reopening a database. Persisted rules are recompiled during open, before any plugin can be added, so a stored rule folding over a plugin aggregate no longer compiles and the open fails naming that rule — unless you open with skip_invalid_locy_rules(true), which drops it with a warning. Rules folding over built-in aggregates (MIN, MAX, COUNT, COLLECT, the M* names) reload normally.
Determinism¶
BEST BY can use deterministic tie-breaking through config (deterministic_best_by = true).
Limits and Guardrails¶
Key guardrails come from LocyConfig:
max_iterationstimeoutmax_derived_bytesmax_explain_depthmax_slg_depthstrict_probability_domainprobability_epsilonexact_probabilitymax_bdd_variablestop_k_proofsdeterministic_best_by
See Errors & Limits for operational guidance.