approved | denied | refused — with a cited proof chain, a
confidence, and a safe refusal when the engine cannot ground the answer well enough to be
accountable.
This is the use case where a pure LLM is unsafe: it will confidently approve actions that
violate policy. The symbolic layer gives deterministic, provenance-traced decisions relative to
the supplied facts and rules; the recursive layer handles multi-hop policy (transitive role
access, bill-of-materials/recall explosion, chained eligibility); the neural layer reads messy
natural-language requests.
Every request also runs through the canonical Grounded Symbol System (GSS) and a
tenant-scoped NSRMachine. Machine inference is mandatory for verified decisions — if the
seed pack is unavailable or inference does not complete, the API fails closed with HTTP 503.
This page reflects NSR v0.9.6. Proof verification, negation and valid-time handling were
tightened in v0.9.4 — see Behavior changes in v0.9.4 — and several
rule-evaluation semantics were corrected earlier in v0.6.0 and v0.7.0. If you wrote rules or a
proof verifier against earlier docs, read both sections.
Endpoints
These thirteen are the decision surface. The engine serves 259 operations in all — knowledge base, rules, NSR-L, machines, flywheel, billing and more — each with a generated reference page and playground under the NSR API tab.
Request
Rule effects
A rule may declare its policy intent explicitly viaeffect, which is authoritative over
predicate-name conventions — better for tenant-specific or non-English vocabularies.
effect: "review" became a first-class declared effect in v0.6.0. Before that it was
expressible only via predicate-name inference, and an inline rule declaring it would fail with
a 422 “unknown variant”. Review rules are subject-bound in the same way deny rules are.Negation-as-failure
A condition may setnegated: true: the rule fires only when no fact matches the atom. A
free variable in the negated atom is a wildcard, so not fraud_flagged(?o, ?r) fails whenever
any fraud_flagged(?o, *) exists.
negated is serde-default and skipped when false, so existing payloads are unchanged.
Outcomes
Cited symbolic policy is evaluated after GSS safety gates but before conversational
uncertainty and execution-runtime state. This matters when policy authorizes an action but
answer composition is ambiguous or live runtime state disagrees.
Response
cited_rules is the audit trail, reduced to the backward dependency path. When the request
carried inline policy, the proof also carries a SHA-256 over the canonicalized request-scoped
facts and rules, pinning the decision to the exact policy inputs it was evaluated against.
Behavior changes in v0.9.4
Three corrections to how proofs are checked. None changes the request shape; all change what the engine will accept as proven. Proof verification checks entailment, not citation shape. Both the in-process checker and the independentnsr-proof-core verifier now unify every cited fact with the atom the rule actually
needs, replay rule heads and bodies, evaluate builtins, reject relabelled builtins, and fail closed
on uncertified negation. Before this, a real but unrelated fact could be cited in support of an
arbitrary goal and pass. Backward chaining also gives each rule invocation a fresh variable
namespace, so sibling branches cannot contaminate one another’s substitutions.
Negation proofs are complete for finite stratified Datalog. An empty negation-as-failure
trace no longer fails merely because some rule defines the predicate. Both proof kernels recompute
the bundle’s perfect model and accept absence only when the ground atom is outside it. Negative
cycles, function-generating programs, non-ground negation, and closure resource exhaustion all
fail closed.
Valid-time is enforced during inference. A live evaluation captures one instant and excludes
facts that have expired or are not yet valid. Historical reasoning over HTTP and SSE uses the
request’s as_of instant, so a past snapshot is no longer filtered against today’s clock.
If you run the offline verifier, upgrade it alongside the server. A bundle produced by v0.9.4
carries the fact and rule slices the new checker replays; an older verifier will accept proofs
the server now rejects.
Behavior changes in v0.6.0 and v0.7.0
Several of these were fail-open correctness bugs. If you built against earlier documentation, the following now behave differently — in every case, more conservatively.Confidence is capped by the weakest supporting fact (v0.7.0)
Decision confidence was1.0 - grounding_uncertainty, decoupled from the proof and
non-monotonic — a 0.4-confidence fact could yield higher confidence than a 1.0 one. An
approval is now capped by its weakest supporting fact:
Subject and action binding (v0.6.0)
A rule fires for whatever the fact base satisfies, not for what the question asks. Givenmay_authorize(?p,?act) :- has_authority(?p,?act) and the fact
has_authority(ceo_kim, approve_wire), the engine proves may_authorize(ceo_kim, …).
The gate previously counted any cited request-scoped effect wholesale, so a wire could be
authorized for stranger_sven off the CEO’s authority, or delete_account authorized off
refund authority. A single conclusion-head check now covers all effects: the cited rule’s
effect only applies when its ground conclusion binds the request’s own subject and action.
Two related binding fixes:
- Numeric builtins — the binding gate replays a cited rule’s body to reconstruct which
subject it fired for, but the replay matcher did not evaluate comparison builtins (
leq,geq,lt, …). It returned “can’t tell”, so no mismatch was detected. A rulemay_refund(?o) :- order_amount(?o,?v), leq(?v, 500)could approve a refund for an order whose amount did not qualify. The matcher now computes comparison builtins under the binding, exactly as the solver does. - Single-character subject tokens — the subject-binding token match dropped tokens shorter
than 2 characters, so
ord-9andord-4(sharingord, differing only in the 1-char suffix) were treated as the same subject. A rule that fired forord-9could bind a decision aboutord-4. The match now requires every alphanumeric token, single characters included. Phrasing variance (order A1vsorder #A1) still grounds.
Abduction no longer asks for un-suppliable builtins (v0.7.0)
A failed comparison builtin such asleq(850,500) was reported in missing_facts with a
resolves_with, telling an agent to “supply leq(850,500)” — impossible. Comparison builtins
are now excluded; the actionable ask is the base predicate that binds the value, e.g.
amount.
Soundness guarantees
A verdict never authorises something the rules do not. Five invariants hold, each regression-tested, with the full set running against a 1,260-case auto-labelled soundness corpus.
Confidence is never overconfident. An approval resting on a
0.2-confidence fact reports
confidence ≤ 0.2, and the relationship is monotonic — a lower fact confidence never yields a
higher decision confidence.
Policy impact analysis
Before changing a policy, find out what it would have changed.Idempotency
Mutating requests accept anIdempotency-Key header, Stripe-style.
Idempotent-Replayed: true and the original decision, billing, and quota
headers preserved.
Keys are scoped per org, 1–255 URL-safe characters. 2xx and 4xx are cached; 5xx is not, so
retries during an incident still reach a healthy backend.
All four SDKs send a key automatically on
decide and decide_batch — a fresh UUID per logical
call, reused across the client’s internal retries. You get safe retries without doing anything.Metering
Each resolved decision is one billable unit. A billable outcome is gated by the org quota, which returns HTTP 402 on exhaustion.usage.outcome_cost_microdollars and usage.billing_tier
report the charge, tiered by mode and proof depth.
Refusals and cache hits are governed by the same billability policy — so a refusal is not a free
way to probe, and a replay is not a second charge.
Verify a proof without trusting us
An approved decision carries averifiable_bundle: the derivation plus the
exact facts and rules it stands on. A proof is only proof-carrying if somebody
other than the engine can check it, so the checker is a reachable endpoint.
POST the bundle back, verbatim:
Response
Response
The handler is stateless by design. It reads no tenant state and touches no
database; the bundle carries its own
org_id, and cross-tenant laundering is
rejected inside the checker. That is what makes the answer worth anything —
the trusted computing base is the checker and the bundle, not the rest of the
server.The outcome loop
Confidence is only meaningful if someone checks it against reality. Record what actually happened to a decision and the engine can tell you how well its own confidence predicts outcomes.
Once outcomes are flowing, the calibration curve is the number that matters:
[0, 1]
where 0 is perfect. That is what lets you set an auto-approve threshold you can
defend rather than guess at.
POST /v1/decisions/{id}/outcome returns 404 once a decision has rotated
out of the retained window, so report outcomes as they happen rather than
batching them weeks later. outcome-by-ref exists so you can do that from the
system that learns the outcome, using the id it already holds.Prove it yourself
A proof harness runs labelled policy cases through the real backward-chaining engine:MCP server
The NSR MCP server gained a per-request timeout (NSR_TIMEOUT_MS) so an agent never hangs on a
slow endpoint, plus client-side input validation that names the exact malformed rule or field
instead of surfacing a cryptic 422 deserialize error.
Verification
NSR ships a verifier-as-oracle evaluation apparatus: a 1,260-case auto-labeled decision-soundness corpus, a deterministic generator, and a runner that checks any live/v1/decisions deployment against the ground-truth labels — plus in-process property-based and
generative multi-invariant soundness tests.
Related
- Neuro-Symbolic Architecture — the conceptual model
- NSR-L — the rule language