Home / Projects / Orchestrata Ultimate / Decisions / ADR index / ADR-0027
ADR-0027: Retain Prolog as the reasoning engine
| field | value |
|---|---|
| Status | Accepted |
| Date | 2026-07-24 |
| Register | factory (docs/adr, 4-digit ids) |
| Source | docs/adr/0027-retain-prolog-as-the-reasoning-engine.md — permalink at bdafa201a (unchanged on master since that commit) |
| Wiki register entry | ADR-0027 (wiki status: Accepted) |
The record
- Status: Accepted
- Date: 2026-07-24
- Spec: SPEC-007; SPEC-033 §1, §4
- Relates to: ADR-0005, ADR-0006, ADR-0010, ADR-0025 (whose closing note this decision settles)
Context
ADR-0025 bans dynamic predicate construction in the governance rulebase, and closed with an open question: production policy engines enforce those restrictions natively, so is Prolog the right engine at all? Soufflé requires stratified negation and guarantees termination absent function symbols; OPA/Rego rejects recursion outright and imposes safety rules on every head and negated-goal variable; Cedar cuts expressiveness to a decidable SMT fragment with Lean-verified soundness. On that reading, ADR-0006's projection-completeness check and ADR-0010's stratification lint are reimplementations of a static checker that ships in the box.
That reasoning was drawn from the spec, not the code. Surveying the actual rulebases changes it.
What the codebase actually contains (SWI-Prolog 10.1.9, Janus bridge, 16 rulebases / ~3,096 lines):
- Zero
call(— dynamic goal construction is already absent. assert/retractoccurrences are almost entirely comments documenting the discipline — e.g.routing.pl:7"Python only ever asserts GROUND facts",kg_relevance.pl:14"rules live HERE, consulted; Python asserts ONLY ground facts. Deliberately…". ADR-0005's stateless-view model is already implemented in practice.constitutional.pl:124already documents the tabling×negation hazard: "Do not add:- table assess_harm/3without first replacing\+with…" — codified as REQ-106-067.- Incremental tabling is in production use on exactly the queries that matter:
depends_on_transitive/2,ownership_chain/3,contains/2,shares_requirement/2, allas incremental, withowns/2anddepends_on/2declareddynamic as incrementalso asserts invalidate exactly the affected table entries. - Remaining divergence from ADR-0025: 121
\+and 18 cuts.
Against that, the candidates:
| Engine | Recursion | Negation | Incremental re-evaluation | Static checks shipped | In-process Python |
|---|---|---|---|---|---|
| SWI-Prolog (current) | unbounded | NAF, unchecked | yes — incremental tabling | none | yes, Janus |
| Soufflé (Datalog) | yes, terminating | stratification enforced | no | strong | no — batch compiler, CSV/SQLite facts |
| OPA / Rego | rejected outright | safe by construction | no | strong | sidecar / HTTP |
| Cedar | no | n/a | n/a | Lean-verified SMT fragment | via SDK |
| clingo / ASP | yes | well-founded / stable models | no | grounding checks | yes |
| XSB | yes | well-founded semantics | yes | none | yes (Janus originated there) |
Decision
Retain SWI-Prolog. Build the one genuinely missing static check (ADR-0010's stratification lint, extended per ADR-0025) rather than migrate the engine.
Alternatives rejected
- OPA / Rego — disqualified outright. Rego rejects recursion, and
depends_on_transitive/2andownership_chain/3are recursive transitive closures. They are the core knowledge-graph queries. - Cedar — wrong shape. It answers "may principal P do action A on resource R", not graph reachability; it has no general deduction.
- Soufflé — the only serious contender, and it fails on the property that motivated the switch: it has no incremental evaluation. Meta built Glean precisely because Datalog engines re-derive from scratch. The migration would be: abandon incremental tabling, then import Glean's ownership algebra to rebuild it. A full rewrite to reacquire a capability the current engine already provides.
- clingo / ASP — sound negation semantics and a good Python API, but grounding blowup on large fact sets, and it answers "which stable models exist", which is heavier than the entailment question we ask.
- XSB — comparable capability and better negation semantics (well-founded), but no incremental tabling advantage over SWI and a full port of the Janus integration for no decisive gain.
Consequences
- ADR-0006 and ADR-0010 stand as specified: we own the static checks. With
call/Nalready absent the call graph is fully knowable, so the stratification lint is tractable static analysis over a known graph rather than an open-ended research problem. - ADR-0025's closing note is superseded by this record and must be rewritten: the ban stands on its own merits (it makes ADR-0006 and ADR-0010 sound), not as a symptom of a wrong engine choice.
- The 18 cuts and the un-stratified
\+usages become a concrete remediation backlog rather than a reason to migrate. - We accept the standing risk that SWI does not enforce stratification, mitigated only by our own lint — so the lint is load-bearing and must itself be tested.
- SPEC-007's existing commitment to Prolog is upheld; no spec amendment is required.
Provenance
The engine question was raised by the 2026-07-24 prior-art research and recorded as an open note in ADR-0025. Resolved by surveying the existing rulebases, which showed the discipline largely already practised and incremental tabling already deployed. Ratified by the human, who directed: "stay on prolog".
Related ADRs
Rulings cited
None.