The Typesafe JEV Model: Is It a New Post-LLM AI?
…or a useless toy? We pointed JEV at real session-replay data and measured it: 37 typed judgments across five calls, 763 ms per call, $0.000635 in total. Here is where it genuinely beats an LLM, the three results that argue against the hype, and why the honest answer is neither of the two options in the question.
TL;DR
JEV is a real new primitive, and it is not a replacement for an LLM. It answers one question at a time with a probability, a label or a score — never prose — so every answer maps onto an if that already exists in your code. That makes it dramatically better than a chat model at the thing we keep misusing chat models for: decisions. It measured 763 ms for 19 questions in one call, $0.00014 per session, and it returned confidence 0.24 on a genuinely ambiguous case where a hardcoded threshold would have just guessed. It is also not magic: one question returned 0.77 on both of two very different sessions, another was confidently wrong on 1 of 19 URLs, and anything resembling narrative, reasoning or multi-factor analysis is still an LLM job. Treat the probability as a ranking signal, never as permission to delete.
LogNroll Team
Engineering & Product
Every number in this article comes from a file on disk, not from documentation. We built a compact state out of real captured sessions from a live tenant, POSTed it to the TypeSafe API, and wrote each run down with its model version, token count, latency, cost and raw answers. The probes, the ledger and the raw probabilities are reproduced at the end.
Two meanings of the phrase new AI
The question “is this a new AI or a useless toy?” is really two questions stacked. The first is whether the capability is real and durable — which is testable. The second is whether it replaces the thing you already have — which, in this case, is the wrong question entirely. Only the first one deserves a verdict.
The question is badly framed
“Post-LLM” and “useless toy” share an assumption: that a new model either eats the old one or is worthless. That assumption is how the industry has read every model release for three years, and it is now actively misleading, because the interesting new systems are not competing with language models at all. They are occupying a layer language models were never good at.
Consider what we currently do with a chat model when we need a decision. We ask it to classify a support ticket, route an alert, tag an error, or label a session. We get back a paragraph, we bolt a JSON grammar constraint onto it, we parse it, and we hope the parse holds. Underneath a hundred lines of prompt engineering and schema validation, we are using a system that predicts the next token in order to answer a yes-or-no question.
That is a category error with a bill attached. A model that writes prose must emit the prose before you can read the decision buried inside it — you pay for the tokens, you wait for them all, and you inherit a text surface that can be manipulated by the text it is classifying. JEV is a different answer to the same question: a model whose entire output is the decision.
From a different direction
TypeSafe named their model System One. That is the cognitive-science term for the fast, automatic, intuitive half of human judgment — the glance that says “something is wrong with this page”, as opposed to the slow reasoning that explains why. Naming it that is the clearest statement of scope the vendor could have made, and the measurements below are consistent with it.
What it actually is
Mechanically it is almost nothing. You send one JSON state and a set of typed questions to POST /v1/systemone. You get back typed answers: no prose, no reasoning trace, no tool calls, no chance to negotiate. There are three question types, and the interesting thing about them is how literal the mapping to code is.
Noul — Is this true?
A single probability and nothing else. There is no separate confidence field, because a two-outcome distribution is fully described by the probability itself.
noul — a probability from 0 to 1Choice — Which of these options?
A label plus the full distribution over the options you supplied. The distribution is the useful part: it shows how close the runner-up was.
choice, probabilities, confidenceScore — Which level on this spectrum?
A position on an ordered scale you define in words — not a numeric rating with no meaning attached to 7 out of 10.
score, probabilities, confidenceNote what is absent. There is no “explain your answer”. There is no temperature, because there is no sampling to control. There is no system prompt to jailbreak, because the output has no text surface: the only channel from your state into the world is a number in a field. For anyone who has watched a classifier get talked out of its own instructions by the document it was classifying, that is a security property, not a limitation.
answer shapes, as returned
noul → { "noul": 0.98 }
choice → { "choice": "backend_api", "confidence": 0.89,
"probabilities": { "backend_api": 0.91, "site_frontend": 0.09 } }
score → { "score": 0.92, "confidence": 0.40,
"probabilities": { "0": 0.34, "1": 0.43, "2": 0.21, "3": 0.02 } }That last line is the part most people miss. choice and score return a probability distribution and a confidence that is separate from the winning probability. In the run above, the model picked backend_api at 0.91 and reported confidence 0.89 — agreement. In another run it picked third_party_marketing with a winning probability of only 0.48 and reported confidence 0.24: it had essentially no idea, and it said so.
That two-axis output is what makes confidence-gated routing possible rather than aspirational: high probability plus high confidence acts automatically, low confidence goes to a human queue, and you get to draw those two lines yourself. Compare that with the single boolean a keyword rule or a temperature-zero LLM call gives you, which has no way to represent “I am not sure” at all.
What we measured
Specifications are cheap, so we built a state document out of real captured sessions and ran three probes. The state builder is the discipline that makes this work: it reduces a session to a bounded aggregate — event-count histogram, console messages deduplicated by prefix and clipped, only failed or slow network requests, the first few form fields — landing at roughly 4.7 KB, about 3,300 input tokens. Never the raw event stream.
| Probe | Shape | Latency | Input | Cost | Answer |
|---|---|---|---|---|---|
| Session with an IDENTIFY event | 6 questions, 1 call | 713 ms | 3,292 in | $0.000138 | pii_present = 0.98 |
| Same site, no IDENTIFY event | 6 questions, 1 call | 699 ms | 3,364 in | $0.000141 | pii_present = 0.19 |
| Ambiguous session (pixel noise) | 3 questions, 1 call | 753 ms | 3,040 in | $0.000128 | own_code_defect = 0.57, confidence 0.24 |
| Clear defect (TypeError + HTTP 500s) | 3 questions, 1 call | 751 ms | 2,340 in | $0.000098 | own_code_defect = 0.92, confidence 0.89 |
| URL classification fan-out | 19 questions, 1 call | 763 ms | 3,080 in | $0.000129 | 18 of 19 labels correct |
All five calls answered on jev-1.13.0, requested through the jev-latest alias. Total spend for the entire investigation: $0.000635.
763 ms
FOR 19 QUESTIONS
$0.00014
PER SESSION JUDGED
37
JUDGMENTS, 5 CALLS
The claims that held up
Cost is input-token-only, and output tokens are free
At $42 per billion input tokens, the five calls above cost $0.000635 in total. A whole session judged at full state is about $0.00014; a session judged from its stored summary is about $0.00004, which is roughly $4 per month at 100,000 sessions.
Fan-out is nearly free
Nineteen URL-classification questions in one request answered in 763 ms — statistically the same latency as the six-question calls. Every question in a request sees the same state and is evaluated in parallel, so the marginal question costs tokens, not time.
Calibrated probability is a second axis you can route on
The genuinely ambiguous session came back at confidence 0.24 while the labelled clear defect came back at 0.89. That is a threshold-only rule saying "I am unsure", which is precisely the signal a hardcoded constant can never produce.
It answers the question it is given, at the level it is asked
The strongest result in the set: the same state builder, the same site, pii_present 0.98 with an IDENTIFY payload and 0.19 without it. It is reading the state rather than pattern-matching the prompt. The catch is that a badly-posed question returns a confident, useless number — see the full-fidelity result below.
The PII contrast: the single best result
Two real sessions from the same site, same state builder, same questions. One contained an IDENTIFY event carrying a name and an email; the other did not. Asked whether the captured state contained personal data that must be masked or purged before storage, the first came back at 0.98 and the second at 0.19.
That gap is the whole argument for a decision model in one number. A keyword rule cannot do it, because the failure mode is semantic — a field named dob, iban or passport, or a free-text comments box, in any of the languages a platform serves, and no substring list converges. An LLM can do it, but you would be paying for prose you throw away, in a path that runs on every session. And critically: the discriminating model was reading the state, not paraphrasing the prompt. A model pattern-matching the instruction would have returned the same number for both sessions.
Where it is a toy
This is the part a vendor post would omit, and it is the part that decides whether you can ship the thing. Four failures, in descending order of how much they should change your architecture.
A question can fail to separate two cases, and no threshold fixes it
We asked whether a session was worth keeping at full fidelity rather than being sampled down to aggregates. Two real sessions, one with an error and one without: both scored 0.77. The model was not wrong — the question was. "Worth keeping" is not a judgment a person makes in a second from a session summary, and it produced a constant instead of a signal.
What it means: A decision model does not rescue a vague question. It gives you a number for it, which is worse, because the number looks like an answer.
It is confidently wrong sometimes, and it tells you when
In the 19-question URL batch, https://www.google.com/ccm/collect was labelled static_asset instead of third_party_tracking. Its confidence was 0.88 — the lowest in the entire batch, and the batch’s only error. The failure was flagged by the model’s own confidence, but the label was still wrong.
What it means: An 18-of-19 classifier must never be the sole reason data is deleted, an alert is silenced, or an error disappears. Make a wrong answer cause a wrong grouping, never a wrong deletion.
It cannot write the sentence, and you should not ask it to
"Summarise this session for the support agent" is a generation task and stays an LLM task. But the better architecture is to generate the narrative from the typed answers rather than asking a language model to re-derive facts a classifier already returned as probabilities.
What it means: Keep the LLM for prose. Move the decision out of the prose.
It is not a multi-factor analyst
"Analyse this session and tell me what to fix" is not a judgment a person makes in one second, and it is not one request here either. Decompose it into independent questions that each mirror a single human glance, then combine the answers in ordinary code.
What it means: If you cannot phrase the question as a thing a person decides at a glance, you are asking for reasoning and you want a different tool.
The deepest limitation is not the model, it is the state
Accuracy degrades as the state grows with material irrelevant to the question, so the state must stay a bounded aggregate. That is fine for a session summary, and impossible for a multi-megabyte trace. Anywhere your data does not compress to a few kilobytes of relevant facts, this tool is simply unavailable — and the compressing is your work, not the model’s.
The operational traps nobody mentions
This is where the difference between a demo and a shipped feature lives, and we found these by reading our own production services rather than by reading any model documentation. If you are integrating a decision model into a pipeline that already processes data, the model is the easy part.
Adding one processor can re-queue two weeks of history
Our session processor tracks a per-processor version map and a processedVersion total computed by summing the individual processor versions. That sum is what the lock filter compares against. Adding a new processor with a non-zero version bumps the total and makes every previously PROCESSED session eligible for reprocessing. Budget for that deliberately, or run the judgment as a post-pass in a service that is not in the pipeline loop.
Duplicate ordering annotations silently make order undefined
Two of our processors declare the same @Order value, so their relative execution order is undefined today, and one of them deletes rows the other owns. A model placed into that pipeline without fixing the ordering inherits a nondeterministic input. Fix the ordering before you add intelligence to it.
An audit that sends session content is the thing it audits
The privacy use case sends data to a third party in order to find out whether that data should have been in the payload at all. That is a data-processing question, not an engineering one: it needs a DPA, and the excerpt should be minimised to only the fields that failed masking. The triage use cases can be designed to send aggregates, message text and URL shapes only, which is a much easier conversation.
A judgment outage must degrade to today’s behaviour
Musts: a hard budget and a kill switch per question, a failed call treated as “no judgment” rather than an exception, and never a retry storm inside a processing loop — especially where an exception releases a lock and makes the work immediately reclaimable. Also pin the model version. An alias that silently moves can change your answers without a line of your code changing, and thresholds tuned against the alias become fiction.
And one non-negotiable piece of instrumentation: store the raw probability, not just the boolean you derived from it. If all you keep is “fired” and “did not fire”, no threshold you chose can ever be evaluated afterwards, because you threw away the number that produced it. Ledger every judgment with its model version, confidence, cost and latency, and run it read-only for two weeks before it is allowed to change anything.
What the integration looks like
The reason this is worth taking seriously is how small the code is. The entire decision surface is one call, and everything downstream of it is the ordinary code you already have. Here is our error-digest triage, which today ranks aggregated errors by occurrenceCount alone — so a customer’s real defect competes for attention with an advertising pixel complaining about an invalid event name.
// One POST, one call. The state is bounded; the questions are typed.
const res = await jev.ask(state, {
actionable: {
type: 'noul',
instructions:
'Could an engineer on the site\'s own team change code, configuration ' +
'or content to fix this error? Third-party tags, marketing pixels, ' +
'browser extensions and recorder-internal messages are not actionable.',
},
owner: {
type: 'choice',
instructions: 'Which owner should be given this error message?',
criteria: {
site_frontend: 'The site\'s own application code or markup',
backend_api: 'A server-side endpoint erroring or too slow',
third_party_marketing: 'Advertising, analytics or tag-manager scripts',
recorder_sdk: 'The session-recording SDK itself',
not_a_defect: 'Informational, not a defect',
},
},
severity: {
type: 'score',
instructions: 'How much does this degrade a user\'s ability to finish?',
criteria: [
'Cosmetic or invisible to users',
'Annoying, but the flow completes',
'Blocks a step of the flow',
'Blocks the whole task',
],
},
});
// Everything below is ordinary code. No model in the loop.
const j = res.answers;
const show = j.actionable.noul >= 0.7 && j.owner.confidence >= 0.5;
const rank = Math.round(j.severity.score * 40 + Math.log1p(occurrences) * 10);
if (j.actionable.noul >= 0.4 && j.actionable.noul < 0.7) {
queueForHumanReview(error); // the "I am unsure" band, made explicit
}Three questions, one request, one round trip per error group per company per day. No keyword list to maintain, no prompt-injection surface in the output, and the thresholds are three numbers you can argue about and tune — which is the actual deliverable. The 0.4–0.7 band is the design detail worth stealing: it is the region where the model is telling you it does not know, and it maps directly onto a human review queue instead of a coin flip.
The economics change what you can afford to decide
A session judged from its stored summary is about $0.00004 — roughly $4 per month at 100,000 sessions. At that price the interesting question stops being “can we afford to judge this?” and becomes “what else could we judge that we currently only guess at?” The real constraint is not money. It is latency: one call per replay load is fine, one call per event is not.
The verdict
Neither option in the headline is right, and the useful answer is a third one. JEV is not post-LLM, because it does not replace language models and does not try to. It is not a useless toy, because a measured 763 ms for 19 parallel judgments at $0.000129, with a confidence field that says “I am unsure” on the genuinely ambiguous cases, is a capability no threshold rule and no chat model currently gives you at that price.
What it is, precisely, is a decision layer. And the reason the framing feels new is that we have spent three years using a generative model for decisions, then papering over the mismatch with structured-output schemas, validators and retry logic. JEV removes the mismatch by removing the generation. The output is the decision.
So the practical litmus test is one question, and it is not about the model at all: does the thing you want back map onto a branch in your code? If yes, a decision model is the right shape and an LLM is an expensive detour through prose. If the thing you want back is a sentence a human reads, keep the LLM and stop looking for a replacement. Most teams have far more of the first kind of problem than they think, which is exactly why this stopped looking like a toy to us.
| You want | Use | Because |
|---|---|---|
| A branch in your code | A decision model | The output is already the shape of the branch: a float to compare, a label to switch on. |
| A sentence a human reads | An LLM | This is generation, and generation is what language models are actually for. |
| A hundred cheap independent judgments per call | A decision model | Fan-out is parallel and nearly free; a chat model pays for each one in latency and output tokens. |
| Reasoning across a long, messy document | An LLM | Accuracy degrades as state grows with material unrelated to the question, so the state must stay bounded. |
| A decision where being unsure must be visible | A decision model | Probability and confidence are separate fields, so low confidence can route to a human instead of guessing. |
If you take one thing away
The risk with this tool is not that it is a toy. The risk is that it is easy enough to wire up that people will use it as an oracle: judge once, trust the number forever, never measure whether the threshold was right. The model gives you a probability and a confidence, and it is telling you the truth in both. Ship it in shadow mode, keep the raw numbers, and let two weeks of your own traffic decide the thresholds — not the vendor’s demo, and not this article.
Reproducing this
Nothing above required a special data set. If you have session-replay events, you have everything needed: reduce a session to a bounded aggregate, phrase each decision as a question a person answers at a glance, and write down the raw answer next to the version of the model that produced it.
# 1. A real session -> a bounded state (~4.7 KB, ~3.3 k input tokens)
node build-state.mjs session-user-events.json
# 2. Six judgments in ONE call: PII, owner, struggle, fidelity
node probe.mjs session-user-events.json
# 3. The contrast that matters: same site, IDENTIFY vs none
node probe.mjs server-events.json
# 4. Discrimination and confidence on a known-ambiguous session
node contrast.mjs session-user-events.json
# 5. Nineteen URL-classification questions, one call
node url-discovery.mjs
# 6. The ledger: model, tokens, latency, cost, raw answers
cat live-runs.jsonlTwo notes on reproducing it honestly. First, the contrast probe compares a real captured session against a labelled synthetic counterfactual (a first-party TypeError plus HTTP 500s) — it demonstrates that the model responds to a clear defect, and it is not a real production defect. Second, the state document contains one real session’s IDENTIFY payload, which is why the ledger stores only models, tokens, timings and answers, and never the state itself.
The tools this connects to are worth reading in their own right: the primitives define the three answer shapes, and the confidence-routing pattern is the part that turns a probability into a workflow. If you want the vendor-neutral version of the same argument, the framing of a decision layer rather than another chatbot has been made independently as well.
Or just watch what your users already told you
Most of the decisions in this article are judgments about sessions you have already recorded: which errors matter, which sessions were worth watching, which flows leaked data they should not have. You do not need a decision model to ask those questions — but you do need the sessions.
Related reading
- How to Run an AI-Assisted Pentest on Your Website (Without the AI Attacking for Real)
- Local LLMs for Session Replay Debugging: Keeping User Data Off Someone Else’s Server
- An MCP Server for Session Data: Giving AI Agents Access to Replay
- Error Tracking with Session Replay: Debugging Production for Real