# How to Implement Rule-Based Reasoning with Semantica's ReteEngine: A Complete Guide

> Implement rule-based reasoning with Semantica's ReteEngine. Learn efficient forward-chaining inference using AlphaNode, BetaNode, and TerminalNode for optimized pattern matching.

- Repository: [Semantica /semantica](https://github.com/semantica-agi/semantica)
- Tags: how-to-guide
- Published: 2026-08-22

---

**Semantica's ReteEngine implements incremental pattern matching through a network of AlphaNode, BetaNode, and TerminalNode classes to perform efficient forward-chaining inference over working memory facts.**

Rule-based reasoning enables AI systems to derive new knowledge from existing facts using declarative if-then logic. The `semantica-agi/semantica` repository provides a production-ready **ReteEngine** in [`semantica/reasoning/rete_engine.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/rete_engine.py) that implements the classic Rete algorithm for efficient pattern matching. This guide demonstrates how to construct inference networks, propagate facts, and execute rules using the engine's core APIs.

## Understanding the Rete Network Architecture

The Rete algorithm optimizes rule evaluation by sharing nodes across multiple patterns and maintaining partial matches in working memory. Semantica's implementation organizes the network into three distinct node types that collaborate to filter, join, and activate rule conclusions.

### AlphaNode (Condition Filter)

**AlphaNode** instances represent the first layer of the network, storing individual rule conditions and the facts that satisfy them. According to the source code in [`semantica/reasoning/rete_engine.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/rete_engine.py), each alpha node maintains a condition pattern and a collection of matching facts【/semantica/reasoning/rete_engine.py#L64-L78】. When facts enter the system, the engine evaluates them against alpha nodes to determine which conditions they satisfy.

### BetaNode (Conjunctive Join)

**BetaNode** objects perform the critical join operation that enables conjunctive rule bodies. These nodes take inputs from two predecessor nodes—typically alpha nodes or other beta nodes—and produce combined matches when their constraints align. The implementation in [`semantica/reasoning/rete_engine.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/rete_engine.py) handles the cross-product of partial matches to build complex logical conjunctions【/semantica/reasoning/rete_engine.py#L85-L100】.

### TerminalNode (Rule Activation)

**TerminalNode** instances represent the rule head and trigger when all conditions in a rule's body are satisfied. These nodes store the rule's conclusion and record **Match** instances whenever their predecessor chain produces valid combinations【/semantica/reasoning/rete_engine.py#L107-L115】. Terminal nodes serve as the output collection points for the inference engine.

## Building and Initializing the ReteEngine

The `ReteEngine` class serves as the primary interface for rule-based reasoning. Initialize the engine to create an empty working memory and an unpopulated network structure【/semantica/reasoning/rete_engine.py#L120-L135】.

```python
from semantica.reasoning import ReteEngine, Rule, Fact

# Initialize the engine with empty working memory

engine = ReteEngine()

# The engine now contains:

# - self.network: empty node structure

# - self.facts: list for working memory storage

# - self.tracker: progress monitoring for diagnostics

```

The engine also provides `reset()` to clear working memory and all node caches, plus `get_network_stats()` to retrieve node counts and fact storage metrics【/semantica/reasoning/rete_engine.py#L62-L73】.

## Defining Rules and Facts

Rules in Semantica follow a declarative pattern using the **Rule** dataclass defined in [`semantica/reasoning/reasoner.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/reasoner.py). Each rule requires a unique identifier, name, list of conditions, and a conclusion pattern【/semantica/reasoning/reasoner.py#L24-L33】.

```python

# Define a rule: IF Person(x) AND HasPet(x, y) THEN OwnerOfPet(x, y)

rule = Rule(
    rule_id="r1",
    name="Owner inference",
    conditions=[
        ("Person", ["?x"]),          # First condition with variable binding

        ("HasPet", ["?x", "?y"]),    # Second condition sharing variable ?x

    ],
    conclusion=("OwnerOfPet", ["?x", "?y"]),
)

# Create facts to populate working memory

fact1 = Fact("f1", "Person", ["Alice"])
fact2 = Fact("f2", "HasPet", ["Alice", "Fluffy"])

```

**Fact** objects encapsulate assertions with unique identifiers, predicate types, and argument lists that ground the logic to specific values.

## Constructing the Network

Before the engine can perform inference, rules must be compiled into the Rete network structure. The `build_network()` method iterates over rule conditions to instantiate the appropriate node topology【/semantica/reasoning/rete_engine.py#L92-L122】.

```python

# Compile rules into alpha/beta/terminal node structure

engine.build_network([rule])

```

Internally, `_add_rule_to_network` performs the following construction steps:

1. Creates one **AlphaNode** for each condition in the rule body
2. Chains **BetaNode** instances between alpha nodes when multiple conditions exist (handling the conjunctive logic)
3. Attaches a **TerminalNode** to the final join node to store the rule's conclusion

Alternatively, rules can be added incrementally using `add_rule()`, which invokes the same network construction logic for individual rules.

## Propagating Facts and Pattern Matching

Fact insertion triggers the incremental pattern matching that makes the Rete algorithm efficient. The `add_fact()` method stores facts in working memory and initiates network propagation through `_propagate_fact()`【/semantica/reasoning/rete_engine.py#L24-L65】.

```python

# Insert facts into working memory and propagate through network

engine.add_fact(fact1)
engine.add_fact(fact2)

```

The propagation algorithm executes recursively:

1. **Alpha evaluation**: The fact visits all alpha nodes; matching nodes store the fact in their local memory
2. **Beta joining**: Alpha nodes with new facts notify successor beta nodes, which attempt joins with counterparts
3. **Terminal activation**: When beta joins succeed, they propagate to terminal nodes, which instantiate **Match** objects

This incremental approach ensures the engine never re-evaluates conditions against the entire fact base, maintaining performance even as working memory grows.

## Executing Inferred Conclusions

After propagation completes, retrieve and execute rule activations using the engine's query methods. `match_patterns()` collects all **Match** instances from terminal nodes across the network, while `execute_matches()` evaluates the associated conclusions【/semantica/reasoning/rete_engine.py#L66-L80】【/semantica/reasoning/rete_engine.py#L14-L27】.

```python

# Retrieve all pattern matches from terminal nodes

matches = engine.match_patterns()

# Execute matched rules to obtain conclusions

results = engine.execute_matches(matches)

print("Inferred conclusions:", results)

# Output: [('OwnerOfPet', ['Alice', 'Fluffy'])]

```

The current implementation returns the stored conclusion directly, though the architecture supports custom handler logic for complex action execution.

## Advanced Customization

The engine's design separates generic Rete mechanics from domain-specific logic through method overriding. For custom condition evaluation, override the `_matches()` method in **AlphaNode** subclasses. For specialized join semantics, override `_can_join()` in **BetaNode** implementations.

The **Reasoner** façade in [`semantica/reasoning/reasoner.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/reasoner.py) provides a higher-level interface that orchestrates rule parsing, fact management, and forward-chaining invocation【/semantica/reasoning/reasoner.py#L24-L38】, though direct ReteEngine usage offers maximum control over network topology and execution semantics.

## Summary

- **ReteEngine** in [`semantica/reasoning/rete_engine.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/rete_engine.py) provides the core forward-chaining implementation using alpha, beta, and terminal nodes.
- **AlphaNode** filters individual facts against single conditions, while **BetaNode** joins partial matches to handle conjunctive rule bodies.
- **Rule** and **Fact** dataclasses in [`reasoner.py`](https://github.com/semantica-agi/semantica/blob/main/reasoner.py) define the declarative logic and ground assertions respectively.
- `build_network()` compiles rules into the Rete topology; `add_fact()` triggers incremental pattern matching via `_propagate_fact()`.
- `match_patterns()` and `execute_matches()` retrieve and materialize inferred conclusions from terminal node activations.

## Frequently Asked Questions

### How does Semantica's ReteEngine handle complex multi-condition rules?

The engine chains **BetaNode** instances between **AlphaNode** outputs to handle conjunctions. Each beta node joins matches from its two predecessors, enabling the network to represent arbitrary logical AND combinations across rule conditions【/semantica/reasoning/rete_engine.py#L85-L100】.

### What is the difference between build_network() and add_rule()?

`build_network()` accepts a list of rules and compiles them into the node structure in a single operation, while `add_rule()` processes individual rules incrementally. Both methods invoke `_add_rule_to_network()` internally to create the alpha-beta-terminal node chains【/semantica/reasoning/rete_engine.py#L92-L122】.

### Can the ReteEngine handle negation or disjunction in rules?

The current implementation focuses on positive conjunctions through **BetaNode** joins. Custom logic would require extending the node classes; override `_can_join()` in **BetaNode** for negation-as-failure semantics, or create multiple terminal paths from different alpha nodes for disjunctive logic.

### How do I reset the engine state between inference sessions?

Call `engine.reset()` to clear the working memory (`self.facts`) and purge all cached partial matches from alpha and beta nodes. This returns the network to its initial state without requiring reconstruction of the rule topology【/semantica/reasoning/rete_engine.py#L62-L73】.