Guide · Decision models

Decision models

DML separates generation from decision. Alongside task/N and prompt/N, it can route a bounded semantic decision to a decision model and branch on a typed answer. Your active model is the default; Jev (TypeSafe System One) is the calibrated backend.

What a decision model is

A decision model answers a small, closed question about an explicit state. It never writes prose and never sees prior memory, so its answer is bounded and reproducible. Every decision is one of four kinds:

KindAnswersDML value
chooseOne of a closed set of optionsThe option atom
rateOne of an ordered list of levelsThe level atom
verifyA yes/no propositionyes | no | unknown
probabilityA yes/no propositionA number in [0, 1]

Because answers are constrained to the options and levels you supply, downstream logic never has to parse free text or guess at a label.

DML predicates

Ask several questions in one batched call with judge/2, and bind the answers in order:

judge(Message, [
    choose("Which team should handle this?",
           [billing-"Charges and refunds", orders-"Delivery", account-"Login"]) - Team,
    rate("How frustrated is the customer?", [calm, frustrated, angry]) - Frustration,
    verify("Does the message ask for a refund?") - Refund,
    probability("Is this urgent?") - Urgency
]),
(   Refund == yes, Team == billing
->  answer("Escalating to the refund queue.")
;   format(string(R), "route=~w frustration=~w urgency=~w", [Team, Frustration, Urgency]),
    answer(R)
).

One-off predicates are also available:

choose(State, Question, Options, Choice)
rate(State, Question, Levels, Level)
verify(State, Question, Truth)          % yes | no | unknown
probability(State, Question, P)         % number in [0, 1]
holds(State, Question)                  % succeeds when verify == yes
holds(State, Question, Threshold)       % succeeds when probability >= Threshold

Judgments are memoized per run by backend, model, state, and questions, so backtracking reuses an answer instead of re-sampling the model.

Backends

Decision predicates are implemented by swappable backends. Each declares its capabilities, and the answer carries its basis.

BackendCalibratedProbabilityCredentials
llm (default)noself-reportedpi's active model and credentials
jevyescalibratedTYPESAFE_API_KEY
mock / mockJevno / yesyesnone

The llm backend is the default, so decision predicates work with no new credentials. Jev (TypeSafe System One) is opt-in and adds calibrated probabilities, confidence, independent questions, and structured criteria. The SDK never reads or stores credentials: the API key is supplied by the caller. A mockJev backend advertises Jev-like calibrated capabilities so the calibrated path can be developed and tested without a key.

Capability gating

A skill that depends on calibration should fail clearly rather than silently run on an uncalibrated backend. require_judgment/2 records a required capability, and with_judgment/2 scopes a named backend to a goal.

% Scope a calibrated backend to this decision.
with_judgment(jev, decide(State, Decision)).

% Fail at the next judgment if 'calibrated' is unavailable.
require_judgment(calibrated, (
    probability("Is this urgent?", P),
    (P >= 0.8 -> escalate(State) ; standard(State))
)).

Configure the SDK

import {
  createDeepClause,
  createLLMJudgeBackend,
  createJevJudgeBackend,
  createMockJevJudgeBackend,
} from "deepclause-sdk";

const sdk = await createDeepClause({
  model,
  llmBackend,
  judgeBackends: {
    llm: createLLMJudgeBackend({ llmBackend }),
    jev: createJevJudgeBackend({ apiKey: process.env.TYPESAFE_API_KEY }),
    mock: createMockJevJudgeBackend(),
  },
  defaultJudge: "llm",
});

The backend used by a judgment resolves in this order: with_judgment/2 scope, then a per-run override, then defaultJudge, then the first registered backend. sdk.getJudgeCapabilities() reports what the active backend can do.

In the pi extension

Enable Jev in .pi/deepclause/config.json; the extension reads the key from the named environment variable and never stores it.

{
  "judgment": {
    "default": "llm",
    "jev": {
      "enabled": true,
      "model": "jev-latest",
      "apiKeyEnv": "TYPESAFE_API_KEY"
    }
  }
}

Then select the backend per run or as the default:

/dc-run skills/triage.dml --judge=jev
/dc-judge enable
/dc-judge default jev

If Jev is enabled but the key is missing, the extension warns and falls back to llm.

Evidence, not authorization

A decision model produces evidence; DML owns policy. Thresholds, weights, routing, and every side effect stay in Prolog. Calibrated probabilities can inform a decision, but money-moving or irreversible actions keep their hard gates and human approvals — and a skill that requires a calibrated backend falls back to its fallback clause when that backend is unavailable.