Decision Tier

Level 3 301 · for engineers

Engineering a decision tier

A decision model in production is a measured component with a contract, not a prompt you tweak until it looks right. This page covers evaluation you can believe, the invariants that keep thresholds honest, adapters across vendors, determinism, and the operational habits that kept two systems running.

A blueprint-style exploded view of four stacked modules (gears, a gauge cluster, a library, a desk) joined by pipes, with flow going up and a return pipe bringing fixes back down.

1. Evaluation you can believe

Before a held-out run

Reading the result

Three held-out months, honestly reported

MonthFiltered (zero LLM tokens)MissesEst. recallEscalation precision
127.8%7 (all low-confidence labels)~97%42.5%
228.6%23~92%50.0%
329.7%5 (all low-confidence labels)~98%55.8%

Month 2 is in the table on purpose. Every change between runs traced to a specific pattern of misses: plainer questions, rules demoted from overrides to facts, context the small model couldn't have had (which services belong to which projects), and charge history per vendor.

Validate on a new time window, not only cross-validation

2. Invariants worth writing down

InvariantWhy
Thresholds are keyed by (model, runtime, rubric) and stored next to the rubric versionA prefill-step change moved probabilities 0.043; swapping backends under a trained combiner raised error from 1.86 to 2.12
Pin model versions, never aliases, when thresholds depend on themAn alias like jev-latest moves on release. The Decisions API has no pinned snapshot at all, so re-check thresholds whenever OpenAI ships
Log the model id the response reports, plus every reading, not just firingsThe useful patterns (flat, rising, falling after a fix) only show up across ticks
Rules are facts, not overridesOverrides saved zero important messages and cost filtering. Only bad input and prompt injection force review
Interfaces fail loudlyA gate read a field the observation layer never sent. Cost read 0, so the gate always passed
Verifiers check outcomes, not starts, and accept deliberate stopsWith storage full, the system silently discarded every product, and "job started" called it success
Private data goes only to backends approved for it, gated in codeIn our harness any non-loopback backend refused private mail without an explicit environment flag and a recorded approval

3. An improvement loop for questions

StepWho
Find a failing concept (blind audit of each window's filed items)LLM subagents
Propose a phrasing ladder and a contrastive ChoiceLLM
Label concept gold: an enriched probe plus a seeded random slice, blindLLM subagents, with human spot-checks
Grade each rung against its own concept; test sub-questions against one good questionCode
Decide the use (feature, gate or rule) and validate on a new windowCode, then LLM judgment
Rule on the gray zones the disagreement exposesHuman only

Rounds of question tuning on a small local model weren't worth running. Concepts that depend on hidden consequences needed the hosted model or the LLM reader.

4. Adapters: one contract, many backends

Write your code against one decision contract and put a thin adapter in front of each backend. We used Jev's POST /v1/systemone shape as the contract and ran OpenAI's Decisions API, Claude Haiku 5.5 and a local open-weight model behind it. The official TypeSafe SDK and every harness then ran unchanged. As of October 2026 the /v1/systemone shape is becoming a de facto standard: Ollama and llama.cpp serve it locally, and AWS's Strands Decider speaks it natively. OpenAI's endpoint does not.

Jev /v1/systemoneOpenAI /v1/decisions
Yes/no typenoulpredicate
Body{state, model, questions: {id: q}}{input, model, questions: [{name, ...}]}
StateText or a JSON objectText or user-role messages, including images. A JSON object is a 400; the adapter pretty-prints it
Criteria on a yes/no{true, false} or free-formNo such field; the adapter folds it into the instructions
Choice optionsMap of option → description or null[{value, description?}]; a null description is a 400
One-option ChoiceAnsweredRejected (minimum 2). The Strands Decider rejects it too
Errors422 {detail: {...}}400 {error: {...}}; the adapter maps to 422
Version pinningjev-1.13.0Only gpt-6-luna

An adapter for a general LLM (we used Claude Haiku 5.5) turns each question set into a structured-output JSON schema: one probability per Noul, one per option or level. Two caveats come with that: every probability is a number the model wrote down, not a measured log-probability, and the adapter has to clamp to [0, 1] and normalize each distribution.

Compatibility gotchas we hit
  • The SDK's choice(q, {a: null}) idiom sends null descriptions; two backends reject them. Send "".
  • The Strands Decider 2B server crashed under concurrent requests (as its docs warn). Serialize requests to it.
  • It has a 4,096-token window, no effort, no auth (binds 127.0.0.1) and no pinned base revision.
  • Haiku requests were about 2× Jev's token count for the same mail (framing plus a newer tokenizer). Any feature built on token counts needs refitting per backend.
  • Restarting an adapter mid-run made a queue runner race through its jobs with instant connection errors. Stop runners first.

5. Determinism and repeatability

BackendRe-ask the same requestConsequence
Hosted JevIdenticalA frozen threshold stays put
OpenAI Decisions (gpt-6-luna)Identical (759 re-asked answers)Stays put, until OpenAI ships an unpinned change
Strands Decider 2BIdentical across runsStays put
Claude Haiku 5.5 (effort low)33% identical; yes-probabilities moved 0.10 at p90 (0.60 max); 13% of Choice argmaxes flippedKeep thresholds away from the busy part of the distribution, or average 2–3 calls at 2–3× the cost

6. Operating it

7. Local and open weights: what to plan for

Next: the cascade puts these pieces together into an architecture.