# Ecosystem reach with the resident model as router

Date: 2026-08-05. Vikunja #517, split out of #405.
Fixture: `internal/router/eval/ru_ecosystem_v1.json`, 30 held-out Russian cases.
Model: Qwen3-1.7B-UD-Q4_K_XL, llama-server on the host at 127.0.0.1:8899.
Harness: `TestReachWithLLMRouter` in `internal/router/eval/llmrouter_test.go`.

## The numbers

| configuration | reached the right place | praxis | hexis | none |
|---|---|---|---|---|
| classifier + hash (V-405 floor) | 16/30 | 0/12 | — | — |
| classifier + ONNX, after the V-516 grammars | 27/30 | 11/12 | — | — |
| **llm-only** (resident model alone) | **17/30 (56.7%)** | **0/12** | 10/10 | 7/8 |
| **cascade + llm** + hash fallback | **28/30 (93.3%)** | **11/12** | 10/10 | 7/8 |

Latency: llm-only p50 1.29s, p95 1.65s. Cascade p50 1.11s, p95 1.64s.
No case errored in either configuration.

## The open question is answered: the model never reaches Praxis

The route grammar lets the model write any string into the `fn` slot. So it
could in principle emit a literal Praxis capability name, and reach a service
the classifier structurally cannot. It does not. **Praxis is 0/12 with the
model alone.** That is exactly what the classifier alone scores. Every one of
the twelve fails the same way: the utterance stays local with an empty `fn`.

So the stage-0 Praxis grammars from V-516 are not a determinism argument. They
are the only path to Praxis that exists. Deleting them takes reach from 11/12
back to 0/12 whichever engine is answering.

The failure is not that the model routes these badly in its own terms. It
spreads them across `query`, `fact`, `system` and `chat`. Those are reasonable
readings of "что требует внимания" and "готово, закрывай" for a model that has
never been told Praxis exists. Nothing in the prompt names a Praxis capability,
so there is no string for it to write.

## What the model does buy

Hexis is 10/10 with the model alone, and the mutating tag is 10/15 llm-only
against 15/15 through the cascade. The model reaches everything Hexis owns
without help, which is the half the act allowlist already names in the prompt.

Cascade + llm scores one point above the classifier baseline: 28/30 against
27/30, the difference being one attention case. That is the same shape as the
routing fixture, where the router buys about 4 points rather than a doubling.

## The two that still miss

- `eco-ru-021 "что там с нексусом"`. Routes `query`, stays local, wants Praxis.
  Asking after a named service reads as a question about a thing, and no
  grammar claims a service name.
- `eco-ru-029 "сделай это"`. Routes `act` and reaches Hexis. The fixture wants
  nothing reached, because "это" names no target. This is the overreach case
  and it is the one direction worth failing on. The confirmation binding
  downstream still resolves a canonical entity id before anything executes.
  The fixture is right that the turn should have asked.

Overreach is 1 in both configurations, under the 4 the harness asserts.

## How to re-run

```sh
MAVEN_LLM_URL=http://127.0.0.1:8899 \
  deps/go/go/bin/go test -v -count=1 -timeout 40m \
  -run TestReachWithLLMRouter ./internal/router/eval/
```

The host `http_proxy` answers 503 for 127.0.0.1. `noProxyLoopback` in the test
excludes it. A run that scores every case as a route error measured the proxy.
