Skip to content

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.

Every predicate resolves to true, false, or "uncertain". Composite conditions combine these with Kleene logic rather than multiplying probabilities:

  • false and uncertain is false; true and uncertain is uncertain.
  • true or uncertain is true; false or uncertain is uncertain.
  • not uncertain is uncertain.

The tables live in the logic namespace if you need them directly.

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.

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 evidence

The 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; // 1
plan.requestCount; // 0

The 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.

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.