Every hand-rolled classifier ends the same way
You're building a support inbox. Tickets come in, and each one needs to reach the right team.
Your first attempt looks like this:
if "refund" in text.lower() or "money back" in text.lower():
route_to_billing()This works for about a week. Then someone writes "I'd like my charge reversed." Your keyword list misses it, so you add another or. Then someone writes "the subscription charged me twice." Another or. Six months later you have this:
BILLING_WORDS = ["refund", "money back", "charge", "charged", "billing",
"invoice", "payment", "subscription", "card", "receipt",
"overcharge", "double charge", "reverse", "credit"]
if any(w in text.lower() for w in BILLING_WORDS):
route_to_billing()And it is still wrong, because "my card won't save in settings" is a technical bug, not a billing issue, and your list just grabbed it.
So you reach for AI
Reasonable. You write a prompt, send the ticket to a language model, and get back:
"Based on the content of this message, it appears the customer is experiencing a payment-related issue, so I would recommend routing this to the billing team."
Now you have four new problems.
You have to dig the answer out of a paragraph. You wanted the word billing. You got a sentence about it. So you write parsing code:
if "billing" in response.lower():
route_to_billing()
elif "technical" in response.lower():
route_to_technical()Which breaks the first time the model says "this is not a billing issue" and your substring check routes it wrong anyway. So you get smarter and ask for JSON — and now you're writing prompts that beg: "Respond ONLY with valid JSON. Do not include markdown code fences. Do not explain." You still wrap it in a try/except, because sometimes it adds the fences anyway.
It's slow. A second or two per ticket. Fine when a human is watching a cursor blink. Not fine across 50,000 tickets overnight, and definitely not fine inside a page load.
It's expensive at volume. You pay for every word it writes, including "Based on the content of this message." Output tokens typically cost several times more than input tokens, so you're paying a premium for prose you immediately throw away.
And the worst one: it never tells you when it's unsure.
That last one is the real killer, and it's easy to miss. The model says "billing" with the same breezy confidence whether the ticket is obvious or genuinely ambiguous. From the outside, those two answers look identical.
So if your model is right 95% of the time, you have no way to know which answers land in the other 5%. Your options are to check everything by hand — which defeats the point — or to accept silent errors you'll hear about from an angry customer.
A model that can't say "I don't know" can't be automated around. That constraint does more damage than any accuracy gap.
What Jev does instead
TypeSafe AI, a San Francisco lab founded in 2024, shipped a model built for exactly this case. It's called Jev, it launched on September 15, 2026, and it doesn't write anything at all. No sentences, no explanations, no code. It returns values.
You hand it two things:
- State — the text you want it to look at. A ticket, a document, a database row, a chat message, anything.
- Questions — what you want to know, with the possible answers spelled out in advance.
It hands back the answers as values. Not a sentence containing the value. The value itself.
from typesafe_sdk import TypeSafeClient, Choice
client = TypeSafeClient()
response = client.system_one(
state=ticket_text,
questions={
"department": Choice(
instructions="Which team should handle this",
criteria={
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions",
},
),
},
)
response.answers["department"].choice # "billing"That's it. No parsing, no prompt engineering, no try/except around a JSON decoder.
You defined three possible answers, so one of those three comes back — not a fourth one it invented, not a refusal, not a markdown code fence. The shape of the output is fixed before the model ever runs. TypeSafe's phrase for this is "frontier intelligence as a function call": unstructured stuff in, typed values out.
It's still a real AI model
Worth being clear, because the API shape invites confusion: Jev is a genuine AI model running on TypeSafe's servers. It isn't a library, a rules engine, or something running on your laptop. You call it over the internet like any other API. It just behaves like a function when you use it.
Why "System One"?
TypeSafe borrowed the name from Daniel Kahneman's Thinking, Fast and Slow. System 1 thinking is fast and intuitive — recognising a friend's face, catching a ball, sensing that an email is angry. System 2 is slow and deliberate — long division, planning a trip, writing an essay.
Regular language models are System 2 machines. They reason step by step, in words.
Jev is built for System 1: the judgment a knowledgeable person makes in two seconds without explaining themselves. That turns out to be most of what software actually needs.
The three question types
There are only three. That's part of the appeal.
Choice — pick one from a list
Use when there's a fixed set of options and you need exactly one.
from typesafe_sdk import Choice
Choice(
instructions="What type of document is this",
criteria={
"invoice": "A bill requesting payment",
"receipt": "Proof that payment was made",
"contract": "A legal agreement between parties",
"other": "None of the above",
},
)What comes back:
{
"type": "choice",
"choice": "invoice",
"probabilities": {
"invoice": 0.91,
"receipt": 0.07,
"contract": 0.01,
"other": 0.01
},
"confidence": 0.86
}Three things to notice. You get the pick (choice). You get how likely each option was (probabilities). And you get a single confidence number, which we'll come back to, because it's the most important part of this whole post.
Always include an escape hatch like "other" or "unclear". Otherwise you're forcing a pick among options that might all be wrong.
Score — rate against levels you describe
Use when the answer sits on a scale.
from typesafe_sdk import Score
Score(
instructions="How frustrated the customer appears",
criteria=[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
],
)What comes back:
{
"type": "score",
"score": 1.035,
"legend": {
"0": "Calm, just stating facts",
"1": "Frustrated but civil",
"2": "Very angry, strong language"
},
"confidence": 0.842
}Note that 1.035 is not an integer. The score interpolates between your levels, so you get finer granularity than the three buckets you defined. A 1.9 is a lot angrier than a 1.1, even though both round to "frustrated but civil."
Describe your levels concretely. "Calm, just stating facts" works far better than "low" — the descriptions are how the model knows what you mean.
Noul — is this statement true?
Use for yes/no. Note that you phrase it as a statement, not a question.
from typesafe_sdk import Noul
Noul(instructions="The message conveys urgency or time-sensitivity")What comes back:
{
"type": "noul",
"noul": 0.999
}A number from 0 to 1: the probability the statement is true. 0.999 means near-certain yes. 0.5 means genuine coin flip. 0.02 means near-certain no.
Nouls don't carry a separate confidence value — the number is the answer and the uncertainty at once. A 0.5 is itself the "I don't know."
Ask them all at once
Here's the part that changes how you design things. You can mix all three types in a single call, and every question is evaluated independently and in parallel.
response = client.system_one(
state=ticket_text,
questions={
"department": Choice(
instructions="Which team should handle this",
criteria={
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions",
},
),
"frustration": Score(
instructions="How frustrated the customer appears",
criteria=[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
],
),
"is_urgent": Noul(
instructions="The message conveys urgency or time-sensitivity",
),
"is_existing_customer": Noul(
instructions="The sender is already a paying customer",
),
"mentions_competitor": Noul(
instructions="The message names a competing product",
),
},
)
dept = response.answers["department"].choice # "billing"
anger = response.answers["frustration"].score # 1.035
urgent = response.answers["is_urgent"].noul # 0.999Adding questions barely changes the response time. They aren't asked in sequence — they're all answered in one pass against the same state.
And because each question is evaluated in isolation, they don't interfere with each other. With a normal language model, asking fifteen things in one prompt makes every answer a bit worse as the context gets cluttered. That doesn't happen here.
This leads to a pattern TypeSafe calls speculative fan-out: ask everything you might need in one call, and let your code decide afterward what was relevant. Questions are cheap. Round trips aren't.
Confidence is the part that actually matters
Speed and price get the headlines. TypeSafe claims 70 to 500 milliseconds per call, $0.042 per million input tokens, and free output tokens. Those numbers are good.
But the genuinely useful thing is that Jev tells you when it isn't sure.
Where the number comes from
Every Choice and Score answer includes probabilities — how likely each option was. The shape of that spread is the signal.
A confident answer:
{"billing": 0.94, "technical": 0.05, "sales": 0.01} # confidence ~ 0.89An unsure one:
{"billing": 0.38, "technical": 0.34, "sales": 0.28} # confidence ~ 0.11Both return "billing" as the top pick. Only one of them means it.
The confidence field collapses that spread into a single 0-to-1 number so you can threshold on it without doing the math yourself. The full probabilities are still there if you want to compute something else.
TypeSafe trained the model specifically for this, using a method they call RLCD — Reinforcement Learning for Calibrated Decisions. The goal is calibration: when it says 90%, it should be right about 90% of the time. That's a different objective from how chat models are trained, which optimises for answers humans like, and tends to produce confident-sounding text regardless of whether the model actually knows.
The three-way branch
Here's what it unlocks. Your code gets an option it never had:
answer = response.answers["department"]
if answer.confidence < 0.5:
send_to_human_queue(ticket) # the model is telling you it doesn't know
elif answer.choice == "billing":
route_to_billing(ticket)
elif answer.choice == "technical":
route_to_technical(ticket)
else:
route_to_sales(ticket)Think about what changed. A model that's right 95% of the time but silent about its misses is barely automatable. A model that's right 95% of the time and flags the shaky ones is something you can build a business process on. You automate the confident cases and route the rest to a person.
That's the whole pitch, and it's a better pitch than the speed.
Scale your threshold to the stakes
One threshold for the entire app is a mistake. The bar should depend on what happens when you're wrong.
action = response.answers["action"]
if action.confidence < 0.5:
# Genuinely unsure. Don't guess at all.
ask_user_to_clarify()
elif action.choice == "check_balance":
# Low stakes. Wrong screen is recoverable with a back button.
show_balance(account_id)
elif action.choice == "approve_transfer":
if action.confidence > 0.9:
confirm_then_execute(account_id)
else:
# High stakes, moderate confidence. Make the human decide.
ask_user_to_confirm(account_id)Read-only actions get a loose threshold. Destructive or irreversible ones get a tight threshold plus an explicit confirmation. Your code ends up encoding your risk tolerance explicitly, in a place you can review.
Start conservative. Log confidence values alongside outcomes for a few weeks, look at where errors actually cluster, then loosen. Don't guess at thresholds on day one.
Build the human queue first
A practical note that will save you pain: build the low-confidence path before you build the automation. If there's nowhere for uncertain cases to go, you'll be tempted to lower the threshold instead — and then you've rebuilt the exact thing you were trying to escape.
Getting it running
The fastest path: the playground
Before writing any code, open console.typesafe.ai/playground, paste some text as the state, and add a question. Ten minutes there will teach you more about phrasing than any amount of reading.
Try this state:
Hi, I've been trying to connect my Stripe account for 3 days and it keeps
failing. I'm losing sales. Please help ASAP.And this question:
{
"urgency": {
"type": "noul",
"instructions": "Does this message express urgency?"
}
}Then add more questions and watch them all come back together.
Python
Requires Python 3.10 or newer.
pip install typesafe-sdk
# or: uv add typesafe-sdkThe client reads TYPESAFE_API_KEY from your environment and uses jev-latest by default.
from typesafe_sdk import TypeSafeClient, Choice, Score, Noul
client = TypeSafeClient()
response = client.system_one(
state="Hi, I've been trying to connect my Stripe account for 3 days "
"and it keeps failing. I'm losing sales. Please help ASAP.",
questions={
"department": Choice(
instructions="Which team should handle this",
criteria={
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions",
},
),
"frustration": Score(
instructions="How frustrated the customer appears",
criteria=[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
],
),
"is_urgent": Noul(
instructions="The message conveys urgency or time-sensitivity",
),
},
)
print(response.answers["department"].choice) # "technical"
print(response.answers["frustration"].score) # 1.035
print(response.answers["is_urgent"].noul) # 0.999
print(response.usage.input_tokens) # 312There's an async client too — AsyncTypeSafeClient, same interface — which is what you want when fanning out over a large batch.
JavaScript
There's a JS/TypeScript SDK with a TypeSafeClient class and choice(), score(), and noul() helpers, plus properly typed responses. Same concepts, same request shape.
Raw HTTP
If you'd rather not add a dependency:
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Hi, my Stripe connection keeps failing. Losing sales. Help ASAP.",
"model": "jev-latest",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this",
"criteria": {
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions"
}
},
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
}
}
}'The response comes back with model, answers keyed exactly as you asked them, and usage carrying input_tokens and output_tokens.
Let your coding agent handle it
TypeSafe publishes an agent skill so your AI coding assistant knows the API shape:
# Claude Code
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
# Other agents
npx skills add typesafe-ai/skills --skill typesafe-aiThen describe what you want built and let it write the calls.
Where this fits in real applications
This is the section you probably scrolled for. The pattern to look for is always the same:
Anywhere you wrote an ugly if-statement, a regex, or a keyword list and weren't happy about it. And anywhere a person currently eyeballs things one at a time.
E-commerce and marketplaces
Listing moderation on upload. Does the description match the category? Does it contain contact details meant to take the deal off-platform? Is the price implausible?
questions={
"category_match": Noul(
instructions="The item description matches the seller's chosen category"),
"offsite_contact": Noul(
instructions="The listing contains a phone number, email, or "
"instruction to contact the seller outside the platform"),
"price_plausible": Noul(
instructions="The price is plausible for this type of item"),
"quality": Score(
instructions="Listing quality",
criteria=["Sparse or unusable", "Adequate", "Detailed and well-written"]),
}Review analysis at scale. You have 50,000 reviews and want to know what people actually complain about. One Choice per review across price / shipping / quality / customer_service / other, one Score for severity, and you have a dashboard.
Return triage. Auto-approve the clear cases, escalate the ambiguous ones, flag likely fraud.
Search intent. Is "apple" the fruit or the brand? Is this query a product name, a question, or a misspelling?
SaaS and B2B products
Ticket routing — the running example in this post.
Churn signals in support conversations. Score frustration on every ticket and alert an account manager when a high-value customer crosses a line.
Feature request extraction. Sift every ticket for "is this a feature request?" and "which product area?" Suddenly your roadmap has data behind it.
Onboarding help. Which of your twelve help articles matches what this user just typed? Low confidence means show search instead of a wrong answer.
Inbound lead qualification. Real prospect, vendor spam, or job applicant? Company size signals? Budget mentioned?
Social and community apps
Moderation with an "unsure" bucket. This is the flagship use case, because the three-way branch maps perfectly onto what moderation actually needs.
response = client.system_one(
state=comment_text,
questions={
"verdict": Choice(
instructions="How should this comment be handled",
criteria={
"fine": "Normal participation, no issues",
"borderline": "Rude or heated but not rule-breaking",
"violating": "Harassment, hate speech, or threats",
"spam": "Promotional content or link spam",
},
),
"severity": Score(
instructions="How much harm would leaving this up cause",
criteria=["None", "Mild", "Serious"],
),
},
)
v = response.answers["verdict"]
if v.confidence < 0.6:
queue_for_moderator(comment) # don't auto-delete on a coin flip
elif v.choice == "violating" and response.answers["severity"].score > 1.5:
remove_and_notify(comment)
elif v.choice == "spam":
shadow_hide(comment)
else:
publish(comment)The old version of this was a banned-word list that punished people discussing their own experiences while missing polite cruelty entirely.
Profile checks. Bio contains contact info, impersonation attempt, obvious bot.
Feed relevance. Does this post actually belong in this community?
Fintech and anything with money
Real-time fraud signals at checkout. 150ms is fast enough to sit inline. Score the transaction narrative, the shipping/billing mismatch story, the account-age pattern.
Transaction categorisation. Which budget bucket does this merchant name belong in? Millions of rows, essentially free.
Document classification in onboarding. Is this upload a passport, a utility bill, a bank statement, or a blurry photo of a desk?
Destructive action gating. The approve_transfer example above. Money movement should require both a confident answer and a human confirmation.
Healthcare, legal, and regulated domains
Use it for triage and routing, not for decisions. Jev can classify an intake form's urgency, route a document to the right specialist, or flag a contract clause as unusual. It should not be the thing that decides anything consequential on its own. Set the confidence threshold high and make the human queue the default rather than the exception.
Intake urgency triage. Route to the right queue, with anything below high confidence going to a clinician or paralegal.
Contract clause flagging. Does this agreement contain an auto-renewal clause? An unusual indemnity? A non-standard governing law? One Noul per clause type, one pass over the document.
Records classification. Which of 40 document types is this scan?
Content, CMS, and publishing
Pre-publish checks on AI-generated drafts. On brand? Contains a claim that needs a source? Reading level appropriate for the audience?
Auto-tagging an archive. Twenty years of articles and no consistent tags. One Choice per article over your taxonomy, plus Nouls for the cross-cutting attributes.
Structure recovery. Text that lost its formatting — classify every block as heading, list, code, or callout and rebuild the markdown.
Recruiting and HR
Résumé screening against explicit criteria. Not "is this person good," which is both unreliable and a legal problem. Instead: "the résumé mentions experience with Kubernetes," "the résumé indicates five or more years in a senior engineering role." Verifiable, auditable facts, each one a separate Noul.
Application routing. Which open role is this general application closest to?
Employee survey analysis. Thousands of free-text responses, categorised and severity-scored in one batch.
Be careful here. Anything touching hiring decisions carries legal exposure and bias risk. Use it to surface and organise, with a human making every call that affects someone's employment.
Developer tools and internal platforms
Duplicate bug detection. For each existing open issue, a Noul: "this new report describes the same underlying problem." Sort by probability.
Alert severity. Does this page someone at 3am or wait until morning? Score the alert text against your escalation levels.
PR triage. Which team owns this change? Does the description match the diff? Is this a risky change to a critical path?
Log clustering. What kind of failure is this, out of your known categories?
Education
Short-answer grading assistance. Score against a rubric you describe in levels, with low confidence routed to the teacher. Never auto-assign a grade on a low-confidence answer.
Content difficulty. Is this reading passage appropriate for a fourth-grader?
Question intent. Is the student confused about the concept or the notation? Different follow-ups.
Using Jev alongside your chat model
Jev doesn't compete with general-purpose chat models. It works alongside them, and the combination is where a lot of the value sits.
Before the model: send it less, better
If you have a chat interface over your database, you might be stuffing 60 table schemas into every prompt. Score each table's relevance to the question, send the top five, and the generated SQL gets better because the model isn't distracted.
# Score all 60 tables in one call
response = client.system_one(
state=f"User question: {question}\n\nTables:\n{table_summaries}",
questions={
f"relevance_{t}": Score(
instructions=f"How relevant is the '{t}' table to answering this question",
criteria=["Irrelevant", "Possibly relevant", "Clearly needed"],
)
for t in table_names
},
)
relevant = [t for t in table_names
if response.answers[f"relevance_{t}"].score > 1.2]Before the model: route to the right one
Not every request needs your most expensive model.
intent = client.system_one(
state=user_message,
questions={
"kind": Choice(
instructions="What kind of request is this",
criteria={
"lookup": "A factual question answerable from our database",
"chitchat": "Greeting or small talk",
"complex": "Needs multi-step reasoning or explanation",
"offtopic": "Unrelated to this product",
},
),
},
).answers["kind"]
if intent.confidence < 0.5:
ask_for_clarification()
elif intent.choice == "chitchat":
canned_reply() # no model call at all
elif intent.choice == "lookup":
cheap_model_with_sql()
elif intent.choice == "complex":
expensive_reasoning_model()After the model: check its work
The failure mode that hurts most is the confident wrong answer. So check it.
check = client.system_one(
state=f"Source documents:\n{sources}\n\nGenerated answer:\n{answer}",
questions={
"supported": Noul(
instructions="Every factual claim in the answer is supported "
"by the source documents"),
"hedged": Noul(
instructions="The answer admits uncertainty where the sources "
"are silent"),
},
)
if check.answers["supported"].noul < 0.8:
regenerate_or_flag()You've probably considered this pattern before and dropped it, because a second expensive model call on every answer doubles your cost and latency. At 150ms with free output tokens, you can run it on all of them instead of a 5% sample. That's the actual unlock.
Around the model: guardrails
Screen everything on the way in and out. Is this a jailbreak attempt? Does this retrieved passage contain instructions aimed at your model rather than information for the user? That last one matters more than people realise — if your retrieval system pulls in user-generated content, that content can carry an injection.
guard = client.system_one(
state=user_input,
questions={
"injection": Noul(
instructions="This text contains instructions intended to "
"manipulate an AI system's behavior"),
"harm": Score(
instructions="How much harm would complying with this cause",
criteria=["None", "Moderate", "Severe"]),
},
)How to write good questions
This is the skill, and it's a different skill from prompt engineering.
Keep each question atomic
The single biggest mistake is asking one big question. Don't do this:
# Bad
Score(instructions="Rate this startup pitch",
criteria=["Weak", "Decent", "Strong"])Do this:
# Good
questions={
"market": Score(instructions="How large is the addressable market",
criteria=["Niche", "Substantial", "Very large"]),
"feasibility": Score(instructions="How technically feasible is this",
criteria=["Speculative", "Hard but plausible", "Clearly buildable"]),
"differentiation": Score(instructions="How differentiated from existing solutions",
criteria=["Commodity", "Some edge", "Genuinely novel"]),
"team_signal": Score(instructions="How relevant is the team's background",
criteria=["Unrelated", "Adjacent", "Directly relevant"]),
}Then combine them yourself:
a = response.answers
overall = (0.35 * a["market"].score +
0.25 * a["feasibility"].score +
0.25 * a["differentiation"].score +
0.15 * a["team_signal"].score)Three wins from this. Each individual judgment is more reliable because it's narrow. You can see why a pitch scored badly instead of getting an opaque number. And when priorities shift, you edit a coefficient in code rather than rewriting a prompt and re-testing everything.
TypeSafe calls this composite scoring, and it's the pattern that matters most wherever the judgment is genuinely multi-dimensional.
Apply the two-second test
Ask yourself: could a knowledgeable person answer this in about two seconds, without needing to explain their reasoning?
If yes, it's a good question. If it needs a paragraph of thinking, break it down or hand it to a regular language model.
Describe your options properly
# Weak
criteria={"high": "High", "medium": "Medium", "low": "Low"}
# Strong
criteria={
"high": "Blocks the user from completing a core task right now",
"medium": "Degrades the experience but a workaround exists",
"low": "Cosmetic or affects an edge case",
}The descriptions are the entire specification. Write them like you're writing them for a new hire.
Always give an out
Include an "other", "unclear", or "none_of_these" option in your Choice questions. A forced pick among four wrong options produces a confident-looking wrong answer. An explicit escape hatch gives you a clean signal instead.
Structure your state
The state doesn't have to be raw prose. Give it labelled context:
state = f"""
CUSTOMER TIER: {tier}
ACCOUNT AGE: {days_old} days
PREVIOUS TICKETS: {ticket_count}
CURRENT MESSAGE:
{message}
"""The model is designed around structured program state, not conversational back-and-forth. Feed it accordingly.
What Jev can't do
Be clear-eyed about this or you'll be disappointed.
It cannot write. No translations, no summaries, no code, no explanations of its own answers. If you need a paragraph, you need a regular language model. Jev gave up string generation on purpose — that's where the speed comes from.
It cannot reason through something complicated in one shot. Decompose and combine in code. That's the whole methodology.
It cannot give you an option you didn't define. Usually a feature. Occasionally a limitation, if you genuinely don't know the answer space in advance.
Choices are capped. TypeSafe puts the ceiling at 255 options. For higher-cardinality problems you go two-stage: score candidates, then choose among the top few.
One honest correction to something you'll see repeated. TypeSafe says Jev can't hallucinate. What's actually guaranteed is that it can't return something outside the options you defined — it will never invent a fourth department, and it will never produce a type error. That's a real and valuable guarantee. But it can still pick the wrong one of your three options. "Can't hallucinate" is narrower than "can't be wrong," and the difference matters when you're designing around it.
Reasons to stay skeptical
Some things worth knowing before you build a company on this.
- It's early access with a waitlist, not general availability.
- The headline multipliers come from TypeSafe's own benchmarks. 20-200x faster, 40-400x cheaper. They built the evaluation workflows themselves and acknowledge in their own launch post that the numbers sit on the higher end of what you'd see in the real world. They publish the details, which is more than most companies do. It's still their homework.
- They can't prove the pricing isn't subsidised, and they say so directly. They expect it to come down rather than go up, but that's a forecast, not a guarantee.
- The comparison baseline biases toward big-lab models, by their own admission, since they use averaged answers from the largest frontier models as the reference.
- No public customer list yet. This is a company that came out of two years of stealth with roughly $40M in seed funding and a launch day. Treat it accordingly: prototype, measure on your own data, and don't rip out working infrastructure on the strength of a blog post. Including this one.
How to actually start
Don't start from scratch, and don't start with something important.
- Find your worst heuristic. The regex you're embarrassed by. The keyword list that keeps growing. The
ifstatement with a comment above it that says# TODO: this is wrong but works most of the time. - Write it as three or four atomic questions. Not one big one.
- Run both in parallel for two weeks. Keep the old logic in charge. Log what Jev would have done, along with its confidence, next to what actually happened.
- Look at the disagreements. This is the valuable part. Where they disagree, who was right? Where confidence was low, was the case actually ambiguous?
- Set your thresholds from that data, not from a guess.
- Build the human queue before you flip the switch. If uncertain cases have nowhere to go, you'll lower the threshold instead, and you'll have rebuilt exactly the silent-error problem you were escaping.
The bet behind Jev
For three years, "adding AI" has meant adding something that talks. Every integration became a prompt, a parser, and a prayer.
Jev is a bet that most of the intelligence software needs isn't conversation at all. It's a thousand small judgment calls — which bucket, how bad, is this true — each one fast enough to sit inside a request, cheap enough to run on everything instead of a sample, and honest enough about its own uncertainty that your code knows when to ask a human.
Whether the bet pays off is unproven. The company is new, the benchmarks are self-reported, and the model is in early access.
But the question behind it is a good one, and it will outlast this particular product: how much more reliable could our software get if we stopped requiring every intelligent piece of it to talk?
Frequently asked questions
What is a System One model?
A System One model answers structured questions in parallel and returns typed values — a choice, a score, or a probability — instead of generating text token by token. The name comes from Kahneman's System 1 thinking: fast, intuitive judgment rather than slow, deliberate reasoning. TypeSafe's Jev is the first model released under that label.
How is Jev different from JSON mode or structured outputs on a normal LLM?
JSON mode still generates text; it just constrains the grammar of that text, so you keep the latency and output-token cost of writing, and you get no calibrated uncertainty signal. Jev doesn't generate strings at all. The output shape is fixed before the model runs, every question is evaluated independently and in parallel, and each answer carries a probability distribution and a confidence score you can threshold on.
What does Jev cost?
TypeSafe prices Jev at $0.042 per million input tokens, with output tokens free. They claim 40-400x cheaper and 20-200x faster than routing the same work through a general-purpose LLM, with response times of 70-500ms. Those multipliers come from TypeSafe's own benchmarks, and the company acknowledges they sit at the higher end of real-world results.
Can Jev write text, code, or summaries?
No. Jev returns values only — no sentences, no code, no explanations of its own answers. That's a deliberate design choice and the source of its speed. For anything that needs prose, you still use a general-purpose language model, often alongside Jev rather than instead of it.
Is it true that Jev can't hallucinate?
Only in a narrow sense. Jev cannot return a value outside the options you defined, and it cannot produce a type error — that part is guaranteed by construction. But it can still pick the wrong one of the options you gave it. "Can't hallucinate" is narrower than "can't be wrong," and the confidence score exists precisely because the second kind of error is still possible.
How should I choose a confidence threshold?
Scale it to the stakes, and set it from data rather than intuition. Run Jev in shadow mode alongside your existing logic for a couple of weeks, log confidence next to actual outcomes, then pick thresholds from where the errors actually cluster. Read-only or easily reversible actions can take a loose threshold; destructive or irreversible ones should require high confidence plus an explicit human confirmation.
How many options can a Choice question have?
TypeSafe caps a Choice at 255 options, which is enough for most taxonomies. For higher-cardinality problems, use a two-stage approach: score or filter candidates first, then run a Choice over the top few.
Sources
- Introducing System One Models and Jevtypesafe.ai
- TypeSafe AI Documentation — Introductiondocs.typesafe.ai
- TypeSafe AI HTTP API Referencedocs.typesafe.ai
- TypeSafe AI debuts model for machines that plays Doomtheregister.com
Book A Call


