How Semantica's Reasoning Layer Works Without LLMs: Symbolic Rule‑Based Inference

Semantica implements deterministic logical inference through forward chaining, backward chaining, and abductive reasoning using pure symbolic algorithms, eliminating any dependency on large language models.

The semantica‑agi/semantica repository provides a fully deterministic reasoning subsystem that operates entirely on structured facts and logical rules rather than neural networks. This article examines the source code to explain how the reasoning layer processes knowledge, prevents infinite loops, and generates explanations without LLM involvement.

Core Architecture: The Symbolic Engine

The Reasoner Facade

The entry point for all inference operations is the Reasoner class defined in semantica/reasoning/reasoner.py. This façade manages the fact base, rule registry, and inference strategies through three primary modes: forward chaining, backward chaining, and action execution.

Facts are stored as plain strings (e.g., "Person(John)") inside a Python set, providing O(1) membership testing. The add_fact method normalizes dictionary‑style knowledge graph objects into this canonical string format, ensuring consistency across the inference pipeline.

Rule and Action Definitions

Logical rules are encoded as immutable dataclasses in the same reasoner.py file. A Rule instance contains:

  • Conditions: List of predicate patterns to match
  • Conclusion: The derived fact template
  • Priority: Execution precedence for conflict resolution
  • Confidence: Certainty factor for the implication
  • Actions: Side‑effects such as AssertAction, RetractAction, or external function calls

Rules can be defined programmatically as Rule objects or parsed from compact string syntax like IF Person(?x) AND Mortal(?x) THEN Mortal(?x). The _parse_rule_definition method converts these strings into structured Rule instances, enabling declarative knowledge engineering without Python code.

Pattern Matching and Unification

Variable binding operates through regular‑expression‑based pattern matching in the _match_pattern method. When processing a predicate like Person(?x), the engine extracts variable names and maps them to concrete values from the fact base through a variable binding table.

The _substitute method then generates concrete conclusions by replacing variables with their bound values. For example, given the binding ?x = John and the conclusion template Mortal(?x), the system produces the grounded fact Mortal(John).

Forward and Backward Chaining

Forward chaining proceeds iteratively through the Reasoner.forward_chain method. The engine scans each rule’s conditions against the current fact set, generates bindings via pattern matching, and asserts new conclusions until reaching a fixed point.

Backward chaining uses Reasoner.backward_chain to prove specific goals. The algorithm recursively matches the target against known facts and then against rule conclusions, unifying variables to establish proof trees.

from semantica.reasoning import Reasoner

# Initialise the engine

r = Reasoner()

# Add facts

r.add_fact("Person(John)")
r.add_fact("Mortal(Humans)")

# Add a rule: IF Person(?x) THEN Mortal(?x)

r.add_rule("IF Person(?x) THEN Mortal(?x)")

# Run inference

new_facts = r.infer_facts(["Person(John)"])
print(new_facts)          # → ['Mortal(John)']

Activation Tracking: Preventing Infinite Loops

To guarantee termination and idempotent side‑effects, the reasoning layer implements activation tracking. The _make_activation_key method creates unique identifiers for each rule‑binding combination, while _fired_activations maintains a registry of previously executed activations.

This mechanism ensures that actions fire exactly once per distinct binding, even when the same conclusion is derived through multiple paths. Consequently, the system prevents infinite loops in cyclic rule sets while ensuring that external side‑effects (such as database writes or event emissions) occur only when new information is actually inferred.

Abductive Reasoning Without Neural Models

The AbductiveReasoner class in semantica/reasoning/abductive_reasoner.py generates explanatory hypotheses for observations using purely symbolic methods. The engine enumerates rules whose conclusions could explain the observed facts through the _rule_explains_observation method.

Each candidate hypothesis receives a score based on configurable HypothesisRanking strategies—simplicity, plausibility, consistency, and coverage. The system ranks candidates and trims the result set to the configured max_hypotheses without invoking any neural model or LLM API.

from semantica.reasoning import AbductiveReasoner, Observation

ar = AbductiveReasoner(
    max_hypotheses=3,
    ranking_strategy="plausibility"
)

# Observation we want to explain

obs = Observation(
    observation_id="obs1",
    description="Patient has fever and cough"
)

hypotheses = ar.generate_hypotheses([obs])
best = ar.rank_hypotheses(hypotheses)[0]

print(best.explanation)   # e.g. "Hypothesis based on rule: IF Infection(?d) THEN Fever(?d)"

Integrating Actions with Knowledge Graphs

Actions bridge the reasoning engine with external systems. The AssertAction with write_back=True enables bidirectional synchronization between the inference engine and knowledge graphs.

from semantica.reasoning import Reasoner, AssertAction

# Fake graph with simple add_fact method

class SimpleGraph:
    def __init__(self):
        self.entities = []
    def add_fact(self, fact):
        self.entities.append(fact)

graph = SimpleGraph()
r = Reasoner(knowledge_graph=graph)

# Rule that asserts a new fact and writes it back to the graph

r.add_rule({
    "rule_id": "r1",
    "name": "CreateBirthFact",
    "conditions": ["Person(?x)"],
    "conclusion": "Born(?x)",
    "actions": [AssertAction(fact="Born(?x)", write_back=True)]
})

r.add_fact("Person(Alice)")
r.forward_chain()

print(graph.entities)  # → ['Born(Alice)']

Observability and Provenance

The reasoning layer supports deterministic debugging through the optional ProgressTracker utility referenced in the implementation. This component records each inference step, variable binding, and rule activation, creating complete provenance trails without AI‑generated narratives. When integrated, the tracker captures the progression from initial facts through intermediate conclusions to final results, enabling full reproducibility of the symbolic inference chain.

Summary

  • Symbolic foundation: The reasoning layer stores facts as strings in Python sets and processes them through immutable Rule dataclasses in semantica/reasoning/reasoner.py.
  • Deterministic inference: Forward chaining (forward_chain) and backward chaining (backward_chain) derive conclusions using regex‑based pattern matching (_match_pattern) and variable substitution (_substitute).
  • Loop prevention: Activation tracking via _make_activation_key and _fired_activations ensures actions execute exactly once per unique binding, preventing infinite loops.
  • Abductive explanations: The AbductiveReasoner generates and ranks hypotheses using configurable strategies without LLM involvement.
  • External integration: AssertAction and related action classes enable side‑effects such as knowledge graph updates and event emissions.
  • Full observability: Optional progress tracking and provenance logging provide complete visibility into inference steps.

Frequently Asked Questions

How does Semantica's reasoning layer differ from LLM‑based reasoning?

Semantica uses deterministic symbolic algorithms—forward chaining, backward chaining, and abductive logic—operating on structured facts and rules defined in semantica/reasoning/reasoner.py. Unlike LLMs, which generate probabilistic text based on training data, this reasoning layer guarantees reproducible conclusions through exact pattern matching and logical unification, with no neural network inference involved.

Can the reasoning engine handle recursive or cyclic rule sets without infinite loops?

Yes. The implementation uses activation tracking through _make_activation_key and the _fired_activations registry to ensure each distinct rule‑binding combination executes exactly once. This mechanism prevents infinite recursion even when rules reference each other cyclically, while still allowing valid transitive inferences to complete.

What strategies does the abductive reasoner use to rank hypotheses?

According to semantica/reasoning/abductive_reasoner.py, the AbductiveReasoner supports four ranking strategies: simplicity (fewest assumptions), plausibility (prior probability), consistency (conflict with known facts), and coverage (explained observations). These are configured via the ranking_strategy parameter and applied during the rank_hypotheses method execution.

Is it possible to extend the reasoning layer with custom actions?

Yes. The Action class hierarchy in the reasoning module allows custom implementations beyond the built‑in AssertAction and RetractAction. Custom actions can perform arbitrary side‑effects such as API calls, database updates, or event emissions, and they integrate with the activation tracking system to ensure deterministic execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →