# Routing vs Polling — Benchmark Result > Generated by `benchmark/run.ts`. **Every number below was produced by running that > script**; nothing in this file is hand-written or estimated. Regenerate with: > > ``` > node --experimental-strip-types benchmark/run.ts > ``` ## Setup | | Count | |---|---:| | Tasks requiring `code` | 15 | | Tasks requiring `docs` | 5 | | Workers declaring `code` | 3 | | Workers declaring `docs` | 2 | - **Seed**: `20261031` (fixed). The task order is shuffled by a deterministic LCG, so the run is reproducible: the same seed always produces the same pool, and the same numbers. - **Routing gate**: the production `profileRefusal` from `src/core/task-store.ts`. No capability logic is re-implemented in the benchmark — each accept/reject is that function's own decision. - **Baseline**: blind round-robin, no gate. A task counts as "successful" the moment it is handed to a worker. ## Headline ``` Without routing: 55.0% success, 45.0% wasted. With routing: 100.0% success, 0 wasted. ``` | Metric | Baseline (round-robin) | With routing | |---|---:|---:| | Tasks assigned | 20 | 20 | | Successful | 11 | 20 | | Failed (mis-assigned) | 9 | 0 unassignable | | Wasted assignments | 9 | 0 | | Success rate | 55.0% | 100.0% | | Wasted rate | 45.0% | 0.0% | ## Per-worker load ### Baseline (round-robin) | Worker | Assigned | Succeeded | Failed | |---|---:|---:|---:| | `worker-code-0` | 4 | 4 | 0 | | `worker-code-1` | 4 | 2 | 2 | | `worker-code-2` | 4 | 3 | 1 | | `worker-docs-0` | 4 | 1 | 3 | | `worker-docs-1` | 4 | 1 | 3 | ### With routing | Worker | Assigned | Succeeded | Failed | |---|---:|---:|---:| | `worker-code-0` | 15 | 15 | 0 | | `worker-code-1` | 0 | 0 | 0 | | `worker-code-2` | 0 | 0 | 0 | | `worker-docs-0` | 5 | 5 | 0 | | `worker-docs-1` | 0 | 0 | 0 | ## Reading the result **Why baseline wastes work.** Round-robin assigns by position, not by capability. With a seeded shuffle, each worker receives a mix of matching and non-matching tasks, so a predictable share of the pool is handed to a worker that cannot serve it. In a real dispatch run that share is not free: the worker claims the task, then either escalates it or fails it, and both paths cost a claim/retry cycle. **Why routing is (or is not) perfect here.** Every task found a worker whose declared capabilities cover it, so routing reached 100.0%. That is a property of the population: 3 code workers cover 15 code tasks, and 2 docs workers cover 5 docs tasks. It is **not** a claim that routing always succeeds — a population where every docs worker is missing would yield unassignable tasks, and the benchmark reports that case explicitly rather than hiding it. ## Unassignable tasks None. Every one of the 20 tasks found at least one accepting worker, so routing achieved 20/20 assignments with 0 mis-assignments. ## What this benchmark does NOT prove: load balance The gate that produces the success number answers exactly one question — *may this worker take this task?* It has no notion of how much work a worker is already carrying, so the assignment loop takes the first accepting worker every time and drains the first one in line. The per-worker tables above show the consequence: | Worker | Baseline assigned | Routing assigned | |---|---:|---:| | `worker-code-0` | 4 | 15 | | `worker-code-1` | 4 | 0 | | `worker-code-2` | 4 | 0 | | `worker-docs-0` | 4 | 5 | | `worker-docs-1` | 4 | 0 | 3 of 5 workers received **no work at all** in routing mode: `worker-code-1`, `worker-code-2`, `worker-docs-1`. Correctness and balance are two different gains, and only the first is measured here: | Gain | Proven | Source | |---|---|---| | **Correctness** — no task lands on a worker that cannot serve it | Yes (100.0% vs 55.0%) | `profileRefusal` capability gate | | **Balance** — work spread evenly across idle workers | **No** | Not implemented | In a real dispatch run this matters less than it looks, because workers **pull** rather than being pushed: whoever finishes first claims the next ready task, so the pool tends to level out on its own. But even distribution is *not* something the routing gate provides, and the success rate should not be read as if it were. ## Reproducibility The population is a pure function of `SEED`. Changing the seed reshuffles the task order, which shifts *which* worker gets which mismatch in baseline mode — but because the detection rule (`profileRefusal`) does not depend on order, **routing's success rate is invariant across seeds** while baseline's is not. That asymmetry is the point: routing converges, polling depends on luck. This was measured, not assumed. Running the same script under `BENCH_SEED={1, 2, 7, 42, 20261031, 999999}` produced: | Seed | Baseline success | Baseline wasted | Routing success | Routing wasted | |---|---:|---:|---:|---:| | 1 | 35.0% | 65.0% | 100.0% | 0 | | 2 | 55.0% | 45.0% | 100.0% | 0 | | 7 | 55.0% | 45.0% | 100.0% | 0 | | 42 | 45.0% | 55.0% | 100.0% | 0 | | 20261031 (default) | 55.0% | 45.0% | 100.0% | 0 | | 999999 | 65.0% | 35.0% | 100.0% | 0 | Routing held at 100% for every seed; baseline ranged from 35% to 65%. Reproduce the sweep with `BENCH_SEED=` (PowerShell: `$env:BENCH_SEED = ""`).