How I Built a System One Option Scorer like Jev
Scott Davis · Hecaton Labs · September 2026
On September 15, 2026, TypeSafe AI came out of stealth and released Jev, its first System One model, into early access. Jev is not a chatbot. It takes a situation (state), scores a short list of allowed moves (options), and returns a structured choice software can act on. No essay. No tool-call monologue. A decision.
That launch put a public name on something many builders already need: a small decide(state, options) model you can train for your own product loop and run locally, instead of renting a giant text generator for every turn.
I built my own. About 256MB on disk, trained on a single RTX 3060, roughly 18ms per turn. The speed comes from a one-pass design: score each legal option against the state in a single forward pass, then pick. No token-by-token generation in the loop.
Architecture got the latency into range. Training decided whether those scores would transfer into control.
The contract
The job is narrow on purpose. Given a state and a short list of legal options, return one ranked choice. Software already knows what moves are allowed. The model only has to decide which one fits this state.
Under the hood that is DistilBERT (Sanh et al., 2019): a small language model that reads the state and one option together, then outputs a score. For each legal option, the input looks like:
[CLS] state [SEP] option [SEP]
One forward pass per option. Softmax turns the scores into probabilities. Argmax (or sampling) picks the move. There is no decode loop writing tokens until an answer appears. That one-pass contract is why sub-20ms turns are realistic on a local GPU.
Scoring the state and each option separately, then combining those embeddings later, barely beat random guessing in early trials. Joint scoring (state and option in the same pass) was what started beating random guessing. The rest of this post is about what training data made those rankings useful for control, not only for language labels.
First training: language as option ranking
The model only needed to do one job: given a situation, pick the best option from a short list. Every training row used the same record shape:
{
"state": "The server returned 503 after the deploy.",
"options": [
"roll back the release",
"retry the request",
"ignore and continue"
],
"chosen": 0
}
state is the situation. options is the candidate list. chosen is the index of the ranked answer. Language tasks and, later, game logs both land in this format before training.
The first supervision came from language data reshaped into that contract. Entailment-style sets such as all-nli and ANLI became three-way choices (premise as state, labels as options). Multiple-choice reading and reasoning sets such as Cosmos QA, HellaSwag, and PIQA became the same shape: question as state, answers as options. The goal was not “learn natural language inference.” It was plentiful supervision that already has a ranked choice.
With that data, a frozen encoder barely moved. Accuracy sat on random guessing for a three-way choice. Packing state and option into one DistilBERT pass finally left random guessing (~33%) and landed around 42% held-out. That was the first clear sign the model was ranking options instead of picking at random. Lightly unfreezing DistilBERT on a modest slice pushed validation into the mid-seventies. Pushing the same mixture harder mostly plateaued.
Useful, and still incomplete. Ranking language labels is not the same job as choosing actions in a game. The architecture could score options fast. The next experiment was whether supervised play logs, in the same schema, would transfer those rankings into control.
Play logs taught control
Language option ranking was still language about language. A model that ranks entailment labels still does not know when to flap, heal, or disable a target. For control, the training rows had to be decisions from play.
So I wrote small scripted experts: programs that already know a correct policy for a tiny game, play thousands of turns, and log each decision in the same {state, options, chosen} schema. Those logs are supervised training data. The scripts are not the product. They exist to manufacture labeled play.
The first game was the Flappy Bird harness from OpenJev, where Flappy is used as a real-time System One–style control demo. Pointing the language-trained scorer at it like a policy failed. After training on the scripted Flappy logs (and fitting a small head to those logs), the same model could play.
Fantasy party combat came next, and that is where the training problem got sharper. In one fight, healer, tank, DPS, and support do not share a single “best” move for the board. A healer’s state stresses who is about to die and whether a heal beats waiting. A tank’s state stresses threat, mitigation, and when to guard. A DPS’s state stresses the kill target without wasting a turn. Support with crowd control (abilities that lock an enemy out of acting) stresses who to disable, when to refresh that disable, and when a small damage option is better than another crowd-control cast.
Same schema every turn. Different goals in the state text. Different legal option sets. Different ranked answers. That role split is what forced the scorer to learn decisions that depend on who is acting, not a generic best move for the whole board.
Those play logs are what taught transfer into control. More language ranking alone would not have.
One support turn
Here is a support turn from the live party demo. Tidecaller (enemy healer) is already crowd-controlled and nearly dead. Legal options are cast crowd control, refresh crowd control, or chip for small damage.
State
role=support side=flare name=Support id=flare_support t=43 active=flare_support
self hp=72/72 mana=11/11 status=none:0 defend_hits=0
allies: Rockjaw(tank) hp=62/110 mana=10/10 status=none:0 defend_hits=2; Warmheart(healer) hp=78/78 mana=14/14 status=none:0; Emberpup(dps) hp=69/88 mana=10/11 status=none:0
enemies: Shellguard(tank) hp=0/110 mana=10/10 status=none:0; Tidecaller(healer) hp=9/78 mana=14/14 status=cc:1; Aquafin(dps) hp=0/88 mana=11/11 status=none:0; Drowsprite(support) hp=0/72 mana=11/11 status=none:0
cc_enemies: Tidecaller
our_taunt_holder=none focus_target=Tidecaller
ally_lowest_hp_frac=0.56 need_heal=1 need_defend=0
options_hint: defend/guard/brace | heal/mend | taunt | nuke | risky_aoe | cc | refresh_cc
Options
[
{ "id": "cc", "text": "use CC: Disable a priority foe (skip turns)" },
{ "id": "refresh_cc", "text": "use Refresh CC: Refresh disable on a locked foe, else CC fresh" },
{ "id": "chip", "text": "use Chip: Small chip damage (~10)" }
]
Model output
{
"scores": [
{ "id": "chip", "score": 0.647 },
{ "id": "refresh_cc", "score": 0.307 },
{ "id": "cc", "score": 0.046 }
],
"chosen": {
"id": "chip",
"text": "use Chip: Small chip damage (~10)"
},
"latency_ms": 18
}
The scorer preferred chip over refreshing crowd control. Tidecaller died, and the fight ended. A healer looking at the same fight would get state text that stresses ally HP and heal pressure. A tank’s state would stress threat and guard. Same schema. Different emphasis. Same ~18ms one-pass ranking.
Watch the loop
A single support turn is easy to inspect. A full fight is where the contract has to hold: every role, every turn, same schema, one ranked action, fast enough on the 3060.
Figure: Video. Fantasy party battle driven by the option scorer.
The support turn above is one frame from that demo: raw state, options, and scores. The video is the same loop over a full fight. State in. Role-aware option list in. One scored choice out. Repeat until the encounter ends.
Close
The scorer is a one-pass ranker over state and option. Language data got ranking off the ground. Scripted play logs, especially multi-role fantasy combat, are what transferred that ranking into control. Same schema. Different goals per role. One scored choice per turn.
Appendix
Dataset reshape notes
Entailment-style sets: premise as state, labels as options, gold label index as chosen. Multiple-choice sets: question as state, answers as options, correct index as chosen.
References
- TypeSafe AI. Introducing System One Models and Jev. 2026. https://typesafe.ai/blog/introducing-system-one-models-and-jev
- Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf. DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter. 2019. https://arxiv.org/abs/1910.01108
- Hugging Face. DistilBERT (Transformers docs). https://huggingface.co/docs/transformers/en/model_doc/distilbert
- sentence-transformers. all-nli dataset. https://huggingface.co/datasets/sentence-transformers/all-nli
- Facebook AI. ANLI dataset. https://huggingface.co/datasets/facebook/anli
- AllenAI. Cosmos QA dataset. https://huggingface.co/datasets/allenai/cosmos_qa
- Rowan Zellers et al. HellaSwag dataset. https://huggingface.co/datasets/Rowan/hellaswag
- Yonatan Bisk et al. PIQA dataset. https://huggingface.co/datasets/ybisk/piqa
- AlexWortega. OpenJev (Flappy Bird System One–style harness). https://huggingface.co/AlexWortega/openjev