The problem
Tool calling through a chat model means asking it to emit a JSON object shaped like your function signature. It usually works. When it doesn’t, you get an argument that isn’t in the enum, a required field missing, or a plausible value the function will reject three layers down. So you write a validator and a repair prompt, and both add latency to every call including the ones that were fine.
Jev removes the generation step. Instead of asking a model to write a call, you ask one typed question per decision inside that call: which function, and what value for each argument whose options are already fixed. The answer to a Choice question is always one of the labels you supplied, so nothing invalid can reach the function. TypeSafe’s function calling cookbook builds exactly this over a trading API, turning “plot rolling correlation between nvda and spy for the past month” into rolling_correlation(symbol='NVDA', benchmark='SPY', window='1mo') at confidence 0.91.
What the state looks like
State is the request being interpreted. For dispatch it’s the user’s command plus whatever context changes which tool applies. Send little else: unrelated conversation history is the kind of large, noisy state that costs Jev accuracy, and here it shows up as arguments filled from the wrong turn.
{
"command": "compare nvda amd and msft over the past three months",
"surface": "chat",
"available_symbols": ["SPY", "NVDA", "AMD", "AAPL", "MSFT", "TSLA"]
}
The questions you ask
One Choice picks the tool. One Choice per closed-set argument fills it. A Noul per optional argument decides whether the user said anything about it at all.
from typesafe_sdk import Choice, Noul, TypeSafeClient
client = TypeSafeClient()
questions = {
"__tool__": Choice(
instructions="What is the user asking the trading assistant to do?",
criteria={
"plot_price": "Draw a price chart for one symbol",
"compare_returns": "Compare the returns of several symbols",
"rolling_correlation": "Measure how one symbol tracks another over time",
"list_symbols": "List the symbols the assistant can work with",
},
),
"plot_price.style": Choice(
instructions="Does the user want a plain line or candles?",
criteria={
"line": "a simple line through the closing prices",
"candles": "a candlestick or OHLC chart, showing each bar's open, high, low and close",
},
),
"plot_price.style?": Noul(
instructions="Does the user say how the chart should be drawn, such as a line, candles, or OHLC bars?",
),
"compare_returns.symbols.NVDA": Noul(
instructions="Does the user want NVDA in the comparison?",
),
}
response = client.system_one(state=command, questions=questions)
A Choice is a pick-one question over labels you define, and the labels here are the literal strings the function accepts, so nothing has to map a friendly name back to an argument afterwards. Up to 255 options fit in one Choice, which covers most enums and symbol lists. Each answer carries a probability for every option and a confidence value from 0 to 1.
The Nouls do two different jobs. A Noul returns a single probability from 0 to 1 that the answer is yes, and one per optional argument asks whether the command mentioned that argument at all. When the answer is no, you leave the argument out and the function’s own default applies. That’s what keeps “is amd tracking nvidia lately” from inventing a time window it was never given. A second use covers set-valued arguments: one Noul per candidate member, because several can be true at once and a Choice would force a single winner. The primitives guide covers where each shape fits.
Every question rides in one request. TypeSafe’s fan-out pattern notes there’s no speed cost for additional questions, so asking every tool’s arguments up front and reading only the chosen tool’s answers is cheaper than a second round trip. Arguments the model never gets asked about, like a free-text note or an integer limit, simply keep their defaults.
Decision policy
Jev answers the questions. Assembling the call and running it stays in your code, which is also where the confirmation step lives.
tool = response.answers["__tool__"]
if tool.confidence < 0.6:
return ask_user_to_rephrase()
arguments = {}
for name in closed_set_arguments(tool.choice):
stated = response.answers.get(f"{tool.choice}.{name}?")
if stated is None or stated.noul > 0.5:
arguments[name] = response.answers[f"{tool.choice}.{name}"].choice
weakest = min(a.confidence for a in chosen_answers(tool.choice, response))
if weakest < 0.7 and tool.choice in DESTRUCTIVE_TOOLS:
return confirm_with_user(tool.choice, arguments)
return TOOLS[tool.choice](**arguments)
The cookbook reports a call’s confidence as the least certain judgment behind it, rather than the product of all of them, because one wrong argument spoils the result and a product would fall simply because a function takes many arguments. Gate destructive tools higher than read-only ones. That split is the whole subject of confidence-gated actions, and the thresholds belong to your domain rather than to the model.
Write each argument question about the idea, not the parameter name. “Which resolution?” gives a command nothing to match against, while a question describing what the resolutions mean does. Two public projects build on this shape: typesafe-computer-use drives a computer-use loop, and TypeSafe’s official agent skill packages the pattern for coding agents.
When not to use this
Arguments with open values don’t belong here. Jev isn’t trained to generate text, so a free-form note, a numeric limit or a filename should come from a parser, a regular expression or a generative model, with Jev picking among candidates if a pick is needed. Dates are the common trap: extract the parts as Choices over closed sets and assemble them in code, because Jev reads dates as text rather than as ordered quantities.
Deep indirection also costs accuracy. A question like “which tool would the user have wanted if the first one fails” is multi-hop reasoning; ask one direct question per decision instead. And since the command is user-supplied text, treat it as untrusted: Jev has no default protection against injected instructions, so a command that argues for its own dispatch can shift the answer. Keep dangerous tools behind a confirmation step rather than behind the classifier alone.
FAQ
How does this differ from an LLM’s structured output mode?
Structured output constrains the shape of generated text and still generates it. Here nothing is generated: each argument is selected from the values you listed, so the result is a value the function accepts by construction. You also get a probability distribution and a confidence per argument, which tells you which part of the call to doubt.
What happens to arguments that aren’t closed sets?
They get no question, and the function’s default stands. The cookbook’s top_movers has three arguments, two closed sets and an integer limit, and the limit keeps its default of 3. If you need a number from the command, extract it with a parser and pass it through, or let a generative model propose candidates for Jev to choose between.
Can one call cover every tool’s arguments at once?
Yes, and the cookbook does: 54 questions per command across ten functions. Your code reads only the chosen tool’s answers and discards the rest. That’s the speculative fan-out pattern applied to dispatch, and it saves the round trip you’d otherwise spend asking for arguments after the tool is known.
How do I know which argument to blame when a call comes out wrong?
Look at the per-argument confidence and the probability spread. The cookbook surfaces the weakest argument by name, which points straight at the question that needs rewording. In its worked example the benchmark argument reads 0.78 against the symbol’s 0.87, so the roles in that question were the thing to clarify first.
Examples in the wild
Jev Ultrafast (Browser Use)
Browser Use's fast browser agent where Jev picks the operation and the DOM element in a single request; a small LLM is only invoked to write text when typing is required. Demoed on a Google Flights search.
typesafe-ai/skills (official TypeSafe agent skill)
Official agent skill for Claude Code, Codex and other agent environments; installs via claude plugin marketplace add typesafe-ai/skills or npx skills add typesafe-ai/skills. MIT licensed.
jkudish/jev-mcp
Proof-of-concept MCP server for Jev that lets Claude Code, Claude Desktop and Codex call the model and receive probabilities they can branch on. The most-starred Jev MCP server found.
typesafe-computer-use
macOS computer-use loop that OCRs the screen, classifies the next action with Jev, then clicks. Author reports roughly $0.0002 per step.
jev-eval-agent
Personal-assistant agent built with Vercel's eve framework and 100 mocked tools, served through OpenRouter. Measures how many steps the agent needs when the LLM picks the tool itself versus when Jev picks it via a 101-option Choice.
mobile-jev
Standalone Android agent for Mobilerun where Jev makes every action decision, with a React studio UI and an Uber booking demo.
Jev Browser (vlad-terin)
Agent skill and runtime that uses Jev to select page elements through an agent's existing computer-use browser tools. The agent plans once, then Jev picks elements inside a continuous observe-act-verify loop without an agent turn between steps.
y0usaf/pi-jev
Extension making Jev the decision layer for the Pi coding agent: a measured tool-call gate plus a jev_ask tool. Judges whether an action is destructive, exfiltrates data or exceeds scope, then blocks or warns on calibrated thresholds.
itsmostafa/typesafe-mcp
MCP server for TypeSafe shipped as a single static Go binary with no Node or Python runtime. Sends usage guidance to the client so the agent writes better questions.
jkudish/jev-browser
Browser-use tool driving a real headless browser through an MCP server, CLI or library, with Jev choosing the actions. Returns text, markdown, HTML or accessibility-tree output plus traces and screenshots.
blakestone-x/jev-mcp
Python MCP server exposing Jev as classify, score, check, match and screen tools, with a confidence value on every answer. Ships registration steps for Claude Code, Codex and Cursor that keep the API key out of tool arguments.
Ying-Kai-Liao/jev-browser
Browser automation where an LLM plans and Jev decides each tactical action, executed through Playwright without feeding page snapshots to the LLM. README reports 40/42 tasks passing and roughly 5x less token context.