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) or memberchk/2. Lists of records arrive as lists of pair-lists.
  • Multi-solution backtracking over call_tool is 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/2 form and the prob-core-iso battery is auto-installed; query with psuccess(Goal, P) (plus pand/2 for conjunctions and expected/3 for 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.validate runs 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.