XLOG fails closed: when a program asks for something the engine cannot run soundly on the device, the engine stops with a typed error that names the rule it violated. It does not silently fall back to a slower or semantically different path. This page catalogs the error types, the exit codes, and the specific rejections you are most likely to encounter.

Error types

Every Rust-level error is a variant of a single enum, XlogError (defined in crates/xlog-core/src/error.rs). The enum is marked non-exhaustive, so new variants may be added without a breaking change.

Exit codes

The xlog CLI returns 0 on success and 1 on any error. Every failure — parse, compile, execution, I/O, or an exhausted memory budget — surfaces as exit code 1 with a descriptive message on stderr. There are no other exit codes.

Fail-closed rejections you may hit

Module import rejection

Module resolution completes before deterministic or probabilistic compilation. It reports these typed diagnostics rather than compiling a program with missing imported behavior: The entry file is loaded from the exact path supplied to the CLI. Imported module paths continue to resolve to .xlog files. Queries and probabilistic queries in imported modules are entry-file-scoped and are not merged; imported pragmas are ignored with warning[W0510]. See Modules for examples and visibility rules.

Function expansion rejection

User-defined functions expand after module resolution. Production compilation registers function declarations in source order, then expands only definitions reachable from calls in ordinary rules and constraints. An unused definition whose body is recursive, calls an undefined function, or shares a name with a predicate does not block that demand-driven path. Rust callers can instead request strict whole-program validation with FunctionRegistry::from_program. That surface inspects every definition in declaration order and every callee in source order, including definitions that production expansion would not reach. E0506 and E0507 are unassigned. Strict validation accepts a recursive SCC when at least one member has a conditional body. That is a structural validation result, not a runtime termination guarantee: function normalization expands both branches eagerly, and a used cycle can still reach E0504 at the configured depth.

Cross-predicate type mismatch

Compilation rejects a rule whose variables draw incompatible column types from the declared schemas of the predicates they touch. The conflict may be between two body atoms, or between a body atom and the head. Both forms are Compilation errors that name the rule, the variable, both types, and where each came from:
The check applies only to predicates that carry an explicit pred declaration; an undeclared predicate has no schema to contradict. Available since 0.12.0 — before that the same program was accepted here and failed later with an internal GPU schema error that did not name the rule.

Epistemic rejections

UnsupportedEpistemicConstruct is one error type covering many distinct rejections. The construct field in the message tells you which one you hit; find it in the left column below. (A modal literal is one written with know or possible. A world view is the set of models the program considers possible at once. A tuple key is the argument list that identifies which tuple a modal literal is talking about.) g91 and faeel are the two epistemic semantics you pick between with #pragma epistemic_mode; faeel is the default. See Epistemic support and boundaries for the same boundaries stated as language rules, and Epistemic reasoning for what the two modes mean.

Resident Monte Carlo rejection

The production Monte Carlo engine — which estimates probabilities by sampling many random possible worlds — runs entirely on the GPU (“resident”) within fixed memory bounds. At compile time it checks every rule and fact against the model of what the device can run; anything outside that model is rejected with a typed ResidentRejection (surfaced as a Compilation error of the form resident MC engine rejected program [kind=...] construct=... context=...). There is no silent CPU fallback.
On the CLI, xlog prob --allow-cpu-oracle lets a rejected program run on a labeled CPU oracle instead; the result is tagged mc_engine: "cpu-oracle" and is never GPU-native evidence. Without the flag, a rejected program fails. See the CLI reference.

Exact aggregate caps

The exact engine (exact_ddnnf) computes probabilities exactly rather than by sampling. It evaluates aggregates over finite probabilistic domains using dynamic programming, and that evaluation is capped per aggregate group:
  • Count-only aggregates: at most 64 uncertain rows per group.
  • All other aggregates (sum, min, max, logsumexp): at most 16 uncertain rows per group.
Rows whose provenance is deterministically true or false do not count against the cap — only rows whose membership is genuinely uncertain do. Over the cap, the compile fails with a Compilation error that names the predicate, the group key, and the cap, and tells you the way out: use prob_engine = mc or reduce the finite aggregate domain. The Monte Carlo engine has no such cap because it samples worlds instead of enumerating outcome formulas. See Probabilistic engines for choosing between the two.

Multiway union size caps

The GPU sort and dedup steps that merge same-head rule outputs index column bytes with 32-bit arithmetic, so they reject any column whose logical bytes exceed 4294967295 (u32::MAX) with a typed error of the form Sort supports at most 4294967295 bytes per column, got ... (column N); the concatenation step applies analogous byte caps, Concat: total_bytes too large: ... and Concat: col_bytes too large: .... Since 0.12.0 the clauses of one head merge in a single batched concat pass, and that pass additionally rejects more than 4294967295 total rows with Concat supports at most 4294967295 rows, got .... These are all fail-closed declines, never silent truncation: a relation column past 4 GiB stops the run instead of scattering rows.

Compile and verify budgets

These two budgets let the engine refuse an oversized problem cleanly instead of crashing the GPU. Available since 0.10.0, the knowledge-compilation phase (D4) and verify-phase controls decline oversized instances rather than risk a CUDA launch failure that would leave the GPU unusable for the rest of the process:
  • A CNF (Boolean formula in conjunctive normal form) whose variable or clause capacity exceeds XLOG_D4_VERIFY_MAX_VARS / XLOG_D4_VERIFY_MAX_CLAUSES declines before any kernel launch with CompileCapacityExceeded. Both bounds default to unbounded.
  • A verify whose SAT search exhausts XLOG_D4_VERIFY_MAX_CONFLICTS declines with VerifyBudgetExceeded — an indeterminate search result is never reported as a proof. The default budget of 0 means unlimited.
Both declines are catchable, and the caller can skip the query or fall back to the approximate mc engine. See Environment variables for the knobs.
A fail-closed decline is a diagnostic, not a result. It blocks an unsound or context-poisoning execution and explains why; it does not mean the query was answered.

Python exceptions

pyxlog maps the Rust error surface onto standard Python exception types: In a source-only import without pyxlog._native, package-level RelationMetadataError and RelationEvidence remain importable. The fallback metadata error still subclasses ValueError; only native evidence instances and session operations require the extension.

See also