[FEATURE] Rule-driven actions with provenance for the reasoning engine
## Problem Statement
The reasoning engine models a `Rule` with a `handler: Optional[Callable]` field
(`semantica/reasoning/reasoner.py:34`), but this field is **never invoked anywhere
in the codebase** — a repo-wide search finds only its declaration. When a rule
matches, `Reasoner.forward_chain()` and `ReteEngine.execute_matches()` only treat
the rule's `conclusion` as a new derived fact string; they cannot trigger any
*action* (writing back to the KG, retracting a fact, emitting an event, or calling
an external tool).
In other words, Semantica today is a *pure inference* engine, not a
*production rule system*. There is no way to say "WHEN this pattern matches, THEN
do X" where X is a side effect rather than just a new fact. This is a capability
gap versus mature rule systems (Drools, CLIPS, pyknow) and limits Semantica's use
in automated knowledge-graph maintenance / agentic workflows.
Separately, `semantica/reasoning/reasoning_provenance.py` imports
`from .reasoning_engine import ReasoningEngine`, but no `ReasoningEngine` class /
`reasoning_engine.py` module exists — the lazy import hides a latent bug.
## Proposed Solution
Introduce a **structured Action layer** driven by rule matches, plus optional
**provenance tracking** for every action-induced change.
**L1 — Action type system.** Define an `Action` abstraction executed when a rule
fires, instead of a single opaque callback:
- `AssertAction` — assert a new fact (optionally write back to `KnowledgeGraph` / `GraphStore`)
- `RetractAction` — retract a fact (truth maintenance)
- `CallAction` — call an external function/tool (wraps the existing `handler`)
- `EmitEventAction` — emit an event for pipeline / agent subscribers
Wire execution into `Reasoner.forward_chain()` and
`ReteEngine.execute_matches()`: when a rule with actions fires, run its actions
with the match bindings.
**L2 — Provenance-aware actions.** When enabled, every fact asserted/retracted by
an action records *which rule, which bindings, when, and at what confidence* via
the existing `semantica.provenance` machinery, and integrates with the existing
`ExplanationGenerator`. This yields **explainable, automated knowledge evolution**
— answer "why did the graph change?" for rule-driven edits. (Also fixes the
dangling `ReasoningEngine` import in `reasoning_provenance.py`.)
## Alternatives Considered
- **Only wire the existing `handler` callback (L0).** Minimal, but leaves a bare
callback with no structured semantics, no KG write-back, and no provenance —
low differentiation.
- **External post-processing of inference results.** Users manually inspect
`InferenceResult`s and mutate the KG themselves. Works today but is ad hoc,
untracked, and not reusable.
## Use Cases
1. **Automated KG maintenance**: WHEN `Person(?x) AND worksAt(?x, ?c)` THEN assert
`Employee(?x)` and write it back to the graph store, with provenance.
2. **Constraint enforcement / repair**: on a SHACL / consistency violation, fire a
rule whose action emits an event or asserts a correction (ties into the recent
SHACL work).
3. **Agentic tool-calling**: WHEN a domain pattern matches THEN a `CallAction`
invokes an external tool, letting the reasoner drive downstream workflows.
## Impact Assessment
- **Who would benefit?** Users building automated / agentic KG pipelines,
anyone needing explainable rule-driven graph edits.
- **Priority**: (deferring to maintainers)
- **Breaking Changes**: No — `actions` is additive; `handler` stays supported
(wrapped as a `CallAction`). Rules without actions behave exactly as today.
- **Dependencies**: Reuses existing `semantica.provenance` and
`ExplanationGenerator`; optional KG write-back via existing `KnowledgeGraph` /
`GraphStore`.
## Implementation Ideas
```python
from semantica.reasoning import Reasoner, Rule, AssertAction
reasoner = Reasoner(provenance=True)
reasoner.add_rule(Rule(
rule_id="r1", name="infer-employee",
conditions=["Person(?x)", "worksAt(?x, ?c)"],
conclusion="Employee(?x)",
actions=[AssertAction("Employee(?x)", write_back=True)],
))
reasoner.add_fact("Person(John)")
reasoner.add_fact("worksAt(John, Acme)")
reasoner.forward_chain() # fires r1 -> asserts Employee(John), records provenance
```
## Additional Context
- Related to the reasoning module (`Reasoner`, `ReteEngine`).
- Latent bug: `reasoning_provenance.py` references a non-existent
`ReasoningEngine` / `reasoning_engine.py`.
- Similar features: Drools / CLIPS / pyknow production-rule engines.
## Contribution
- [x] I'm willing to help implement this feature
- [x] I can help with documentation
- [x] I can help with testing
1 条评论