Ask for features, not answers
Ask Jev "is this email a phishing attempt?" and you get a mediocre classifier. Ask it five narrow questions about the email and put the answers through ten lines of your own code, and you get a very good one. This is the highest-leverage idea on the site.
By the end you'll be able to
- Decompose a verdict into observable signals
- Combine signals in code instead of trusting the model's summary
- Recognise when a question is too abstract to answer well
- Know why this beats asking for the conclusion directly
On this page
The finding#
An independent benchmark run in launch week put Jev against 2,000 emails and asked it to judge whether each was a phishing attempt. Jev's own verdict scored 62.6%, against Claude Haiku 4.5's 81.3% — a clear loss, statistically significant.
The same author then ignored Jev's verdict and instead fed five narrow signal questions into a plain logistic regression. Same model, same emails, same API. Accuracy: 95.1%, AUROC 0.988.
Why this happens#
Jev is trained for System One judgments — the call a knowledgeable person makes in a second given the right context. "Is this phishing?" is not that. It is a conclusion that depends on a dozen observations, weighed against each other. TypeSafe's own docs warn about exactly this shape: "Analyze this message and determine the best course of action" is not [a good question]. That needs slow reasoning, and it is a signal to break the task into small questions.
Asked for a verdict, the model has to do the weighing somewhere you cannot see, cannot adjust, and cannot audit. And it is not good at it — multi-hop indirection is one of its nine documented failure modes.
Asked to a founder directly on X, the answer was the same. Someone asked whether they could send their whole codebase and ask "is it safe to deploy?" Diogo Almeida's reply:
you can ask that, but it probably won't go well! / jev does better with smaller decomposed questions that compose into harder tasks
Rewriting a question as signals#
The move is mechanical once you see it. Take the verdict you want, and ask: what would I have to notice to reach it? Each of those observations is a question. The verdict is arithmetic.
| Verdict question (bad) | Signal questions (good) |
|---|---|
| Is this email a phishing attempt? | Does the sender domain differ from the organisation it claims? Does it ask for credentials? Does it create artificial time pressure? Does the link text differ from its target? Is the greeting generic? |
| Is this code good? | Does this function duplicate logic that exists elsewhere in the file? Does it handle the error case it opens? Are its names consistent with the surrounding code? |
| Is this paragraph well written? | Is there a sentence that only restates the sentence before it? Does the paragraph make more than one claim? Does the first sentence state the paragraph's point? |
| Should we approve this refund? | Does the customer state a reason covered by the policy? Is the order inside the return window field we computed? Has this account requested more than two refunds in the records we attached? |
Two developers reported the same effect in the same week, in different domains. One found that "it cannot answer 'quality', but it can answer 'duplicated_code' very well". Another, working on prose:
“Is this paragraph well written” gave me nothing usable; “is there a sentence that only restates the sentence before it” works. Rewording three rules from paragraph-level to sentence-level took them from 5/8 to 7/8 caught.
In code#
from typesafe_sdk import Noul, Score, TypeSafeClient
client = TypeSafeClient(model="jev-1.13.0")
SIGNALS = {
"domain_mismatch": Noul(
instructions=(
"Does the sender address in `headers.from` use a domain other than "
"the organisation named in `body`?"
),
),
"asks_for_credentials": Noul(
instructions="Does `body` ask the reader to enter a password, code, or payment detail?",
),
"manufactured_urgency": Noul(
instructions=(
"Does `body` claim the reader must act within a short deadline "
"to avoid a negative consequence?"
),
),
"link_text_mismatch": Noul(
instructions="In `links`, does any anchor text name a different domain than its href?",
),
"generic_greeting": Noul(
instructions="Does `body` open without using the recipient's name?",
),
}
def signals(email) -> dict[str, float]:
r = client.system_one(state=email, questions=SIGNALS)
return {key: r.nouls[key].noul for key in SIGNALS}Now the verdict is yours, and it is ordinary software. Start with weights you can defend in a meeting:
import math
WEIGHTS = {
"domain_mismatch": 2.4,
"asks_for_credentials": 1.9,
"manufactured_urgency": 1.1,
"link_text_mismatch": 2.1,
"generic_greeting": 0.4,
}
BIAS = -3.2
def phishing_score(s: dict[str, float]) -> float:
z = BIAS + sum(WEIGHTS[k] * s[k] for k in WEIGHTS)
return 1 / (1 + math.exp(-z)) # logistic, nothing exoticAnd once you have a few hundred labelled examples, stop guessing the weights and fit them. This is the step that took that benchmark from 62.6% to 95.1%, and it is four lines:
from sklearn.linear_model import LogisticRegression
X = [[s[k] for k in SIGNALS] for s in collected_signals] # one row per email
model = LogisticRegression().fit(X, labels)
print(dict(zip(SIGNALS, model.coef_[0]))) # your weights, learnedThe counterintuitive part#
Every instinct from classical ML says to keep the number of questions small, because more outputs means more chances to be wrong. Jev inverts that, and practitioners noticed immediately:
It's a bit weird and unintuitive using Jev. Generally, you want to decrease the # of questions you ask a classifier, or it makes more mistakes… With Jev, you feel incentivized to go the other way, to formulate your query as a set of independent questions.
The reason is structural. Questions in a request are evaluated independently and in parallel, so a wrong answer on generic_greeting does not corrupt domain_mismatch. Errors do not compound across questions the way they compound across the steps of a chain of thought. They land as noise in separate features, and a weighted combination is exactly the right tool for averaging noise out.
Where this leaves the verdict field#
You will still use Choice and Score for genuine decisions — routing a ticket to one of four teams is not a hidden aggregation, it is a real classification. The rule is narrower than "never ask for a conclusion":
- Ask directly when the answer is an observation: which language is this, which team owns this, is there a date in this field.
- Ask for signals when the answer is a judgment built from several observations: is this risky, is this high quality, should we approve this.
- The test: if you would need to explain your reasoning to justify the answer, ask for the inputs to that reasoning instead, and do the reasoning yourself.
Next#
This pattern and composite scoring are the same idea seen from two angles — one starts from a verdict you want to decompose, the other from dimensions you want to weigh. Read them together. Then what the numbers actually say for the rest of the independent evidence.
Sources for this page
- TypeSafe — Primitives
- TypeSafe — jev-1.13 jaggedness
- jev-phishing-bench (independent, community)
- agentjournal.dev — LLM judge vs feature extraction
- Diogo Almeida on decomposed questions
Last reviewed 2026-09-18. Jev is days old and moving — where a claim is TypeSafe's own rather than independently verified, this page says so in the sentence that carries it.