Jev Decides, Claude Writes: Building a Model Router for Claude Code A developer built a model router for Claude Code that uses TypeSafe's Jev, a non-generative "System One" model, to classify each incoming message and route small jobs to a cheaper Claude model instead of the largest configured one. Jev answers typed questions in fixed shapes — choice, score and noul — returning probabilities and a confidence, with calls taking about 300–600 ms round trip at $0.042 per million input tokens and free output tokens; the API is a single POST endpoint to api.typesafe.ai/v1/systemone, where jev-latest currently points to jev-1.14.0. The author demonstrated Jev by scoring a fabricated sales email on lead strength, email kind and whether it needs a personal reply, and notes parts of the router still don't work as intended. Jev Decides, Claude Writes: Building a Model Router for Claude Code Most of what I type into Claude Code is small. I ask it to rename a variable, look something up, or add "yes, but shorter". Every one of those messages still goes to the biggest model I have configured. That's like hiring a senior architect to change a light bulb. This post covers how I wired TypeSafe's Jev https://typesafe.ai/?ref=corti.com model into Claude Code, and how I used it to build a model router . Jev sizes up each message and sends small jobs to a smaller, cheaper Claude model. I cover what the router does, why I built it this way, and the parts that still don't work the way I'd like. What Jev is and isn't Jev is TypeSafe's first "System One" model. The name comes from the fast, intuitive system in Kahneman's two-system model of thinking. Jev doesn't generate text. You give it some state text, or JSON describing it and a set of typed questions . It answers each question in one of a few fixed shapes: | Type | What it answers | What comes back | |---|---|---| | choice | Pick one option from a list | the top option, a probability per option, and a confidence | | score | Place the content on an ordered scale | a weighted position, a probability per level, and a confidence | | noul | How likely is this statement true? | one probability, p yes | There's also box , which finds things in images, but I didn't need it here. Why it fits this job: - Fast. My calls took about 300–600 ms round trip. - Cheap. $0.042 per million input tokens, and output tokens are free. - Calibrated. Answers come with probabilities, so your code can decide what to do when Jev isn't sure. The whole API is one endpoint: POST https://api.typesafe.ai/v1/systemone Authorization: Bearer $TYPESAFE API KEY { "model": "jev-latest", "state": { "message": "..." }, "questions": { "size": { "type": "choice", "instructions": "...", "criteria": { "option a": "description", "option b": "description" } } } } jev-latest currently points to jev-1.14.0 . First contact: sorting a sales email Before building anything, I wanted to see Jev work on something concrete. I made up a sales email from a Head of Operations. It asked for 300 seats, said the Q4 budget was approved and the decision was due by Nov 30, and asked for pricing and a call. I asked Jev three questions in one request: "questions": { "lead strength": { "type": "score", "instructions": "How strong a sales lead is the sender of email ?", "criteria": "No buying interest: the sender is not looking to buy anything", "Curious: the sender asks general questions with no stated need, budget, or timeline", "Interested: the sender describes a real need but no budget or timeline", "Qualified: the sender has a stated need plus either budget or a timeline", "Hot: the sender has a stated need, approved budget, a decision timeline, and asks for next steps" }, "email kind": { "type": "choice", "instructions": "What kind of email is email ?", "criteria": { "purchase inquiry": "A prospective customer asking about buying, pricing, or a demo", "support request": "An existing customer asking for help with the product", "partnership pitch": "Another company proposing a partnership or reselling deal", "vendor solicitation": "Someone trying to sell us their own product or service", "spam": "Unsolicited bulk or irrelevant mail" } }, "needs personal reply": { "type": "noul", "instructions": "Does email ask for something that only a person on our team can answer, such as custom pricing or scheduling a call?", "criteria": { "true": "The sender asks for custom pricing, a meeting, or another answer a template cannot give", "false": "A standard automated reply or a link to existing material fully answers the email" } } } The answers: | Question | Answer | How sure | |---|---|---| | Lead strength | 4 Hot | confidence 1.0 | | Email kind | purchase inquiry | confidence 1.0 | | Needs personal reply | yes | p = 0.97–0.98 | The request used 758 input tokens, about $0.00003 , and took 295–528 ms. Two things stood out: The questions don't depend on each other. Each one is answered on its own, against the same state, and you pay for the state once. Putting several questions in one request is the cheap default. Jev returns judgments. My code turns them into decisions. "Hot lead + needs a personal reply → answer today" is a rule I wrote myself, not something I asked Jev. That second point became the rule for everything after: Jev decides, Claude writes. Jev classifies, scores and estimates likelihood. Claude writes the replies, summaries and code. When Jev isn't sure, Claude makes the call and says so. I saved this as a reusable Claude Code skill jev-sort , so "use Jev to sort these" now works on any pile of text. The router idea Once you have a classifier that answers in about 400 ms for a fraction of a cent, routing is the obvious next step: For every message I send, ask Jev: what's the smallest model that can do this job well? Send the job to that model. There are four sizes, matched to the four Claude models I have, smallest to biggest: | Size | Typical job | Helper | Model | Input $/MTok | |---|---|---|---|---| | tiny | a lookup, a rename, a one-line answer | jev-tiny | Claude Haiku 5.5 | $0.10 | | everyday | a normal email, post, or short document | jev-everyday | Claude Sonnet 5.5 | $2 | | large | a multi-step build, research, a full report | jev-large | Claude Opus 5.5 | $4 | | hardest | strategy, or anything where a wrong call is expensive | jev-hardest | Claude Fable 5.1 | $10 | The Haiku-to-Fable gap is 100× on input price. If even a fraction of daily messages are tiny, that's real money. Claude Code can't switch models per message Claude Code can't change the main session's model for each message. Whatever model the session runs on reads every prompt you send. What Claude Code does have is: Hooks : shell commands that run on lifecycle events. UserPromptSubmit fires before each message reaches the model. It can add context to the message, or block the message. - Subagents : agent definitions in ~/.claude/agents/ .md . Each one can pin its own model: . So the closest working design is: the hook sizes the message, and the main model hands the job to a helper pinned to the right model. you type a message │ ▼ ┌──────────────────────────────┐ │ UserPromptSubmit hook │ router OFF? → exit, no output │ ~/.claude/jev-router/ │ "jev router on/off/status"? → answer, block │ router.py hook │ secret-looking? short reply? → no output └──────────────┬───────────────┘ │ POST /v1/systemone 1.5 s timeout ▼ ┌───────────────┐ │ Jev: size? │ → tiny / everyday / large / hardest + probability └───────┬───────┘ │ p ≥ 0.60 → additionalContext: "delegate to jev-