Rules & rule store
A rule is a safe (range-restricted) Horn clause with stratified negation-as-failure, authored as JSON. Rules are the only place you encode policy; the reasoner executes them deterministically.
Anatomy of a rule
Section titled “Anatomy of a rule”{ "id": "rule:coordinated_threat", "name": "High-confidence drone convergence is a coordinated threat", "version": 1, "priority": 10, "enabled": true, "if": [ {"pred": "formation", "args": ["?E", "CONVERGENCE"]}, {"pred": "member", "args": ["?E", "?A"]}, {"pred": "member_type", "args": ["?A", "drone"]}, {"pred": "weight", "args": ["?E", "?W"]}, {"compare": ["?W", ">=", 0.8]}, {"not": {"pred": "cleared", "args": ["?A"]}} ], "then": {"pred": "coordinated_threat", "args": ["?E"], "confidence": 0.9}}| Field | Type | Meaning |
|---|---|---|
id | string (required) | Unique rule id. Upserting the same id replaces it. |
name | string | Human label (shown in listings). |
version | int | Author-managed version (default 1). |
priority | int | Higher fires first within a stratum (default 0). |
enabled | bool | Disabled rules are skipped by the reasoner (default true). |
if | list (required) | Body literals — positive atoms, comparisons, negations. |
then | object (required) | Head atom with optional confidence in [0, 1]. |
provenance | object | Free-form metadata stored alongside the rule. |
if/then are the canonical keys; body/head are accepted as aliases.
Variables and constants
Section titled “Variables and constants”Variables are ?-prefixed strings (SPARQL style): ?E, ?A, ?W. Everything
else — numbers, unprefixed strings — is a constant. This makes a formation
label like "CONVERGENCE" unambiguous rather than being mistaken for a variable.
A variable name must match ^\?[A-Za-z_][A-Za-z0-9_]*$. Predicate names must be
snake_case (^[a-z_][A-Za-z0-9_]*$).
Base predicates (EDB)
Section titled “Base predicates (EDB)”Every retrieved hyperedge is projected into these extensional base predicates. Rules read them but may not redefine them as a head:
| Predicate | Produced as | Meaning |
|---|---|---|
hyperedge(E) | hyperedge(edge_idx) | The hyperedge exists. |
formation(E, F) | formation(edge_idx, "CONVERGENCE") | Typed formation label. |
weight(E, W) | weight(edge_idx, 0.91) | Edge weight (rounded to 6 dp). |
at(E, Ts) | at(edge_idx, 1700000000) | Event timestamp (omitted if 0). |
member(E, A) | member(edge_idx, node_id) | A member node of the edge. |
member_type(A, T) | member_type(node_id, "drone") | A member’s type (lowercased). |
A rule head must derive a new predicate (e.g. coordinated_threat). Heads
derive intensional (IDB) predicates that can in turn feed other rules.
Body literals
Section titled “Body literals”A body (if) is a non-empty list. Each element is one of:
Positive atom — {"pred": "member", "args": ["?E", "?A"]}. Binds variables
by matching facts. At least one positive atom is required (it supplies bindings).
Comparison — {"compare": ["?W", ">=", 0.8]}. Operators: >=, <=, >,
<, ==, !=. Both sides must already be bound (or constants). Type-mismatched
comparisons evaluate to false rather than raising.
Negation (as failure) — {"not": {"pred": "cleared", "args": ["?A"]}}. True
when no matching fact exists. Variables inside a negation must be bound by a
positive atom (safe negation).
Confidence
Section titled “Confidence”The head may carry confidence in [0, 1] (default 1.0). A derived fact’s
confidence is rule_confidence × min(premise_confidence), so confidence
degrades monotonically along a derivation chain and never inflates.
Safety (range-restriction)
Section titled “Safety (range-restriction)”parse_rule rejects unsafe rules at author time. A rule is safe only if:
- The body has at least one positive atom.
- Every head variable is bound by a positive body atom.
- Every comparison variable is bound by a positive body atom.
- Every negated variable is bound by a positive body atom.
These guarantee a finite, well-defined derivation. Violations raise
RuleValidationError with a precise message, e.g.:
rule:bad: head variables ['?X'] not bound by any positive body atom (unsafe rule)Stratification
Section titled “Stratification”The whole rule set must be stratifiable: any predicate used under not must
be fully computed in a strictly lower stratum. This rules out negation through
recursion and guarantees a unique, terminating result. A non-stratifiable set is
rejected:
ruleset is not stratifiable: negation through recursion detectedStratification is enforced both by the reasoner and by the RuleStore on every
write, so a stored rule set can never be one the reasoner would reject. See
Reasoning engine → Stratification
for the algorithm.
The rule store
Section titled “The rule store”RuleStore is a durable JSON registry at {db_dir}/rag_rules.json — the same
sidecar pattern HyperMesh uses for models and ingest provenance. Every mutation
re-validates the entire rule set for stratifiability, atomically (temp file +
os.replace + fsync).
from hypermesh.rag.symbolic import RuleStore
store = RuleStore("data/")
# Create / replace (raises RuleValidationError on bad or non-stratifiable rules)out = store.upsert(rule) # -> rule dict + {"stratum": N}
# Dry-run validate without writingreport = store.validate(rule) # -> {"ok": bool, "errors": [...], "stratum"?: int}
store.get("rule:coordinated_threat") # -> rule dict | Nonestore.list() # -> [{id, name, enabled, priority, head, body_size}, ...]store.delete("rule:coordinated_threat") # -> bool
# Parsed views the reasoner consumesstore.all_rules() # -> list[Rule]store.enabled_rules() # -> list[Rule] (enabled only)RAGPipeline reads from the store automatically when you don’t pass ad-hoc
rules=. Pass rules=[...] to override the store for a single query (useful for
what-if analysis and tests).
Methods
Section titled “Methods”| Method | Returns | Notes |
|---|---|---|
upsert(rule_dict) | rule dict + stratum | Validates + stratifies the whole set; raises RuleValidationError. |
validate(rule_dict) | {ok, errors, stratum?} | No write; safe to call from a UI on every keystroke. |
get(rule_id) | dict | None | Raw stored rule. |
list() | list of summaries | {id, name, enabled, priority, version, head, body_size}. |
delete(rule_id) | bool | False if the id wasn’t present. |
all_rules() | list[Rule] | Parsed objects. |
enabled_rules() | list[Rule] | Parsed, enabled only. |
path (property) | str | Absolute path to the JSON registry. |
Next: Reasoning engine.