Last week, on September 15, TypeSafe AI introduced Jev, its first public “System One” model. I wanted to try it on two tools I use every day and see what it could actually improve:
- Etabli, my personal setup for working with several agents, where each request needs the right workflow;
facteur, a little Grok bot that sorts my mail and brings up whatever needs my attention.
In Etabli, the question was “which route should this take?” With facteur, it was more “does this email deserve to show up?” Different problems, but the same need: a short, usable decision, without handing permissions over to the model.
The short version of Jev
Jev takes a state, answers closed-ended questions and returns typed values with their probabilities. You can try it in the Playground or call its API.
The three main primitives make more sense with an email or a workflow choice in mind:
Choicepicks a category from a list, such as “to do”, “to reply”, “to read” or “noise”;Scoreestimates a level of urgency;Noulanswers a question like “does this message need a reply?”
The code uses these values to accept the answer or return to its usual path. For Choice and Score, confidence summarises the probability distribution. A confidence of 0.8 doesn’t mean an 80% chance of being right.
Routing Etabli
A request sent to Etabli might need a direct answer, research, a plan, an implementation or a review. It might also need to stop because it involves a secret, a deployment or a destructive operation.
I plugged Jev into route selection. It gets a state limited to the useful information and proposes an option from a closed list. Protected routes, permissions and the actual state of the plan stay under the code’s control.
Here’s a simplified version of the call. The 0.7 threshold is illustrative; tune it on your own cases. This snippet chooses a route, it doesn’t execute an action. Permission checks are still required before anything runs, whatever Jev’s confidence says.
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const jev = new TypeSafeClient();
const { answers } = await jev.systemOne({
state: {
request: "Redeploy production with the new token",
context: "personal repo, Coolify deployment, secrets in environment variables",
},
questions: {
route: choice("Which route should this request take?", {
answer: "direct question, no code",
research: "research is needed before answering",
plan: "multi-step work, start with a plan",
implement: "code change",
review: "check a change",
stop: "secret, deployment or destructive operation",
}),
},
});
const route =
answers.route.confidence >= 0.7
? answers.route.choice
: deterministicRoute(); // Keep the existing deterministic fallback.
Six options, no more. If Jev hesitates, the deterministic router takes over. Nobody minds.
I didn’t switch it on all at once: first in shadow, comparing its answers with the existing router, then live on the validated scope only. On a corpus of 30 cases replayed three times, the router reached 93.3% raw accuracy, 100% when keeping only the sufficiently confident answers, with 64.4% coverage and a p95 of 349 ms. Roughly two decisions out of three; the deterministic fallback handles the rest.
I then tested a different setup to save tokens. Jev proposes a category used to select compact, prewritten instructions for the LLM. This saves it from rereading the routing and workflow selection documents. In the version enabled on Pi, the code fixes the route and rejects any Jev proposal that contradicts it.
Across seven tasks repeated three times, this candidate cut traditional LLM tokens by 39.62%: 1,472,609 down to 889,124. That measures Jev and the compact instructions together. Results under our grading criteria stayed the same, including two tasks that still failed in both versions, while protected cases held. I was aiming for 50%. I didn’t get there.
I used this small internal benchmark to adjust the candidate over successive attempts. It covers the planning, implementation and review routes tested at the time, without proving we’d save as much on other tasks. The plan-implement route, added later, isn’t included. The measurements and their limits are in the campaign report.
Sorting my mail
For email, I’m starting with a much more ordinary problem. My inbox has a few important messages buried among platform notifications, repo alerts, spam and newsletters. A mess to work through every morning.
I already had an MCP server that could search, read messages and prepare a draft under certain conditions. It caps the text it returns and doesn’t return attachment contents. ALLOW_SEND enables both drafts and sending. The client calling the MCP therefore needs to handle human approval before each operation.
After a very long brainstorming session (a few seconds), facteur was born: the Grok bot that calls the MCP. I ask it to bring up whatever needs action and leave the rest in counters.
In a recent run, it displayed five items to act on and three to read, the latter marked “low confidence”. Here’s an abridged, anonymised version of the report:
attention · 8
to do
- check a security alert
- check a GitHub OAuth authorisation
- check a second GitHub OAuth authorisation
- check a travel account login
- note when a subscription ends
to reply
(none)
to read
- a job search guide
- a project import notification
- an update from a followed project
silent: noise 3 · repos 12 · jobs 7 · tech news 5
This run shows what Grok displays. It contains no trace of a Jev call, so I can’t attribute the result to Jev. Even “low confidence” doesn’t tell me where that confidence came from.
To add Jev to this triage, I’d split the decision into small questions: a category with Choice, a property with Noul, then an urgency level with Score. The LLM could then write the report from those signals.
Here’s an example call for those three signals. It illustrates the integration that still needs verifying in facteur. This reuses the jev client from the first example; from, subject and body come from the email:
import { choice, noul, score } from "@typesafe-ai/sdk";
const { answers } = await jev.systemOne({
state: { from, subject, body },
questions: {
category: choice("Where should this email go?", {
"to do": "needs an action other than a reply, including an alert to check",
"to reply": "expects a reply",
"to read": "worth reading sometime, not urgent",
noise: "spam or a message with no action or reading value; exclude alerts to check",
}),
human: noul("Did this message come from a human?"),
urgency: score("How urgent is it?", [
"can wait a week",
"needs attention today",
"needs attention within the hour",
]),
},
});
Here’s the simplest branch for sorting the result. mail is the current message, report is the list to show and silence is the counter. This isn’t the whole triage policy. Before automatically hiding emails, you also need to handle uncertain answers and measure mistakes.
if (answers.category.choice === "noise") {
silence++;
} else {
report.push({
mail,
category: answers.category.choice,
urgency: answers.urgency.score,
});
}
For replies, I’d keep an explicit step before creating the draft: the LLM proposes the text, I approve it, then the client calls the MCP. Jev could flag a message that needs a response; permission to write or send would still need to be handled separately.
The setup I kept
I’m keeping the path used in Etabli as a starting point for further work on facteur:
verifiable sources
↓
bounded, versioned state
↓
typed judgments with probabilities
↓
deterministic policy and abstention
↓
reversible action or human checkpoint
↓
receipt, measurement and rollback option
Jev’s decision is one input among others. The code already knows which actions are allowed, what the fallbacks are and when it needs to ask for confirmation.
Calibration is measured across a set of answers. Choosing confidence thresholds requires a representative corpus, tracking abstentions and checking errors.
For Etabli, that means keeping the question, model, thresholds, repetitions, fingerprints of the loaded code, quality, latency and rollback test. For facteur, I’ll mainly need to label real messages and look at the triage mistakes before automating more.
The limits
The jev documentation lists limitations around numbers, dates, indirection, irrelevant context and adversarial content. I keep a few simple rules:
- Jev can judge whether a text sounds urgent, but the code compares dates;
- it can classify an intent, but it doesn’t create a permission;
- it can spot a signal, but it doesn’t replace business validation;
- the state sent to the model contains only the fields it needs;
- an irreversible action keeps a code-level guard or human confirmation.
What gets sent to the provider matters as much as the result. For an email or business data, I want to know exactly what leaves the machine and why.
Before adding Jev somewhere, I look for a closed-ended decision, uncertainty that actually changes the system’s behaviour and an available deterministic fallback. Otherwise, an if, schema validation or a regular LLM will often do the job better.
To dig deeper
The Awesome Jev community directory collects other experiments:
- Jev Ultrafast drives a browser: Jev picks the operation and the DOM element;
- fast-jev-compaction cleans up the context of a Claude Code session;
- Jevmail is a local app that reads Gmail without changing it and calls Jev through Vercel AI Gateway for triage;
- pg-jev filters PostgreSQL rows with a natural-language condition.
It might just be passing hype, honestly no idea. But experimenting with models built on a slightly different paradigm is good fun.