Uncertainty and acceptance policy
A model answer is a probability or a distribution. A decision is yes or no. The acceptance policy is the set of thresholds that turns one into the other, and everything the policy will not decide stays uncertain.
Three-valued truth
Section titled “Three-valued truth”Every predicate resolves to true, false, or "uncertain". Composite conditions combine these with Kleene logic rather than multiplying probabilities:
false and uncertainisfalse;true and uncertainisuncertain.true or uncertainistrue;false or uncertainisuncertain.not uncertainisuncertain.
The tables live in the logic namespace if you need them directly.
The thresholds
Section titled “The thresholds”defaultPolicy is a conservative starting point:
- Noul: accept yes when P(yes) ≥ 0.8, accept no when P(yes) ≤ 0.2. Between the two is uncertain.
- Choice: accept the chosen option when confidence ≥ 0.5.
- Score: accept a score when confidence ≥ 0.5.
Noul reports a yes-probability; Choice and Score report a distribution-derived confidence. The two are not interchangeable, so they get separate knobs. noBelow must be strictly less than yesAbove; resolvePolicy throws a RangeError otherwise.
These are application thresholds, not universal truths. Tune them against your own fixtures with grade: lowering yesAbove raises coverage and risks false positives; raising it does the reverse.
Setting them
Section titled “Setting them”For the shared runtime, pass policy to configure. For one expression, call .withPolicy({...}) on given, from, or any predicate, branch, query, measurement, choice, or ask expression they build. Either form accepts any subset of the fields.
import { configure, defaultPolicy, given } from "jevlish";import { ticket } from "./support.js";import { blocked } from "./vocabulary.js";
defaultPolicy;// { noul: { yesAbove: 0.8, noBelow: 0.2 }, choice: { minConfidence: 0.5 }, score: { minConfidence: 0.5 } }
// For the shared runtime:configure({ policy: { noul: { yesAbove: 0.9, noBelow: 0.1 } } });
// For one expression:const lenient = await given(ticket).withPolicy({ noul: { yesAbove: 0.7, noBelow: 0.3 } }).when(blocked);
lenient.evidence.policy.noul.yesAbove; // 0.7: the policy that was applied is part of the evidenceThe policy that was applied is part of every judgment’s evidence, so a result can always be read against the thresholds that produced it.
Code settles what it can before anything is sent
Section titled “Code settles what it can before anything is sent”Because false and x is false no matter what x is, a false code predicate under and decides the whole expression locally. The Noul is never asked. plan() reports these as decidedByCode.
import { given } from "jevlish";import type { Ticket } from "./support.js";import { blocked } from "./vocabulary.js";
const closed: Ticket = { id: "T-9", status: "closed", subject: "Login down", body: "Nobody can log in." };
// `false AND uncertain` is false, so a false code predicate settles the whole// expression before anything is sent.const plan = given(closed) .when(blocked) .and((t) => t.status === "open") .plan();
plan.decidedByCode; // 1plan.requestCount; // 0The symmetric rule applies to or: a true code predicate settles true or x without asking x. The same folding happens per item in from(...), so closed tickets never reach the model when .where((t) => t.status === "open") is in the chain.
Thresholds apply after the cache
Section titled “Thresholds apply after the cache”A cached response is the raw model answer. The policy is applied when the answer is read, so changing thresholds changes decisions without invalidating the cache. See Runtime.