Logic Cells
The complete guide to DataGrout’s symbolic reasoning layer: engines, rules, in-rule tool calls, tabling, probabilistic reasoning, batteries, and cross-namespace composition.
For the per-tool API reference (logic.remember, logic.query, logic.assert, …) see the Logic Tools Reference. This guide covers what you can do inside a cell.
What a logic cell is
A logic cell is a durable, per-namespace fact-and-rule space with a Prolog engine attached. Facts and rules persist in encrypted storage as the source of truth; a warm Prolog worker per namespace serves queries at sub-millisecond latency and is rebuilt automatically from storage whenever it goes cold. You never manage workers — assert facts, store rules, query.
Namespaces are fully isolated: their facts, rules, engine choice, and worker are their own. logic.worlds lists yours.
Engines: SWI, Scryer, and the ISO default
Every namespace runs on one of two Prolog engines:
| Scryer (ISO) | SWI | |
|---|---|---|
| ISO conformance | strict | no (superset) |
Tabling (:- table) |
✅ | ✅ |
call_tool in queries & stored rules |
✅ | ✅ |
Probabilistic rules (P::head) |
✅ via battery rewrite | ✅ native |
SWI dicts (_{k: v}), SWI-only builtins |
— | ✅ |
The default is ISO. A fresh namespace is configured auto and resolves to Scryer. If you later store a rule that genuinely needs SWI (dicts, SWI-only builtins), an auto namespace upgrades to SWI automatically — one-way and sticky for the life of the namespace. Rules that use call_tool or ProbLog notation do not trigger the upgrade; both are fully supported on the ISO engine.
Inspect or set the engine with logic.namespace:
{ "namespace": "my-app" } // read: configured + resolved engine, pin state
{ "namespace": "my-app", "engine": "swi" } // set explicitly (auto | swi | scryer)
{ "namespace": "my-app", "iso_pin": true } // pin ISO-only (see below)
The read form works before the namespace exists and reports exactly what first use will establish.
ISO pinning
iso_pin: true makes the ISO guarantee inviolable: the namespace can never fall back to SWI, and rules using non-ISO constructs are rejected rather than triggering an upgrade. Pinning is monotonic — it cannot be undone; fork to a new namespace if you need a fresh engine choice. Use it when strict ISO conformance is a requirement (compliance tiers, reactor apps), not just a preference.
Switching a populated namespace onto Scryer reloads it immediately and returns any non-ISO rules that had to be disabled (disabled_rules), so nothing dies silently.
Rules (constraints)
Store rules through logic.assert with the prolog field (or logic.constrain):
{ "namespace": "crm", "prolog": "hot_lead(L) :- attribute(L, score, S), S > 80." }
Rules are validated before compilation (call-graph analysis, termination classification, sandbox check) and each one gets a Cognitive Trust Certificate recording the assurances: validated, termination_class (datalog / well_founded / generator / unbounded), sandboxed, cycle-freedom, and the defined/called predicate lists. Queries mint their own certificates referencing the rules they exercised. Rules that call tools (see below) are fully certifiable.
Multi-clause blobs are fine — facts and rules can share one prolog string. Engine directives like :- table are recognized, applied, and reported separately (directives in the response) — they are never stored or certified as rules.
Predicates available inside rules and queries
Fact predicates
entity(Name), attribute(Entity, Attr, Value), relation(Subj, Rel, Obj), metric(Entity, Metric, Value), tag(Entity, Tag), context(Key, Value) — plus handle-aware variants (attribute_h/4, relation_h/4, …) when you need the fact handle (e.g. for probability joins).
Graph and probability
path/5, path_prob/6, reachable/4, reachable_prob/5, neighbors/4; probability(Goal, P) (noisy-OR marginal over weighted rules); lc_fact_weight(Handle, P). On ISO namespaces the probabilistic interface is psuccess/2 (see Probabilistic rules below).
Utilities
findall/3, aggregate_all/3, member/2, memberchk/2, append/3, sort/2, msort/2, length/2, between/3, sum_list/2, max_list/2, min_list/2, nth0/3, nth1/3, include/3, exclude/3, maplist/2-4, arithmetic and comparison operators, and string helpers (atom_string/2, string_concat/3, …). Note catch/3 is not available to user goals — error handling uses bound error markers instead (see below).
call_tool/3 — tools as evidence, mid-proof
Rules and queries can call any tool on your server, right in the middle of resolution:
akin(A, B) :-
ev(A, VA), ev(B, VB), A \== B,
call_tool('data-grout@1/linalg.cosine_sim@1', [a-VA, b-VB], R),
member(similarity-S, R), S > 0.5.
-
Args are a pair-list (
[key-Value, ...]) — it becomes the tool’s JSON arguments. -
Results come back as a pair-list too: read fields with
member(key-V, R)ormemberchk/2. Lists of records arrive as lists of pair-lists. -
Multi-solution backtracking over
call_toolis safe and returns every solution (bounded at 1,000 per query). - Billing, policy, and PII rules apply exactly as if the agent called the tool directly.
call_tool/4 additionally binds the response metadata (credits, cache_ref, certificate) for cost-tracking and audit rules: call_tool(Tool, Args, Payload, Meta).
call_tool_cached/3,4 memoizes on the canonicalized (tool, args) — the first call dispatches, every repeat is a plain fact lookup. Use it for repeated identical evidence calls and inside tabled reasoning. Ground arguments only; the cache is per-worker and bounded (512 entries); lc_clear_tool_cache/0 drops it.
Limits and refusals. At most 64 tool calls per query, one nesting level of tool→query recursion, and logic.* tools are refused as inner calls against the running namespace (they would deadlock its worker) — cross-namespace logic calls have their own predicates below. Refusals and tool failures never abort your query: the result binds an error marker ('$call_tool_error'(Reason)) that your rule can pattern-match, and comparisons against it simply fail.
call_ns/3,4 — query another namespace
call_ns('forensics', 'causal_chain(cert_expired, X, Path)', Solutions).
call_ns(NS, Goal, Solutions, Limit). % default Limit 50
Evaluates a goal against another of your namespaces — on that namespace’s own worker and engine (an ISO cell can query an SWI cell and vice versa) — and binds the solutions as a list of [VarName-Value, ...] pair-lists. The target must be a different namespace than the running query; chains are depth-limited. Errors bind '$call_ns_error'(Reason).
call_tool_into/4 — route a tool’s results into a namespace
call_tool_into('salesforce@1/get-accounts@1', [region-west], 'crm_mirror', Receipt).
Dispatches the tool, then materializes its result as facts in the target namespace: each record becomes an entity (named from its id/name field, or content-hashed — so re-running converges instead of duplicating) with an attribute per scalar field. Receipt reports entities, facts_written, and any nested fields that were skipped. Same-namespace targets are refused; errors bind '$call_tool_into_error'(Reason).
findall_limit/4
findall/3 with a solution cap — the right tool for bounding generative sub-goals inside a rule.
Tabling
:- table pred/arity is honored on both engines. Tabled predicates memoize their answers, which makes recursive definitions over cyclic data terminate:
{ "prolog": ":- table reach/2. reach(X,Y) :- edge(X,Y). reach(X,Z) :- edge(X,Y), reach(Y,Z)." }
The directive may precede or follow its clauses, in the same batch or a later one. Tables are invalidated automatically when facts change, so tabled answers always reflect current data. Tabling composes with call_tool_cached (table the pure predicates; make evidence calls cached).
Probabilistic rules
ProbLog-style weight annotations work on both engines:
{ "prolog": "0.7::likes(alice, prolog)." }
-
On SWI, the notation is native; query marginals with
probability(Goal, P). -
On ISO/Scryer namespaces, the rule is transparently rewritten to
prob_rule/2form and theprob-core-isobattery is auto-installed; query withpsuccess(Goal, P)(pluspand/2for conjunctions andexpected/3for expected values). The namespace stays on the ISO engine.
Weighted rules accumulate under their head — each P::head :- body is an additive rule, matching ProbLog authorship. Re-stating a body updates its weight; pass mode: "replace" to overwrite the whole set (how you express a correction).
Batteries
Batteries are installable rule packs, scoped per namespace:
-
batteries.search/batteries.install_many/batteries.installed/batteries.validate/batteries.remove -
batteries.validateruns every predicate the battery defines and tells you exactly which facts to assert to make it fire — run it after install.
Four rule libraries are preloaded into every worker on both engines (no install needed): forensic (causal chains, blast radius, contradictions — see the Forensic Tools page), fsm (state-machine reachability, cycles, validation), plan-fsm (plan execution tracking), and lumen-bench (usage/cost analysis over _lumen facts).
Configuration quick reference
| Setting | Where | Values / limits |
|---|---|---|
| Engine |
logic.namespace engine |
auto (default, ISO-first) / swi / scryer |
| ISO pin |
logic.namespace iso_pin |
true (one-way) |
| Namespace |
every logic.* tool |
any string; default "default" |
| Query timeout |
logic.query timeout |
1–10 s (default 3) |
| Result paging |
logic.query limit/offset |
limit ≤ 500 (default 50), has_more in response |
| Solutions per streamed query | fixed | 1,000 |
| Tool calls per query | fixed | 64 |
| Assert mode |
logic.assert/remember mode |
append / upsert / preview; dry_run: true validates only |
| Weighted-rule mode |
logic.assert mode |
merge (default, additive) / replace |
Serving rules as apps
Any rule in a cell can be exposed as a live HTTP API and shareable web form — see Reactor Apps & Smart Panels.
Error conventions
Inside a query, tool and cross-namespace failures bind markers instead of throwing: '$call_tool_error'(Reason), '$call_ns_error'(Reason), '$call_tool_into_error'(Reason). Pattern-match them when you want fallback behavior; otherwise downstream comparisons fail cleanly and the query simply yields fewer solutions. Errors that reach the top level (syntax errors, timeouts, undefined predicates) come back as structured tool errors with a diagnostic and, where possible, a suggestion.