# mtlm-router — llms-full.txt > Full-content companion to /llms.txt (generated by tools/render_llms_full.py — do not hand-edit). One fetch gives an agent the product README, the lane catalog, the pricing contract, and the hosted API's own machine doc. --- # README (https://github.com/javimosch/mtlm-router) # mtlm-router **Route natural-language requests to the right lane — on your own hardware, with an honest "I don't know".** Every company has an inbox nobody wants to read: support tickets, citizen complaints, ops requests, shared mailboxes. Someone reads each line, decides *what kind* it is, and sends it to the right team or tool. mtlm-router is the component that makes that decision — typed, confidence-scored, and safe enough to abstain instead of guessing wrong. It's a **7M-parameter decision layer**, trained end-to-end in pure [machin/MFL](https://github.com/javimosch/machin), made in Europe. ~15 ms per decision on CPU, fully self-hosted — data never leaves your network. ## Proof on real production data Trained a draft head on an **anonymized export of a real municipal citizen-request inbox** (6,402 labeled requests, 15 routes): | | | |---|---| | Holdout accuracy | **76.9%** | | Confident-wrong (≥90% conf) | **~0%** — it abstains instead of misrouting | | Head size | 18.7 KB | | Latency | ~180 ms on plain CPU | Live trace: ```text » "mon bac jaune n'a pas été ramassé" → dotation_bacs @ 0.89 — routed » "dépôt sauvage rue des lilas" → abstains @ 0.53 — human reviews » "what's the weather in lyon" → delegate — off-domain, refuses to guess » "attack the dragon" → escalate @ 0.98 — absurd input, safely out ``` The weak routes aren't model failures — they're overlapping labels citizens pick wrong in the form itself. That's a route-table design problem, which is exactly the kind of thing a pilot fixes. [The honest breakdown is in the proposal report](https://hart.intrane.fr/a/javimosch/mtlm-router-realdata-report). ## The input is just your labeled inbox No ML engineering required. One line per historical request: ```text depot_illicite :: il y a un dépôt sauvage de gravats rue des lilas dotation_bacs :: je voudrais obtenir un bac pour le compost collecte_om :: mon bac jaune n'a pas été ramassé ce matin ``` `route :: request text` — or JSONL `{"user": ..., "expect_tool": "route"}`. ~1,000 rows per frequent route is enough for a first draft; ambiguous and refused requests belong in too (they teach abstention). No PII needed — hash identifiers if you want grouping. ```bash python3 tools/head_studio.py --config your_routes.json \ --hf /path/to/hf-bundle --name helpdesk --outdir out/ ``` One command produces a calibrated ~7–20 KB `.head` you swap into the appliance. Per-client routing vocabulary = a file, not a fine-tune. ## The one-call API ```bash curl localhost:8097/v1/route \ -H 'content-type: application/json' \ -d '{"state":"remind me to call the dentist tomorrow at 6pm"}' ``` ```json {"action":"tool_call","route":"set_reminder","confidence":0.9999, "p_yes":1.8e-06,"reason":"ok", "call":{"name":"set_reminder","arguments":{"text":"call the dentist","when":"tomorrow at 6pm"}}} ``` `action` is one of `tool_call` / `chat` / `delegate` — the safety gates are built in (low confidence, `escalate`, head↔generation disagreement all delegate rather than misroute). See [PRODUCT.md](PRODUCT.md) for the full endpoint surface. ## Mixture of experts — one trunk, many heads A frozen 7M trunk; domain knowledge lives in kilobyte `.head` files: ```bash ANVIL_GATE=moe_gate.head \ ANVIL_EXPERTS="it_helpdesk:helpdesk.head,fleet_gate:fit.head,mairie:mairie.head" \ ./anvil-serve model.bin 8401 ``` - **Per-head pooling** — each head declares its own feature tap (`last-token` / `mean@-k` / `max@-k`); the eval picks the recipe, the server obeys. - **Expert pin** — `{"state":..., "expert":"mairie"}` skips the gate for known-domain traffic; the head's own `escalate` class is the abstention path. - **Tenants** — `ANVIL_TENANTS="key:head"` binds API keys to their own heads — multi-customer on one trunk. ## Numbers — held-out *and* live | metric | value | |---|---| | route accuracy | **97.6%** (ECE 0.012) | | gate accuracy (holdout) | **95.4%** (5 domains) | | gate accuracy (live canary) | **98.0%** aggregate — verified per-domain before swap | | real-corpus domain head | **76.9%** (~0% conf-wrong) | | latency | ~15 ms / decision, no GPU | | size | 7.2M params, 8.06 MB int8 | We publish holdout and live-eval side by side because they disagree — candidates pass holdout and still regress a dominant lane live. `tools/eval_gate_live.py` judges per lane, not in aggregate. ## Free lanes — generated from the catalog | lane | routes | status | holdout | oos recall | install | |---|---|---|---|---|---| | `banking77@0.1.0` | 51 | certified | 73.3% | yes | `lane_install banking77` | | `civic@0.2.1` | 14 | demo | 86.6% | 97% | `lane_install civic` | | `civic_fr@0.1.1` | 12 | demo | 99.7% | 91% | `lane_install civic_fr` | | `clinc150@0.1.4` | 151 | certified | 74.1% | 20% | `lane_install clinc150` | | `eq_tone@0.2.0` | 6 | demo | 50.3% | 88% | `lane_install eq_tone` | | `helpdesk@0.2.0` | 7 | demo | 100.0% | 90% | `lane_install helpdesk` | Generated by `tools/render_catalog.py` from [mtlm-heads/index.json](https://github.com/javimosch/mtlm-heads) — every pack is sha256-pinned and `lane_install` verifies before installing. Human version: [the catalog page](https://mtlm-router.intrane.fr/catalog.html). ## The flywheel — it trains on its own traffic Every `/v1/route` decision is logged (`decisions.jsonl`). The flywheel harvests uncertain real requests into a review queue → labeled rows → new head. A daily timer runs the loop; the gate thickens on real traffic, never on its own confident predictions. ## Appliance — the whole product in 7.5 MB ```bash # https://github.com/javimosch/mtlm-router/releases tar xzf mtlm-router-*.tar.gz && cd mtlm-router-* && ./start.sh # /v1/route on :8097 — or install mtlm-router.service ``` Static binary + int8 model + tokenizer + heads + launcher. No deps, no GPU, no cloud. `manifest.json` ships sha256s and the eval it was verified with — the honesty contract is a file. The tarball also ships an agent-first `mtlm-router` CLI — `guide`, `daemon start|stop|status`, `feedback`, `update`, `install` — and the server self-describes (`/guide`, `/llms.txt`, `/v1/whoami`, `/_health`, `/_shutdown`, `/v1/feedback`). 28/28 on [cli-spec-conformance](https://github.com/javimosch/cli-spec-conformance); details in PRODUCT.md §Agent-first surface. ## Who this is for - **SME / integrator**: a shared mailbox or ticket queue that should route itself — your client's vocabulary, their on-prem box, flat cost. - **Ops / platform**: typed ops actions (`set_reminder`, `unlock_account`) behind an API that escalates risky or ambiguous requests instead of firing them. - **Compliance-sensitive**: EU-hosted, air-gap capable, no LLM calls, no per-request cost, no data egress. ## Links - **Landing**: [mtlm-router.intrane.fr](https://mtlm-router.intrane.fr) (EN/FR) - **Model**: [javimosch/mtlm-7m-router3s384](https://huggingface.co/javimosch/mtlm-7m-router3s384) · [Model card](docs/MODEL-CARD.md) - **Runtime**: [machin-anvil](https://github.com/javimosch/machin-anvil) - **Hosted API**: `https://api.mtlm-router.intrane.fr` — [onboarding](https://api.mtlm-router.intrane.fr/llms.txt), pay-per-decision - **Demo**: [machin-game-mtlm-rpg-poc](https://github.com/javimosch/machin-game-mtlm-rpg-poc) (an agent plays a dungeon through the router) ## Layout - `tools/head_studio.py` — self-serve head pipeline (config → `.head`) - `tools/head_probe.py` — layer/pooling sweep + `mhd2` emit - `tools/gen_routespec.py`, `tools/train_head.py` — spec → `.head` - `tools/route_audit.py` — lead requests → audit report + appliance package - `tools/synth_tools.py` — trunk training corpus generator - `tools/eval_gate_live.py`, `tools/probe_gate.py` — per-domain canary + CI gate - `tools/harvest_gate.py`, `tools/judge_review.py`, `tools/build_gate_spec.py`, `tools/shadow_feed.py` — the flywheel - `tools/calibrate.py`, `tools/refit_temp.py`, `tools/health_check.py` — ops - `tools/package_appliance.sh` — build the self-hosted tarball - `docs/MODEL-CARD.md` — bilingual model card - municipal corpus moved to private `mtl-data` repo (client-owned) ## License Apache-2.0 — see [LICENSE](LICENSE). --- # Lane catalog (https://mtlm-router.intrane.fr/catalog.html) Free Apache-2.0 packs installable via tools/lane_install.py; registry at mtlm-heads/index.json. - `banking77@0.1.0` (free) — 51 labels, holdout 73.3%, floor conf>=0.7 - `civic@0.2.1` (free) — 14 labels, holdout 86.6%, floor conf>=0.5 - `civic_fr@0.1.1` (free) — 12 labels, holdout 99.7%, argmax carries - `clinc150@0.1.4` (free) — 151 labels, holdout 74.1%, floor conf>=0.7 - `eq_tone@0.2.0` (free) — 6 labels, holdout 50.3%, floor conf>=0.9 - `helpdesk@0.2.0` (free) — 7 labels, holdout 100.0%, argmax carries --- # Pricing & licensing (mtlm-heads/PRICING.md) # Lane pricing & licensing The runtime, trunk, and tools are open source (mtlm-router, machin-anvil). What is licensed are **lane packs** — calibrated decision heads plus their eval evidence and provenance. You are not buying a weight file; you are buying the certification around it: taxonomy design, leak-free eval, conformal operating point, and recalibration support. ## Tiers | tier | what's in the pack | license | price | |---|---|---|---| | **free / demo** | head + eval report, honestly labeled (incl. weak lanes) | Apache-2.0 | €0 | | **free / certified** | head + holdout eval + conformal qhat + full provenance | Apache-2.0 | €0 | | **commercial lane** | certified pack + commercial-use license + 12mo recalibration window | commercial (per-domain) | per agreement | | **custom lane** | head trained on *your* exported route examples — your distribution, your taxonomy, your exclusivity | work-for-hire | per agreement | | **dedicated hosted** | single-tenant instance on bare metal, your own endpoint, all catalog lanes, no per-call metering | monthly | €99/mo | ## Hosted API posture `api.mtlm-router.intrane.fr` is an **evaluation/demo tier**: a shared metered endpoint for trying the API and lanes before committing — latency and capacity there are not representative. Production clients are assigned a **dedicated bare-metal instance** (€99/mo, per-client endpoint, no metering). ## What "commercial lane" means concretely - License grants production use of that lane's head within the licensed domain, on your own infrastructure — no egress, no per-call metering. - Recalibration window: monitor drift (`tools/monitor.py`), and when the conformal operating point needs refitting we recalibrate against a fresh labeled split — the guarantee tracks your traffic, not the training set. - A lane pack's `manifest.json` carries `pricing.tier` and `license`; `index.json` projects them so installers and buyers see terms before fetch. ## What stays private, always - Client-fitted heads and client-derived corpora live in the private `mtlm-data` repo — never published, never resold, never folded into a public lane. - A commercial lane built for one client is exclusive to that client. - Public demo lanes exist so the format and the honesty bar are inspectable before anyone pays for anything — including lanes that *fail* certification (`eq_tone` ships its 51.4% eval openly; that is the point of the catalog). ## Why free certified lanes exist They are the proof, not the product: `clinc150` demonstrates the format, the verification tooling (`lane_verify.py`), and what a certified eval looks like — on public data where the numbers can be checked by anyone. --- # Hosted API — agent doc (https://api.mtlm-router.intrane.fr/llms.txt, fetched at build time) # mtlm-router hosted API — https://api.mtlm-router.intrane.fr A typed decision layer: send a request, get a route + calibrated confidence + escalate? + complexity in ~35ms compute (~100ms p50 end-to-end measured server-side on the metered path). Same API as the self-hosted appliance (https://github.com/javimosch/mtlm-router). ## Pay per decision (peage rail, EUR) 1. Wallet: curl -s -X POST https://peage.intrane.fr/v1/wallets -d '{"email":"you@example.com"}' -> token pw_… (new wallets include free starter credit) 2. Call: curl -s -X POST https://api.mtlm-router.intrane.fr/v1/route -H 'X-Peage-Wallet: pw_…' -d '{"state":"status of ticket INC4821"}' 3. Price: 1 cent buys 20 decisions for your wallet (X-Meter-Remaining tells you what is left; X-Peage-Receipt carries the signed receipt when a charge happened). Top up: POST https://peage.intrane.fr/v1/topup. No wallet header -> 402 with a JSON pointer to the rail. Idempotent charges; a crashed proxy never double-bills. ## Endpoints (all POST JSON, forwarded to the router) /v1/route {state|messages, options?, expert?, min_conf?} -> {action: tool_call|generate|delegate, route, confidence, p_yes, tier, reason, domain?, expert?} /v1/decide {state, options?} -> which route · /v1/noul {state} -> escalate? p_yes · /v1/score {state} -> complexity 1-4 · /v1/assess {state} -> all three GET /v1/lanes -> installed expert lanes (free) · GET /health -> liveness (also probes the router) · GET /pricing -> this contract as JSON Posture: this endpoint is a shared evaluation tier — production clients get a dedicated bare-metal instance (EUR 99/mo, see /pricing). Lanes: clinc, clinc2 (generic intent), bank77 (banking), civic, civic_fr (municipal), helpdesk2/it_helpdesk, mairie, mairie_c (FR municipal), eq (tone). Pin one with {"expert":"bank77"}. Machine-readable catalog of this surface: GET /.well-known/api-catalog (RFC 9727 linkset). ## Try it free (no wallet) POST /demo/route {"state":"any-session-id>=2chars","text":"your request","expert":""} -> same response shape as /v1/route. Paid from a server-side demo wallet: 30 req/h/IP, 3000/day total; CORS-locked to the landing origin. {"error":"state required"} means the state field is missing or <2 chars. ## Custom routes Your own tool vocabulary is a ~7KB head trained on the frozen trunk: see https://mtlm-router.intrane.fr/#pricing (custom head, one-off) or self-host the free appliance.