Jev Model Router Evaluation
Eval: jev-model-router
Section titled “Eval: jev-model-router”Eval for #2667, part of the LLM cost-tiering work (#1974). It builds on the model-assignment policy (#2680), subagent-aware token accounting (#2685) and the Jev client and egress policy (#2672).
Verdict: no-go for now. BoB does not install the mod on any machine. The reasons are below, together with the conditions for a re-evaluation.
What was reviewed
Section titled “What was reviewed”The mod’s source is davila7/claude-code-templates, at
cli-tool/components/mods/productivity/jev-model-router/ (upstream commit 666c061,
reviewed 2026-09-19). It has three parts:
hooks/jev-model-router.tsregisters the function hooks.hooks/policy.tsholds the routing policy..claude-plugin/plugin.jsondeclares the options.
The mod needs Claude Code 2.1.259 or later and CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1.
The function-hook $ API is early access.
The mod hooks three events:
| Event | What it does |
|---|---|
prompt.submit |
Classifies the user’s prompt: tier, effort and risk. |
turn.step |
Applies the result to the main loop: its effort, and its model if routeMainModel is on. |
agent.spawn |
Classifies the subagent’s prompt, description and type, and sets the subagent’s model. |
Every failure path fails open: the request goes ahead exactly as the engine built it.
The live sandbox run (claude plugin validate, then a headless session with the mod
loaded) was not done in the warp-drive run: executing downloaded third-party code needs a
person’s approval. The findings below come from reading the code. The sandbox run is
tracked as a todo on #2667.
Findings
Section titled “Findings”1. The built-in classifier path cannot save anything
Section titled “1. The built-in classifier path cannot save anything”With no API key, the mod calls the engine’s $.model.classify(prompt, ['fast', 'balanced', 'deep']). The result has a tier and nothing else: confidence, effort
and risky are all null. policy.ts then does two things:
- It never moves effort.
route()skips effort when the effort score isnull, sorouteMainEffortdoes nothing on this path. - It only upgrades a model.
allowed()returns!isDowngradewhen confidence isnull. That is the mod’s own safety rule: spending less on an unmeasured guess is the bad trade.
So on the path that keeps prompts on the machine, the only change the mod can make is to move a subagent to a more expensive model. A trial of this path can measure a cost increase, never a saving.
2. It overrides the declared agent tiers
Section titled “2. It overrides the declared agent tiers”#2680 sets each agent’s model: to follow its work class, and a warp-drive delegation
spawns haiku or sonnet on purpose. agent.spawn measures the change from the
requested model (e.model ?? e.parentModel), so the router second-guesses a declared
choice. On the built-in path a haiku mechanical subagent classified deep goes to
opus, which undoes the lever #2680 measured. The model-assignment policy is declared
and reviewed in version control. A per-prompt classifier that overrides it is a second
source of truth for the same decision.
3. It mis-ranks models it does not know, and that can move a subagent down unmeasured
Section titled “3. It mis-ranks models it does not know, and that can move a subagent down unmeasured”rankOf() finds a model’s tier by substring: the configured tier names, then haiku /
sonnet / opus. Anything else ranks null, and claude-fable-5-1 is one of those.
With the current rank null, allowed() does not treat any move as a downgrade. A
subagent that would inherit a Fable parent can therefore go to haiku or sonnet on an
unmeasured built-in label. The confidence-null guard exists to stop exactly that. The
fix is upstream: an unknown current model should rank as the top tier, or the tier map
should be configurable. Until then, the “only routes up when unmeasured” guarantee
does not hold on a Fable session.
4. The hosted path conflicts with BoB’s egress policy
Section titled “4. The hosted path conflicts with BoB’s egress policy”With a TypeSafe or Gateway key, prompt.submit sends { prompt: e.text }, the whole
user prompt, and agent.spawn sends the subagent prompt and description. The
llm-tiering egress policy (#2672) requires filtering in code to the fields the questions
read, with no secrets ever. A raw prompt is unfiltered: a pasted token, a log line or a
.env fragment would leave the machine. The mod has no filter hook.
Privacy decision: BoB does not enable a hosted backend for this mod. Revisit only if upstream adds a prompt filter that BoB can supply, such as a redaction callback or a length and field allow-list.
5. It defaults to an unpinned model
Section titled “5. It defaults to an unpinned model”typesafeModel defaults to jev-latest. BoB pins jev-1.13.0 because an alias
moves when TypeSafe ships and thresholds do not carry over. A config can override this,
so it does not block adoption, but any adoption must set it.
6. routeMainModel stays off
Section titled “6. routeMainModel stays off”The mod reaches the same conclusion as BoB’s own measurement in
Warp-Drive Model Routing: 67–81 % of per-chunk cost is
cache reads of the accumulated context, and switching the main-loop model mid-session
re-caches all of it. The mod ships with routeMainModel off, and BoB keeps it off.
What BoB already does instead
Section titled “What BoB already does instead”| Decision | Where BoB makes it |
|---|---|
| A subagent’s model | The agent’s model: frontmatter, set from the model-assignment policy (#2680) |
| Delegating a mechanical chunk | _workflow.delegate_mechanical_chunks with the three delegation tests; the main loop is the fail-open backstop |
| A typed classify, route or dedupe decision in a pipeline | scripts/lib/jev.js: pinned version, confidence tiers, filtered state, fail-open (#2672) |
| Main-loop reasoning effort | The warp-drive engine’s per-phase REASONING BUDGET line |
The per-task classifier that the mod adds on top of these would compete with the declared policy rather than fill a gap. The mod’s safety model is worth reusing if BoB builds its own router: asymmetric upgrade and downgrade bars, upgrades only when unmeasured, a risk override and fail-open on every path. The llm-tiering skill already follows the same shape.
A/B measurement
Section titled “A/B measurement”The planned A/B was ten chunks with routeSubagentModel and routeMainEffort on,
against the #2680 baseline. It is not run. On the built-in path, finding 1 settles the
outcome in advance. On the hosted path, finding 4 rules the trial out before any
measurement. If a re-evaluation condition below is met, the A/B runs then, over
subagent-aware token records (#2685).
Re-evaluate when
Section titled “Re-evaluate when”Any one of these reopens the evaluation:
- Upstream adds a prompt filter BoB can supply, so the hosted path can meet the egress policy.
- The mod can respect a requested model: a switch that routes a subagent only when the caller named no model. The declared agent tiers would then stand.
rankOf()treats an unknown model as the top tier, or the tier map becomes configurable, so Fable sessions are safe.
If BoB adopts the mod after a re-evaluation, it is declared: the mod is vendored under
registry/ at a pinned upstream SHA, and the environment flag and pluginConfigs are
set through BoB-managed settings. It is never hand-installed into ~/.claude/settings.json.