Skip to main content

Decision recipes

A decision model does not write text. Given a state (a request, a ticket, a document, an event) and a typed question (pick one of these options, yes or no, a level on a scale), it returns a probability per option. That makes it one forward pass, easy to threshold, and measurable with accuracy and calibration instead of judges. A router that decides whether an agent thinks before answering is one (experiment).

RecipeStageWhat it addsValidation metrics
llm_decision_sft1a head that reads the options, trained with cross-entropy; optionally reasoning chains as contextaccuracy, nll, ece
llm_decision_cispo2, from a stage-1 checkpointreasoning before the decision, reinforced when it leads the head to the right optionthe same, plus think_accuracy

Both build on the LLM base models (transformer, hybrid), fit a temperature at the end so the probabilities are calibrated, and write it next to the head (head.json). The row format follows the Jev request format: a state with one or more questions.

{"state": "Order total: $62. Free shipping applies to orders of $70 or more.",
"questions": {"free_shipping": {"type": "noul", "instructions": "Does free shipping apply to this order?", "label": false},
"tier": {"type": "choice", "instructions": "Which customer tier does the order total fall into?",
"criteria": {"bronze": "under $30", "silver": "$30 to $69", "gold": "$70 or more"}, "label": "silver"}}}

Question types: choice (one of criteria), noul (yes or no), score (a level on a scale). One question per row (state, question, label) is accepted too. The heads and prompt markers follow the open jeff and Jeeves projects, reimplemented on this framework's loop, so their released checkpoints do not load here.

How the heads and stages work​

A decision model answers choice, noul (yes/no) and score questions about a state with a probability per option. The backbone gets a head (model.head) that reads the options, and the prompt marks state, question and options with rare tokens of the Qwen tokenizer:

HeadHow it scoresOrigin
readoutoptions get one-token codes (A, B, ...); a linear layer on the decide position scores the codesJev, jeff
pointera dot product between the decide position and each option's end position; no codes, any number of optionsJeeves

llm_decision_sft trains the head with cross-entropy over the options. With method.think_fraction: 0 that is a one-pass decision model. Above 0, that share of the questions gets a reasoning chain sampled from the base model before training and inserted as context, so the head learns to decide after reasoning as well as without it.

llm_decision_cispo continues from such a checkpoint (model.init): per question it samples group_size reasoning chains, rewards each by the probability the head then gives the right option, and reinforces the chains that beat their group's mean. The head's loss after reasoning and without reasoning is added, so the one-pass answer keeps working.

Both keep method.calibration of the training records aside, fit one temperature on them when training ends and store it with the head (head.json in the checkpoint); validation reports calibrated accuracy, nll and ece. The training unit is the question: training.batch_size counts questions, however many a record holds, and CISPO forwards batch_size x group_size sequences per step.

model:
head: pointer
init: output/decision-pointer-qwen3.5-0.8b-<hash>/validate/checkpoint # stage 2 starts where stage 1 ended

model.init works in every llm_* and embedding_* recipe: it is "start from this checkpoint of an earlier experiment". Point it at <experiment>/validate/checkpoint: that link exists only when the earlier experiment passed validation, whereas train/checkpoint is there even when it failed. The checkpoint counts by content in the fingerprint, so rebuilding the earlier experiment rebuilds the one that continues it.

Source: README.md. This page is generated; edit the source and run npm run sync.