SWI-Prolog Capability Atlas
This folder is a systematic capability survey of SWI-Prolog — the dialect embedded
in-process by orchestrata as its governance/policy brain — swept from the official
swi-prolog.org documentation on 2026-07-29. Each of the ten
topic pages was written against the actual manual sections and package documents (the exact
URLs read are cited in each page's ## Sources section), not from folklore or secondhand blog
posts. Every page follows the same contract:
- Full predicate reference — every major predicate/directive of the subsystem with its signature, determinism, and semantics explained in full sentences.
- Runnable examples — complete SWI-Prolog code blocks taken or adapted from the manual,
long enough to paste into
swipland run. - A mermaid diagram of the subsystem's architecture or data flow.
## Pitfalls and gotchas— the real caveats the manual documents (and a few it only implies), because these subsystems fail in non-obvious ways.## How orchestrata can leverage this— 3–6 concrete proposals grounded in the real codebase: named rule files (governance.pl,kg_relevance.pl,test_schedule.pl, …), named Python seams (core/prolog_adapter.py,governance/prolog_gate.py,orchestrator/prolog_worker.py), and named infrastructure (the redis-queue container at 10.53.1.10, the Memgraphorchgraphcluster at 10.53.1.160).
The cross-project consolidation of all those leverage sections — deduplicated, ranked, with code sketches — lives in the companion capstone page: Prolog Leverage Roadmap for orchestrata.
Why this folder exists
Orchestrata already runs SWI-Prolog in production: 22 rule files consulted through the janus
bridge cover governance gating, intent NL parsing, symbolic knowledge-graph reasoning, and
CLP(FD) test scheduling. But the live-analysis audit that preceded this sweep found the
project uses a narrow slice of what the runtime ships: no plunit, no Prolog-side threads,
no library(redis), no semweb, no engines, no persistency, tabling only in the KG tier,
CLP(FD) only in one file, DCGs only in one grammar. Each topic page documents one of those
unused (or under-used) subsystems end-to-end so that future design work can reach for the
right primitive with the manual semantics already digested.
The topic pages
| Page | What it gives you |
|---|---|
| Redis from Prolog — library(redis) | SWI's native Redis client: named lazy-connecting servers with auto-reconnect (redis_server/3), commands as callable terms with typed reply mapping and as Type coercions, pipelining, PUB/SUB via library(broadcast), and full consumer-group streams in library(redis_streams). |
| Threads, Message Queues and Concurrency | Native preemptive OS threads sharing only the heap (thread_create/3, thread_join/2), term-copying message queues with unification-based selective receive, thread_wait/2 condition-variable semantics over the dynamic DB, signals, library(thread) high-level parallelism (concurrent_maplist, concurrent_forall, first_solution/3), and library(thread_pool). |
| Unit Testing with plunit | The complete plunit contract: begin_tests/end_tests units as sub-modules, the full test/2 option catalogue (true/fail/throws/error/all/set, forall, setup/cleanup, blocked/fixme, nondet), run_tests and CI wiring, .plt companion files, and library(prolog_coverage). |
| Janus — the Python-Prolog Bridge | Both directions of the bridge orchestrata lives on: query_once/query/apply_once/cmd/consult from Python (with the truth-value and iterator-close contracts), py_call/py_iter/py_module from Prolog, data conversion rules, GIL/engine threading model, and error translation. |
| Tabling (SLG Resolution) and Memoization | :- table in all its modes: variant vs subsumptive, answer subsumption (lattice, po, min/max/sum/first/last), incremental tabling with its dependency graph, monotonic tabling, shared tables across threads, well-founded semantics with tnot/1, and restraints (max_answers, tripwires) for bounded rationality. |
| Constraint Solving — CLP(FD), CLP(B), dif/2 | Integer constraints as true relations (#=, in/ins, labeling/2 with branch-and-bound min/max), global constraints (all_distinct, global_cardinality, cumulative/2), reification, Boolean SAT via CLP(B) (taut/2, sat_count/2), and sound disequality with dif/2. |
| Graphs in Prolog — semweb/RDF, ugraphs, assoc | The C-indexed RDF triple store (rdf/3-4, rdf_reachable, typed rdf11 literals, named graphs, transactions/snapshots, the SPARQL client, crash-safe journal persistency) plus pure in-memory graph algorithms (library(ugraphs): top_sort, transpose, reachable, layers) and AVL maps (library(assoc)). |
| DCGs and Text Processing | How -->/2 translates to difference-list clauses, phrase/2-3 vs call_dcg/3, pushback/semicontext notation, library(dcg/basics) and library(dcg/high_order) (sequence//3, optional//2), and reversible parse/generate grammars. |
| Engines, Coroutining and Attributed Variables | Engines as ~20 KB suspendable Prolog VMs pulled with engine_next/2 (not bound to OS threads), the engine_post/engine_fetch/engine_yield stateful-agent protocol, freeze/2, when/2, frozen/2 diagnostics, and the attributed-variable hooks under all SWI constraint solvers. |
| Modules, Dynamic Predicates and Persistent State | The module system (use_module/2 with except/renaming, reexport facades, meta_predicate/1 specifiers), dynamic/1 and the ISO logical update view under concurrency, library(persistency) journaled facts, library(settings) typed config, and term_expansion/goal_expansion compile-time hooks. |
How the subsystems compose
The pages are written to stand alone, but the subsystems are not islands — SWI-Prolog's power for a system like orchestrata comes from how they stack:
- janus is the membrane. Everything orchestrata does today flows through it
(janus). Every other capability on this map is reachable through that
membrane without new infrastructure: a Python thread calling
query_once/1can transparently hit a tabled predicate, a CLP(FD) model, a DCG, or aredis/3call — the bridge does not care what the goal does. - Tabling and dynamic predicates are two halves of one mechanism.
Incremental tabling (tabling) only invalidates when the dynamic
predicates underneath are declared
as incremental(modules-dynamic-state) — both ends must opt in, and orchestrata's ground-facts-only assertz doctrine is exactly the purity precondition that makes tabling the governance corpus safe. - Threads, queues, and engines are three different concurrency shapes.
OS threads (threads) give preemptive parallelism with copied-term
queues; engines (engines-coroutines) give suspendable
sequential computations with per-pull thread attachment — a frozen proof search or a
resumable counterexample cursor is an engine job, a parallel gate-evaluation pool is a
thread job.
thread_wait/2turns the shared dynamic DB into a condition variable, which is what makes push-based (rather than polled) gating possible. - CLP and DCG are both "relations, not procedures". CLP(FD) constraints (clpfd) work in all directions the way pure DCGs (dcg) parse and generate from the same rules; both replace imperative Python loops with declarative specifications the explain/provenance machinery can reason about.
- Redis and semweb are the two native I/O stores.
library(redis)(redis) reaches the project's hot store directly from rule code; the RDF store (semweb-graphs) offers named-graph snapshots, built-in transitive closure, and a crash-safe journaled audit log — capabilities the hand-rolled ground-fact projections currently reimplement or lack. - plunit closes the loop. Every capability above becomes safely adoptable only when the
rule files have native tests (plunit) — which orchestrata currently has
zero of (
.pltcount in the repo: 0).
Overview diagram
Solid arrows are data paths; dashed annotations mark where orchestrata stands today (USED / UNUSED / PROPOSED). The asymmetry is the whole story: the janus membrane and the tabling/DCG/CLP islands behind it are production-proven, while the entire right-hand column of I/O and lifecycle machinery (redis, semweb, persistency, plunit, threads, engines) is dark.
Orchestrata touchpoint summary
A condensed map of where each subsystem meets the codebase — the leverage roadmap page expands every row into a concrete build item:
| Subsystem | Status in orchestrata | Key touchpoints |
|---|---|---|
| janus | Production, exclusive bridge | core/prolog_adapter.py (ground-facts doctrine, engine pinning), orchestrator/prolog_worker.py (PrologWorker/PrologProxy singletons), governance/prolog_client.py (the one blessed loader) |
| Tabling | Production (KG tier only) | kg_relevance.pl:48-54, knowledge.pl:32,53 incremental tables over per-refinement facts; tools/codebase_db.pl tabled closures; governance corpus untabled; constitutional.pl:133 warns against tabling assess_harm/3 instead of restructuring it |
| CLP(FD) | Production (one file) | test_schedule.pl best_schedule_pairs/4 driven by core/threading/test_scheduler.py; documented crux: conflicting modules forced onto the same worker |
| DCG | Production (one grammar) | intent_grammar.pl + generated intent_lexicon.gen.pl (CI drift check via scripts/gen_intent_lexicon.py), routing.pl parity-tested against INTENT_VOLK_MAPPING |
| plunit | Absent | Zero .plt files; only hits are the vendored janus self-test and an orphaned runner (rtm/scripts/run_plunit_tests.py) pointing at a directory that does not exist |
| Threads/queues | Absent (Prolog-side) | All concurrency is Python threads with janus per-thread engine pinning (~3.4x measured); no thread_create in any project .pl |
| library(redis) | Absent | Redis is the project's hot store (Postman queues, audit queues db 15, receipts) but is Python-only today |
| semweb/RDF | Absent | KG reasoning is hand-rolled ground facts projected from the Memgraph orchgraph cluster; no rdf/3, no named graphs, no journal persistency |
| Engines | Absent | No suspendable computations; deny provenance re-runs the whole proof through explain.pl |
| persistency/settings | Absent | Every fact rebuilt per process; thresholds are magic numbers in resources.pl/completion.pl; no hot-reload of consulted rules (consult-once globals throughout) |
Facts worth remembering
Datapoints from the sweep that repeatedly change design decisions — each is documented in full on its topic page:
- janus failure is silent, not raised:
query_oncereturns{'truth': False}withNonebindings on failure; only-ERR-class conditions raise. An unclosedjanus.query()iterator leaves SWI inconsistent and typically crashes the process — always use the context manager. (janus) - Capitalised names in a goal string are Python-bound inputs — string interpolation
into goals is a documented injection risk and the root of orchestrata's
ground-facts-only assertz doctrine.
apply_once/cmdskip goal-string parsing entirely and are the documented faster path. (janus) - Incremental tabling needs both ends to opt in: an incremental table over a non-incremental dynamic predicate gets no dependency-graph edge and silently never invalidates. Granularity is the whole table, not the clause. (tabling)
- Subsumptive tabling is only sound for pure predicates — the manual's own benchmark drops 13 s / 150 M inferences to 0.015 s / 140 K, but answers come from more general completed tables, so impure predicates give wrong results. (tabling)
- Message queues copy terms both ways and
thread_get_message/1does selective receive: it unifies against queued terms and skips (leaves) non-matching ones. The/3variants withtimeout/deadlinefail silently on expiry —timeout(0)is a poll. (threads) thread_wait/2+thread_update/2turn the dynamic DB into a condition variable — block until a goal over dynamic predicates becomes true, woken per-module on assert/retract. This is the primitive for push-based (zero-poll) gating. (threads)- A named Redis server is one mutex-serialised socket, not a pool;
redis_connect/3handles are unlocked and do not reconnect by default. An all-nil reply makesredis/3fail (semidet) rather than return nulls. (redis) - plunit flags nondeterministic success by default — a supposedly-det test body that
succeeds with a leftover choicepoint is reported unless the test says
nondet;set/1outcomes catch duplicate solutions.cleanupalways runs, even on exceptions; a failingconditionskips where a failingsetuperrors. (plunit) all_distinct/1is domain-consistent and can prove infeasibility before labeling; the manual recommends it over the cheaperall_different/1.labeling/2over finite domains is complete, terminating, and non-redundant, withmin/maxbranch-and-bound built in. (clpfd)cumulative/2requires every task field to be an integer or bounded FD variable —task(Start,Duration,End,Consumption,Id)terms with an unbounded field make it useless. (clpfd)- Engines are ~20 KB suspendable Prolog VMs, not OS threads — they attach to the
calling thread per
engine_next/2pull, andengine_yield/1raisespermission_errorinside a C callback (directly relevant under januspy_callframes). (engines-coroutines) rdf_reachable/3is built-in cycle-safe BFS transitive closure with shortest hop distance; named graphs are a 4th triple dimension with O(1) bulk teardown (rdf_unload_graph/1); the RDF persistency.jrnjournal is a crash-safe replayable log of timestamped assert/retract terms.sparql_query/3speaks SPARQL-over-HTTP only — it cannot talk Bolt to Memgraph. (semweb-graphs)- DCG nonterminals must never be called directly —
-->/2rules compile to clauses with two hidden difference-list arguments;phrase/3type-checks and rejects SWI-7 strings as list arguments (use backquoted text orstring_codes/2); pure grammars run backwards for generation. (dcg) - Modules resolve by filename, not module name,
use_module/1imports are weak symbols (local definitions override), and same-name exports from two modules are a hard error requiringuse_module/2. The ISO logical update view means a running goal sees exactly the clauses that existed when it started. (modules-dynamic-state)
Reading order
For a first pass, read in dependency order rather than table order:
- janus — the membrane everything crosses; understand the truth-value dict, the iterator-close hazard, and why assertz is ground-facts-only.
- modules-dynamic-state — modules,
dynamic/1, and the logical update view; prerequisites for reading any rule file critically. - tabling — the most consequential single capability for the governance corpus (incremental tables = gate decisions as trie lookups).
- plunit — because nothing else should ship untested.
- Then in any order: threads, redis, clpfd, dcg, semweb-graphs, engines-coroutines.
- Finish with the leverage roadmap, which turns the ten
## How orchestrata can leverage thissections into one ranked, deduplicated build plan.
Provenance and maintenance
- Source of truth: the official SWI-Prolog manual and package docs at swi-prolog.org,
read 2026-07-29 (SWI-Prolog 9.x documentation). Each topic page cites the exact
pldoc/man?section=andpldoc/doc_for?object=URLs it was written from. - Project claims (file names, line numbers, container addresses) come from a live analysis of the orchestrata codebase performed the same day; when orchestrata's bridge or rule layout changes, the touchpoint table above and the leverage roadmap are the pages to update — the ten topic pages are project-independent references and should only change when SWI-Prolog itself does.
- Conventions: predicate indicators are written
name/arity, DCG nonterminalsname//arity; all code blocks are SWI-Prolog syntax (SWI-7 strings, dicts where noted).
Sources
The seed table-of-contents pages for the sweep (each topic page cites its own deeper URLs):
- https://www.swi-prolog.org/pldoc/man?section=threads — threads, queues, signalling
- https://www.swi-prolog.org/pldoc/man?section=tabling — SLG resolution, all tabling modes
- https://www.swi-prolog.org/pldoc/man?section=clp — constraint programming overview (clpfd, clpb)
- https://www.swi-prolog.org/pldoc/man?section=DCG — definite clause grammars
- https://www.swi-prolog.org/pldoc/man?section=engines — engines/interactors
- https://www.swi-prolog.org/pldoc/man?section=modules — module system
- https://www.swi-prolog.org/pldoc/man?section=db — dynamic predicates and the update view
- https://www.swi-prolog.org/pldoc/man?section=janus — the janus bidirectional bridge
- https://www.swi-prolog.org/pldoc/doc_for?object=section(%27packages/plunit.html%27) — plunit
- https://www.swi-prolog.org/pldoc/doc_for?object=section(%27packages/redis.html%27) — library(redis)
- https://www.swi-prolog.org/pldoc/doc_for?object=section(%27packages/semweb.html%27) — semweb/RDF
- https://www.swi-prolog.org/pldoc/man?section=ugraphs — library(ugraphs)
- https://www.swi-prolog.org/pldoc/man?section=coroutining — freeze/2, when/2, dif/2
- https://www.swi-prolog.org/pldoc/man?section=persistency — library(persistency)
- https://www.swi-prolog.org/pldoc/man?section=settings — library(settings)