Jev is a smart if-statement.
Jev does not chat. It does not write the reply, the plan, or the code. You give it facts and a few typed questions. It gives your program numbers it can branch on.
That is the whole idea.
state + questions → answers
facts Choice / Score / Noul keys and probabilitiesTypeSafe calls this a System One model. One endpoint, three question types, no prose to parse.
The three questions
Use the type that matches the decision.
Choice — pick one option from a list you define.
Team is billing, technical, or other.
Score — place something on an ordered scale.
Frustration is Calm / Annoyed / Angry. A result of 1.4 means between Annoyed and Angry.
Noul — probability that a statement is true, from 0 to 1.
"Does this ask for a refund?" → 0.98.
0.5 means "cannot tell," not "medium."
Wrong type is the usual first mistake. "How urgent?" is a Score. "Is it urgent?" is a Noul.
One call
curl -s https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-latest",
"state": "Charged twice for order A-104. Please refund the extra payment today.",
"questions": {
"refund": {
"type": "noul",
"instructions": "Does the customer ask for money back?"
}
}
}'You get something like:
{
"answers": {
"refund": { "type": "noul", "noul": 0.98 }
}
}Then your code owns the side effect:
if answers["refund"] > 0.9:
start_refund(order)
else:
queue_for_human(order)The first useful app
Ask every independent question in the same request. Extra questions are cheap.
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
ticket = "Stripe payouts have failed for 3 days. We cannot pay contractors."
with TypeSafeClient() as client:
res = client.system_one(
state={"ticket": ticket},
questions={
"team": Choice(
"Which team should handle ticket?",
criteria={
"billing": "Payments, invoices, refunds, payouts",
"technical": "Bugs, outages, integrations",
"other": "Not enough information, or neither",
},
),
"urgent": Noul("Does this need a same-day reply?"),
"frustration": Score(
"How frustrated is the customer?",
criteria=["Calm", "Annoyed", "Angry"],
),
},
)
a = res.answers
if a["team"].confidence > 0.85:
page_oncall(ticket)
else:
assign(ticket, a["team"].choice)Jev did not page anyone. It returned technical, a confidence, and a probability. Policy stays in code.
How to write the input
State is a record, not a prompt. Do not write "you are an expert router." Send the ticket, the plan, the policy excerpt.
Choice options must not overlap. Always keep other.
Noul asks one fact. "Does the message ask us to return a payment?" is good. "Is this important?" is not.
Point at fields with backticks: ticket.
What to build
Start with a decision you already make.
Router — tickets, issues, leads. Choice for the queue, Noul for "needs a human," Score for severity.
Gate — run Jev before an expensive LLM. If there is not enough evidence, skip the generation.
Filter — you retrieved 20 chunks; ask one Noul per chunk and keep the useful ones.
Computer use, later — snapshot one window into e1, e2, e3. Jev picks click/scroll/done. Your code clicks that id. Do not send screenshots. Do not target "whatever is frontmost."
Ship the first three in shadow mode. Keep the old router authoritative, log Jev's proposal beside it, flip the switch when the whole workflow is better.
What it will not do
Write text. Invent options you did not list. Count or do date math. See pixels. Remember the last call. Stay useful if the state is a wall of noise.
If the option set is open-ended, extract candidates in code first, then let Jev pick.
Thirty minutes
Label 20 real tickets by hand. Write one Choice with an other bucket. Call Jev next to your current router. Add a Noul for missing evidence. Keep a human path when confidence is low.
Jev is the judgment that does not need another long model response. Everything else stays in your stack.
Docs: docs.typesafe.ai · longer read: flaviocopes.com/jev