Eight years of GPT spinoffs. Then Jev, and a week of BERT's revenge.

TypeSafe shipped Jev on a Tuesday. By Sunday there were three open rivals, and the fastest of them is a fine-tuned ModernBERT that reads its answers out of [MASK] tokens. Somewhere in there the encoder got its job back.

Jev and BERT's Revenge: Why System One Models Are Encoders

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander writes about the operational side of shipping production AI - agents, retrieval, evals, and the guardrails that keep them from going sideways.

First came Jev. Then Laya. Then Laya-MLX. And now Kev.

That’s four System One decision models, three of them open, and the open ones all landed between Friday morning and Sunday night. I’ve followed a fair number of model launches and I can’t remember clones showing up this fast, before most people had even finished the original announcement. So, let us breathe, folks. And while we’re catching our breath, it’s worth looking at what people grabbed when they sat down to rebuild Jev over a weekend. I expected LoRAs on whatever chat model was lying around. What I found was a BERT, and a Qwen that had been bent into behaving like one.

Quick recap if you missed the launch (I wrote up what Jev is and how it fits an agent harness a couple of days ago). You POST a state and a map of typed questions, and you get typed answers back with calibrated probabilities attached. Noul is yes/no, Choice picks one of your options, Score puts the state somewhere on a ladder of levels you describe in words. The model never returns text. What I didn’t cover there is what kind of model is actually good at this, and the answer goes back to 2018.

The fork in 2018: fill the blank or finish the sentence

2018 is roughly when transformers split into two camps. OpenAI’s GPT read left to right and learned to guess the next token. Google’s BERT looked at the whole sentence at once, both directions, and learned to fill in blanks. You pick 15% of the tokens, hide most of them behind [MASK], and make the model put them back using context from both sides. For a couple of years BERT was the thing. It swept the language-understanding benchmarks of its day, and if you shipped a text classifier, a tagger or a search ranker in 2019, odds are it was a fine-tuned BERT.

Then generation turned into the product. A model that can finish a sentence can finish a conversation or a program, and every time someone scaled it up it got better, so the money went there. Encoders didn’t go anywhere, though. They kept doing the embeddings for your vector store and the reranking in your retrieval pipeline, and nobody wrote launch posts about them. When Answer.AI and LightOn released ModernBERT in December 2024, their announcement pointed out that encoder-only models were pulling over a billion downloads a month on Hugging Face, nearly three times the 397 million for decoder-only models. Chat got the attention. Encoders kept getting the downloads.

Here’s the thing for anyone building agents. Most of what a harness asks a model has no need for prose. Is this tool call destructive? Which of four queues gets this ticket? Is this retrieved chunk relevant, or has the agent been looping for five turns? Every one of those is a blank with a handful of acceptable answers, and for about three years we’ve been sending them to models trained to write essays, then regexing a single word back out.

A decision is a [MASK] slot

If you haven’t looked at it in a while, the Hugging Face course has a chapter on fine-tuning a masked language model that reads differently this week. They take DistilBERT (about 67M parameters) and keep training it with the same masked-token objective, only now on IMDb reviews. Before that, This is a great [MASK]. fills in with Wikipedia-ish words like deal, success, adventure. Afterwards you get movie, film, story, and perplexity falls from 21.75 to 11.32. The architecture is untouched. The model has just learned what usually goes in that blank when the text is about films.

from transformers import pipeline

mask_filler = pipeline(
    "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb"
)
mask_filler("This is a great [MASK].")

The training side is pretty dull, honestly. Glue the reviews together and cut them into 128-token chunks (25,000 reviews turn into 61,289 examples that way), then let a data collator mask 15% of tokens as batches go by. There’s also a whole word masking variant at 20%, which hides every sub-token of a word together so the model can’t reconstruct “unbelievable” from “unbeliev”. You never write a label. Each blank is effectively a multiple-choice question where the choices are the entire vocabulary.

from transformers import DataCollatorForLanguageModeling

collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)

Now imagine shrinking that vocabulary down to the four options you wrote for one question, and normalising only over those. You’ve basically got a typed decision, and that’s close to how Laya works. Its English checkpoint is ModernBERT-large, fully fine-tuned, with a small decision head on top (two transformer layers, an option-marker scorer, and an act/escalate head). A Noul gets scored at a [MASK] position. For Choice, the model card says “every option is scored at its own [MASK] token, then softmaxed over that question’s options.” So it’s fill-mask where you get to pick the dictionary. All the excitement about type safety, and underneath it’s one of the oldest BERT tricks there is.

The reframe: A System One question is a fill-in-the-blank where you supply the vocabulary. Encoders were pretrained on exactly that. Decoders were pretrained to guess what comes next, and the set of possible next things is enormous.

Cloze questions were the first System One

The idea itself has been around for a while, and the history is useful because it shows you where it breaks. In January 2020 Timo Schick and Hinrich Schütze put out Pattern-Exploiting Training, or PET, which recast classification as cloze questions. Instead of adding a classifier head to BERT, you write a pattern along the lines of Review: {text}. It was [MASK]. and map each label to a word (they call that mapping a verbalizer), say great for positive and terrible for negative. Then you just read the masked-token logits for those words. With little labelled data it beat ordinary supervised fine-tuning, since the pretrained model already had a decent sense of what goes in a blank like that.

Rename “pattern” to “question” and “verbalizer” to “options” and it’s a Choice. PET didn’t have the product around it, to be fair. No stable API, no batch of questions sharing one state and one pass, no ordinal Score type, and nobody was telling you to put a threshold on the probabilities. Those parts are new and they matter. The central idea, though, was already in a paper two years before ChatGPT came along and everyone stopped reading about encoders.

It’s easy to try the 2020 version against a 2024 encoder, and doing it shows you both why this works and why the clones need training data. Below is plain zero-shot cloze scoring on answerdotai/ModernBERT-base. No fine-tuning, no Laya.

import torch
from transformers import AutoModelForMaskedLM, AutoTokenizer

name = "answerdotai/ModernBERT-base"
tok = AutoTokenizer.from_pretrained(name)
mlm = AutoModelForMaskedLM.from_pretrained(name).eval()


def choice(state: str, question: str, options: list[str]) -> dict[str, float]:
    text = f"{state}\nQuestion: {question}\nAnswer: {tok.mask_token}."
    enc = tok(text, return_tensors="pt")
    pos = (enc.input_ids[0] == tok.mask_token_id).nonzero()[0, 0]
    with torch.no_grad():
        logits = mlm(**enc).logits[0, pos]
    ids = [tok(" " + o, add_special_tokens=False).input_ids[0] for o in options]
    return dict(zip(options, logits[ids].softmax(-1).tolist()))

I tried it on three inputs. “The app crashes every time I open the settings page” went to technical with 0.80. For git push --force origin main, “Is this command destructive?” came back yes at 0.78. Then “I was charged twice for my subscription this month” gave billing 0.32, technical 0.33, sales 0.35, which is flat and also wrong if you take the top pick. I like that third one more than the other two. The plumbing all works, a single pass and an answer from your list and probabilities you can inspect, but the model has no idea what a support queue is, and the flat numbers are about as close as it can get to saying “no clue”. Take the argmax and a double charge lands in sales. One caveat on the snippet: it only works when every option is a single token, which Laya avoids by giving each option its own [MASK] marker.

Why ModernBERT, and not 2018’s BERT

Laya isn’t built on the BERT from the paper, and for this kind of work the upgrade matters more than I first thought. ModernBERT saw 2 trillion training tokens, a big chunk of them code, which is handy when your inputs are mostly shell commands and JSON. It uses rotary position embeddings, runs full global attention on every third layer with 128-token local windows on the rest, and drops padding so short sequences in a batch don’t pay for the longest one. Context went from 512 tokens in the original BERT to 8,192. The large variant is 395M parameters, and Laya’s English checkpoint comes to 421M once you add the decision head.

For a decision model that’s pretty much the wishlist. You’ve got lots of short inputs, you want to batch them, and you want an answer quickly on hardware you already have. The 2018 BERT could fill in a blank too, but I wouldn’t hand it a tool call with a shell command and a JSON diff in it. For the multilingual checkpoint Laya switches to mmBERT-base, 322M parameters covering 100+ languages, and the card says it runs about 2.2x faster than the English one. Note that neither checkpoint actually uses ModernBERT’s full 8k window. More on that below, because it’s part of the cost.

Why one pass beats decoding

When a decoder answers a question, it first reads the prompt and then produces tokens, one forward pass per token, until it stops. Even a one-word reply means a prefill plus a sampling step. Ask for JSON with a reason field and you’re looking at dozens of steps, each waiting on the previous one. An encoder only does the reading part. State, question and blanks all go through in one pass, and once that pass finishes the answer is already in the blank’s hidden state. You don’t wait on a loop and you don’t parse anything.

How attention is wired matters just as much. In a causal model each token only looks backwards. Put the state first and it never gets to see the question that follows, and if you pack two questions into one prompt the second one can read the first and get pulled off course. An encoder lets state and question look at each other both ways, and the blank sees everything. The figure below is the easiest way to see the difference.

Figure - Who can see whom: three attention masks over the same two questions
Mask:
Each row is a token, each filled cell a token it may attend to. Switch the mask and hover a row. Under GPT the state never sees the questions and question 2 reads question 1. Under BERT / Laya everything sees everything, and the answer is read at a [MASK] slot in one pass. Kev keeps a causal backbone but walls the questions off from each other, so each branch sees the state and itself only.

The latency numbers fit that picture. TypeSafe quotes 70 to 500ms end to end for Jev, over the network. Laya’s card has 32.8ms p50 for a single question on a Tesla T4 with the multilingual checkpoint, and 103 to 332 questions per second batched on the same GPU. Please don’t turn those into a ratio. One is a hosted API and the other is a local GPU, so they aren’t comparable. Still, one typed question in under 50ms on an entry-level datacenter GPU from 2018 is fast enough that you stop worrying about whether a check is worth the wait.

Then the MLX port moved it onto a laptop. Laya-MLX isn’t from Convai; it’s an independent community project that went up on 19 September (pip install laya-mlx). It runs the Laya checkpoints on Apple’s MLX with no PyTorch needed at inference. Their numbers, on an M3 Max, are 13.42ms p50 for one short question on the English model and 7.39ms on the multilingual one. That excludes model loading but includes tokenization and calibration. Peak memory for the 421M model is 943.6 MiB. So you can have a guardrail using under a gigabyte, answering in around ten milliseconds, sitting right next to the agent on a developer’s MacBook. None of the hosted options can do that.

Kev: a GPT wearing an encoder

Laya alone would make for a neat headline. Kev makes it messier, and more interesting. Kev is Jared Palmer’s family of open decision models, and it’s built on a decoder. The first checkpoint runs on a frozen Qwen2.5-0.5B, which is causal all the way down. Kev just never lets it generate. The card calls it a “causal transformer, prefill-only” with a block-causal branch mask, where “a question token sees the state and its own branch only. Questions cannot see each other.” On top sits a small pointer head that compares each option’s </opt> hidden state against the question’s <decide> hidden state and takes a softmax over the options.

If you squint, that’s BERT’s setup running on GPT weights. There’s one forward pass and no generation, the answer is read at a designated slot and normalised over a closed set, and questions can’t see each other. What Kev keeps from the decoder is really just the weights, and those are cheap to adapt. On the 0.5B, rank-16 LoRA plus the head comes to 9.3M trainable parameters, 1.9% of the backbone, trained in about an hour and three quarters on an Apple M5. The data is small and all public or synthetic: 10,000 examples from ten public datasets, 896 generated policy examples, and 1,680 examples from 60 generated rule structures. The README says outright that no Jev outputs went into training.

On 20 September Palmer moved Kev to Qwen3.5 at 0.8B, 4B and 9B, for around $95 of H100 time and three cents of Jev API calls. And he hit a very predictable problem. Qwen3.5 interleaves attention layers with Gated DeltaNet layers, which are recurrent and ignore attention masks, so the branch mask stops keeping questions apart. His fix was to run each question as its own row. In other words, the newer and more decoder-ish architecture pushed back on an encoder-style job, and getting around it cost the shared pass. On the README’s new datasets Kev-9B gets 0.852 accuracy to Jev’s 0.857. Palmer is upfront that it isn’t a controlled architecture comparison, since nobody outside TypeSafe knows what Jev was trained on.

Laya

  • ModernBERT-large, 421M, fully fine-tuned
  • Bidirectional attention over state and questions
  • Answer scored at [MASK] tokens
  • 512-token context on the English checkpoint

Kev

  • Qwen backbone, frozen, 9.3M trainable on the 0.5B
  • Causal attention with a branch mask
  • Pointer head reads from a decide token
  • Inherits the decoder's long context and pretraining

So, BERT’s revenge or not? I’d say the objective won more than the weights did. TypeSafe hasn’t said much about Jev’s internals beyond “a new model architecture, parallel sampler”, and I’m not going to guess. What I can say is that every open attempt I’ve seen this week landed on the same contract encoders have always had. Read the whole input once, put the answer in a slot, and don’t generate. Kev actually makes that case better than Laya does, because Palmer started from one of the most popular open decoder families and most of his engineering went into stopping it from acting like a decoder.

Calibration is the part BERT never promised

A softmax over your options gives you numbers that add up to one. Whether they mean anything is another matter. System One’s whole selling point is that a 0.8 should come true roughly 80% of the time, so a threshold in your code actually means something, and you only get that from training. TypeSafe calls its approach Reinforcement Learning for Calibrated Decisions (RLCD). Laya uses the same name and says what the reward is: “a strictly proper scoring rule (log + spherical, plus ranked probability score for ordinal questions). Expected reward is maximised only by reporting honest probabilities.”

A proper scoring rule just means your best bet, on average, is to report what you really believe. Nudge a 0.7 up to 0.9 to sound sure of yourself and you get punished on the cases that go the other way. Worth pointing out that log loss, the cross-entropy every classifier already trains on, is itself strictly proper, so RLCD is less exotic than the name makes it sound. The piece I find actually useful is the ranked probability score for Score questions. It penalises by how far up the ladder you missed, so answering “medium” when the truth is “high” hurts less than answering “low”.

The usual way to measure calibration is expected calibration error. You bucket predictions by confidence and average how far each bucket’s confidence is from its actual hit rate. Laya’s card puts its routed model at 0.081 and Jev at 0.246, which would be a big win for Laya if the comparison held up (I get to why it might not in a minute). Laya-MLX’s docs also carry a warning I’d repeat to anyone: confidence is not correctness, and calibration has to be checked on the distribution you actually deploy on. Your tool calls aren’t the model’s training distribution until you make them so.

The practical consequence: Calibration makes your thresholds mean something, but only on data that resembles what the model was calibrated on. Plot a reliability curve on your own logs before a probability gates anything destructive.

The bill: encoders need your data

BERT lost the hype war for a reason, and that reason is still around. You can point a decoder at a new task, describe it in plain English, and get something half-decent back. An encoder usually needs fine-tuning before it’s good at much of anything. My three-input experiment above is a small taste of that. Laya’s model card says it more bluntly: “Base checkpoints are near chance on typed-decisions zero-shot.” On their benchmark the English base gets 0.362 where random guessing gets 0.318. The 0.766 number everyone’s quoting is from a checkpoint fine-tuned on that same benchmark’s training split.

That card also lines Laya up against Jev on accuracy, calibration and latency, and Laya comes out ahead on all of it. Then you hit the footnote. The Jev numbers are “third-party published, never measured here”, and the card warns that sample sizes and prompts differ. Fair enough that they disclose it, but until someone runs both models on the same questions I’m reading that table as marketing. Kev’s comparison is more careful, and it’s the one where Jev still comes out slightly ahead.

Context is the other cost. Jev takes up to 64k tokens per request. Laya’s English checkpoint takes 512 and the multilingual one 1,024, and that has to cover the state, the instructions and all the options. A tool call with a big diff won’t fit. Something upstream has to choose what the model sees, and that filtering step is now yours to build and maintain. Laya-MLX also says it isn’t designed for arithmetic, counting or date comparisons, which overlaps with the weak spots TypeSafe lists for Jev. Keep those in code. I suspect that’s true of this whole class of model and not just one vendor.

What the clones actually sell: Not zero-shot smarts. What you get is a cheap, local, fine-tunable slot-filler that speaks the Jev API. If your logs are full of labelled decisions, that’s a great deal. If they aren’t, the hosted model’s generality is the thing you’re paying for.

It’s the old encoder vs decoder trade-off again. Jev seems to be trained broadly enough that it’s useful as soon as you write a question. The open encoders are fast and you own them, but they’re only as good as the labelled data you give them. In a harness the questions tend to be few, stable, and asked millions of times, so owning the model often wins. And you probably have the labels already. Every tool call a human approved or rejected is a training row, and every escalation that turned out to be a false alarm is a calibration point. With an open encoder your eval set and your fine-tuning set are the same file, which makes the loop much shorter than anything you can do against a hosted API.

Jev, Laya, Laya-MLX or Kev: which one

These four aren’t really fighting over the same spot. Each one makes a different call on generality versus control versus where it runs, so it’s more useful to ask which of your decisions should go where.

Jev
hosted, general
Useful zero-shot, 64k context, undisclosed architecture. Where you prototype questions and find out which ones are worth keeping.
Laya
open encoder
ModernBERT or mmBERT, Apache 2.0, fast on a T4. Needs a fine-tune on your decisions before it is accurate; 512 to 1,024 tokens of context.
Laya-MLX
local, Apple Silicon
Community MLX port of the Laya checkpoints. About ten milliseconds and under a gigabyte, so it can run beside the agent on a laptop.
Kev
open decoder, encoder-shaped
LoRA on Qwen at 0.8B to 9B, trainable for about $95. Closest open result to Jev on its own evals, with a decoder's longer context.

In a coding harness I’d run them as a relay. New judgements start on Jev, since writing a good question is the hard part and a general model tells you quickly if you’ve written a bad one. Log every answer alongside what the human eventually did. When a question has settled down and has a few thousand rows behind it, move it to a fine-tuned Laya or Kev running locally, and keep Jev around for anything new, rare or too long for a 512-token window. Jev ends up doing discovery and the local model handles the volume. It’s the same split we use with a probe agent and the fast checks in front of it, just one layer lower.

Let us breathe: the spec won

What spread fastest this week wasn’t any particular set of weights. Laya ships Noul, Choice and Score. Kev’s server exposes POST /v1/systemone and matches TypeSafe’s System One API. A community Node wrapper for Laya calls itself Jev-compatible. Five days after launch, three independent codebases were implementing the same interface, and what runs behind it (a hosted model nobody outside has seen, a fine-tuned ModernBERT, a LoRA on Qwen) had turned into a deployment choice.

If you’re building a harness, I think that’s about the best thing that could have happened. Write your guardrails and routers against the typed contract, prototype on the hosted model, and once your logs have enough labels, move the busy, stable questions onto a local encoder. For three years we’ve been asking models what comes next, when most of the decisions in an agent loop were fill-in-the-blanks all along. It took one launch and a very long weekend for the tooling to catch up with that.

Jev vs BERT: FAQ

Is Jev a BERT model?

TypeSafe has not said. The announcement describes “a new model architecture, parallel sampler” that produces all outputs in a single query and is trained with RLCD, and gives no encoder or decoder details. What is public is that the open Jev alternatives converged on encoder behaviour: Laya is a fine-tuned ModernBERT, and Kev runs a decoder in prefill-only mode with no generation.

What is Laya?

Laya is an open-source System One decision model from Convai Innovations, released under Apache 2.0. The English checkpoint is ModernBERT-large, 421M parameters in total, and the multilingual one is mmBERT-base at 322M across 100+ languages. It answers Noul, Choice and Score questions by scoring each option at its own [MASK] token and reports 32.8ms p50 per question on a T4.

What is Laya-MLX?

An independent community port of the Laya checkpoints to Apple’s MLX framework, released on 19 September 2026 and installed with pip install laya-mlx. It needs macOS 14+ and Apple Silicon, and reports 13.42ms p50 for the English model and 7.39ms for the multilingual one on an M3 Max.

What is Kev?

Kev is Jared Palmer’s open family of Jev-style decision models, Apache 2.0, built as a rank-16 LoRA adapter and a pointer head on Qwen base models. The Qwen3.5 generation ships at 0.8B, 4B and 9B. It serves the same POST /v1/systemone API, and on its README’s new datasets Kev-9B scores 0.852 accuracy against Jev’s 0.857.

Why are encoder-only models good at System One decisions?

A decision is a fill-in-the-blank over a closed set of options, which is the masked language modelling task encoders were pretrained on. An encoder reads the state and the question in both directions in one forward pass and the answer is read at the blank, with no token-by-token decoding and no text to parse.

Can I use Laya instead of Jev?

For stable, repeated questions where you have labelled data, often yes, after fine-tuning. Laya’s own card says the base checkpoints are near chance zero-shot, 0.362 against a 0.318 random baseline, and its context is 512 to 1,024 tokens against Jev’s 64k. Its published wins over Jev compare Laya’s own runs against third-party Jev figures with different prompts and sample sizes.

Follow in Google

Make Metaheuristic a preferred source.

One tap and posts like this one surface higher in your Top Stories.

Work with us

Production AI, with guardrails.

Start with a fixed-scope AI Workflow Audit. We map the opportunity and quote a build.

Start a Discovery Sprint →