diff --git a/services/hermes/plugins/auto-router/__init__.py b/services/hermes/plugins/auto-router/__init__.py index ca5479cbb..4bdbc4608 100644 --- a/services/hermes/plugins/auto-router/__init__.py +++ b/services/hermes/plugins/auto-router/__init__.py @@ -377,6 +377,11 @@ def classify_task( # safety floor: it can prevent a downgrade or preserve an explicit work # shape/provider, but it does not bypass the local classifier. effort = max((baseline.effort, local.effort), key=EFFORT_RANK.__getitem__) + # Small local models sometimes wobble between low and medium for the same + # short prompt. Keep an otherwise trivial task on the low route unless the + # Jetson sees a strong enough signal to raise it to high or xhigh. + if baseline.effort == "low" and local.effort == "medium": + effort = "low" shape = baseline.shape provider = ( baseline.provider @@ -388,7 +393,7 @@ def classify_task( effort, provider, "jetson-context" if used_context else "jetson", - "Jetson task/provider/effort classification with deterministic safety floors" + "Jetson task/provider/effort classification with deterministic safety and cost bounds" + (" and recent assistant context" if used_context else ""), local.latency_ms, ) diff --git a/testing/tests/test_hermes_auto_router.py b/testing/tests/test_hermes_auto_router.py index 6954b8ecd..ade3daef0 100644 --- a/testing/tests/test_hermes_auto_router.py +++ b/testing/tests/test_hermes_auto_router.py @@ -175,6 +175,41 @@ def test_every_auto_classification_consults_jetson_and_keeps_safety_floors(monke ) +def test_trivial_prompt_ignores_one_step_jetson_effort_wobble(monkeypatch): + calls = [] + + def classify(text): + calls.append(text) + return router.Decision("question", "medium", "codex", "jetson", "test", 5) + + monkeypatch.setattr(router, "jetson_decision", classify) + + decision = router.classify_task( + "Reply with exactly ROUTE_SMOKE_OK. Do not call tools." + ) + + assert calls == ["Reply with exactly ROUTE_SMOKE_OK. Do not call tools."] + assert (decision.effort, decision.provider, decision.classifier) == ( + "low", + "codex", + "jetson", + ) + + +def test_trivial_prompt_can_still_escalate_on_strong_jetson_signal(monkeypatch): + monkeypatch.setattr( + router, + "jetson_decision", + lambda text: router.Decision( + "question", "high", "claude", "jetson", "test", 5 + ), + ) + + decision = router.classify_task("Check this.") + + assert (decision.effort, decision.provider) == ("high", "claude") + + def test_architecture_and_review_fail_upward_to_claude(monkeypatch): monkeypatch.setattr( router,