The task
160 support messages, 9 labels
Refunds, invoices, sign-in, deletion, bugs, features, integrations, plan changes, and "not ours". 50 fixtures for the compile, 110 sealed for the gate. Written by us: no customer data.
Evidence · 2026-10-23 · batch-20261023T165405Z
Support-ticket triage on a pack we wrote: 160 messages, 9 labels. Gemma 4 E4B cleared every check and passed the gate on 108 of 110 sealed fixtures. Every number below is read from the run's evidence files.
The task
Refunds, invoices, sign-in, deletion, bugs, features, integrations, plan changes, and "not ours". 50 fixtures for the compile, 110 sealed for the gate. Written by us: no customer data.
The contract
An answer passes only when it is exactly a label. Off-topic messages answered as if they were ours may not exceed 10%, proven by a bound, so at this sample size the gate allows none.
The fleet
Five small open models on Ollama on a Mac, four Claude tiers on Anthropic's API, and four Grok 4.7 effort levels through a bridge to a Cursor plan. Each runs with the settings it would be served with.
Step 1 · Compile
The screen asks for an 80% Wilson bound on the dev slice to clear the floor and the off-topic limit, so a model that only touches the floor is not assigned. The cheapest survivor by measured cost per call gets the draft.
| Model | Right on dev | p95 | Off-topic accepted | Per 1,000 calls | Verdict |
|---|---|---|---|---|---|
| Qwen 3.5 2Blocal, on Ollama | 36/50 | 190 ms | 3/10 | $0.0027 | below floor pass rate 0.72 (36/50), 80% lower bound 0.66, is below the floor 0.90 |
| Granite 4.2 3Blocal, on Ollama | 45/50 | 196 ms | 2/10 | $0.0028 | below floor pass rate 0.90 (45/50), 80% lower bound 0.86, is below the floor 0.90 |
| Qwen 3.5 4Blocal, on Ollama | 47/50 | 236 ms | 2/10 | $0.0040 | off-topic leak out-of-scope accepts 2/10, 80% upper bound 32%, is over the 10% limit |
| Gemma 4 E4Blocal, on Ollama | 49/50 | 311 ms | 0/10 | $0.0042 | assigned |
| Granite 4.2 8Blocal, on Ollama | 48/50 | 192 ms | 2/10 | $0.0070 | off-topic leak out-of-scope accepts 2/10, 80% upper bound 32%, is over the 10% limit |
| Claude Haiku 5.5Anthropic API | 50/50 | 2,106 ms | 0/10 | $0.040 | too slow p95 latency 2,106 ms is over the 2,000 ms ceiling |
| Claude Sonnet 5.5Anthropic API | 50/50 | 2,352 ms | 0/10 | $0.808 | too slow p95 latency 2,352 ms is over the 2,000 ms ceiling |
| Claude Opus 5.5Anthropic API | 50/50 | 2,075 ms | 0/10 | $1.62 | too slow p95 latency 2,075 ms is over the 2,000 ms ceiling |
| Claude Fable 5.1Anthropic API | 50/50 | 3,341 ms | 0/10 | $4.04 | too slow p95 latency 3,341 ms is over the 2,000 ms ceiling |
| Grok 4.7 lowCursor plan, via a bridge | 50/50 | 12,421 ms | 0/10 | plan | too slow p95 latency 12,421 ms is over the 2,000 ms ceiling |
| Grok 4.7 mediumCursor plan, via a bridge | 50/50 | 10,723 ms | 0/10 | plan | too slow p95 latency 10,723 ms is over the 2,000 ms ceiling |
| Grok 4.7 highCursor plan, via a bridge | 50/50 | 10,493 ms | 0/10 | plan | too slow p95 latency 10,493 ms is over the 2,000 ms ceiling |
| Grok 4.7 xhighCursor plan, via a bridge | 50/50 | 10,508 ms | 0/10 | plan | too slow p95 latency 10,508 ms is over the 2,000 ms ceiling |
Step 2 · Gate
Each draft ran once on the sealed slice, which the compile never reads. The first run screened on point estimates and assigned a model that barely cleared the floor on dev; the gate refused it, and the screen moved to bounds. The second draft leaked one off-topic message. The third passed.
refused: lower bound 0.80 is under the floor
refused: 1 of 30 off-topic messages accepted
passed: lower bound 0.947, 0 of 30 off-topic accepted
Refused · Qwen 3.5 4B
pass rate lower bound 0.80 (95/110) is below the floor 0.90.
One of the off-topic messages it accepted:
"Write a Python script that sorts a list."
Refused · gpt-oss 20B
out-of-scope accepts 1/30, 95% upper bound 14%, is over the 10% limit.
One of the off-topic messages it accepted:
"Our Shopify store's checkout is broken, please fix it."
Passed · Gemma 4 E4B
0 of 30 off-topic messages accepted, an upper bound of 8.3%. p95 241 ms. The fingerprint matched the compile.
Step 3 · Enforce
An ordinary OpenAI-style call with one extra header. The enforcer picks the model and its settings from the passed policy and names the model that served the call. A task the policy does not cover is refused before any model is called.
These calls were re-sent through the enforcer with the run's passed policy after the run. The Slack message is borderline: during the run it came back integration_help, here feature_request. That variation is why the gate measures a slice, not one answer.
POST /v1/chat/completions x-sllim-task-id: support-triage user: Hi team, our card was charged twice for the October renewal. Can you send one of those payments back? → 200 billing_refund x-sllim-model-id: local-gemma-e4b · 190 ms POST /v1/chat/completions x-sllim-task-id: support-triage user: Is there a way to push paid invoices into our Slack channel automatically? → 200 feature_request x-sllim-model-id: local-gemma-e4b · 185 ms POST /v1/chat/completions x-sllim-task-id: support-triage user: Can you recommend a good pizza place near the office? → 200 out_of_scope x-sllim-model-id: local-gemma-e4b · 185 ms POST /v1/chat/completions x-sllim-task-id: support-reply user: Write a friendly reply to this customer. → 409 task_unassigned no model was called
Version 1, passed. The binding constraints are the checks that removed cheaper models; the rejected list keeps every other model and why it lost.
{
"id": "support-triage-policy",
"version": 1,
"status": "passed",
"assignments": [
{
"task_id": "support-triage",
"model_id": "local-gemma-e4b",
"prompt_id": "triage-support-prompt-1",
"fallback_model_id": null,
"constraint_ids": [
"floor",
"out_of_scope",
"latency"
],
"batch_id": "batch-20261023T165405Z",
"model_fingerprint": "sha256:e3cfce764ed72c018420d2b7a4dddf216c14489c79d90ed3218f5726c7e62c01",
"rejected": [
{
"model_id": "local-qwen-2b",
"reason": "pass rate 0.72 (36/50), 80% lower bound 0.66, is below the floor 0.90; out-of-scope accepts 3/10, 80% upper bound 43%, is over the 10% limit"
},
{
"model_id": "local-granite-3b",
"reason": "pass rate 0.90 (45/50), 80% lower bound 0.86, is below the floor 0.90; out-of-scope accepts 2/10, 80% upper bound 32%, is over the 10% limit"
},
{
"model_id": "local-qwen-4b",
"reason": "out-of-scope accepts 2/10, 80% upper bound 32%, is over the 10% limit"
},
{
"model_id": "local-granite-8b",
"reason": "out-of-scope accepts 2/10, 80% upper bound 32%, is over the 10% limit"
}
]
}
]
}The bill
Each model's cost is its measured tokens per call times its price. Claude is at list price. The local model's price is a cost we set for hardware time; a customer would put in their own.
1,000,000 calls a month, measured tokens per call
What this does not prove
The run shows the mechanism working end to end on real models. It is not a customer result, and the savings on a real workload depend on its tasks, its floor, and its fleet.
Caveats
Reproduce it from the repository: bun run demo:triage --allow-paid. The evidence files for all three runs are kept in the repository beside the code.