# exp (c) — Replay dispatch mode: `tracets` vs `thinktime`

Which replay mode should we benchmark agentic serving with, and how much does it
change the measured performance?

**Two dispatch modes** (`replayer --dispatch-mode`):
- **`tracets`** (default): turn-k at the absolute trace timestamp ⇒ effectively
  `max(prev_finished, trace_ts)`.
- **`thinktime`**: turn-1 at trace arrival; turn-k at
  `prev_finished + time_to_parent_chat` (the REAL production gap; annotated by
  `scripts/add_time_to_parent.py` from the raw trace's
  `request_ready_time_ms`/`request_end_time_ms`).

Setup: w600 windowed to first 300 s (366 reqs, 223 multi-turn), round-robin across
N H20 instances, both modes on the same instances, 100% completion throughout.

## Performance result

| metric | N6 tracets | N6 thinktime | N8 tracets | **N8 thinktime** |
|---|---:|---:|---:|---:|
| system TPS | 110.9 | 96.1 | 111.8 | **119.3** |
| wall (s) | 975 | 1125 | 967 | **787** |
| TTFT p50 / p90 / p99 | 4.4 / 61.8 / 135 | 4.5 / 83.7 / 130 | 2.9 / 56.1 / 115 | 3.1 / **39.7** / **83.5** |
| TPOT p50 / p90 / p99 | .039 / .242 / .96 | .037 / .264 / .69 | .037 / .174 / .89 | .037 / .188 / .85 |
| E2E p50 / p90 / p99 | 17.1 / 118 / 298 | 15.0 / 120 / 338 | 11.9 / 102.8 / 245 | 12.3 / **73.5** / **227** |

**At N=8 (capacity slack), `thinktime` is clearly better**: E2E p90 −28%, TTFT p90
−29%, TPS +7%, wall −19%. **At N=6 (saturated) they converge** (E2E p90 ≈ 118–120 s).
TPOT (decode) is dispatch-independent everywhere.

## Why — the mechanism (`figs/exp_c_dispatch_ablation.png`)

`tracets` collapses the realized inter-turn gap to ~0 under load (p50 0.00 s, 75%
< 0.5 s) — it fires the next turn immediately because the trace timestamp is in the
past. `thinktime` preserves the real gap (p50 1.22 s = the trace). The figure shows
both realized-gap CDFs against the real `time_to_parent_chat`.

That gap-collapse manufactures **bursts** → peak concurrency spikes → KV-pool
pressure → preemption → inflated tail latency + wasted throughput. The bursts
re-saturate the system regardless of capacity, which is why scaling 6→8 instances
barely helped `tracets` (975→967 s) but helped `thinktime` a lot (1125→787 s).
Under saturation (N=6) there is no slack for bursts to harm, so the modes converge.

## Conclusion

Benchmark agentic serving with **`--dispatch-mode thinktime`** — it is the faithful
closed-loop agent load and avoids the `tracets` burst artifact that makes the system
look ~30% worse on tail latency than it is. Use `tracets` only as an explicit bursty
stress case. (See the repo [`README.md`](../../README.md) for the headline guidance.)

Caveat: round-robin pays full prefill every turn (no cache reuse), so absolute
latencies here are high; a cache-aware policy (LPWL) would lower them and likely
widen the `thinktime` advantage. The raw window is also heavy (E2E in tens of
seconds); a lighter load shows a healthier operating point.

## Repro
```bash
N=8 TRACE=traces/w600_ttp_win.jsonl bash v2/exp_c_dispatch_ablation/run_ablation.sh
python v2/exp_c_dispatch_ablation/analyze.py traces/w600_ttp_win.jsonl \
    v2/exp_c_dispatch_ablation/results/metrics_{tracets,thinktime}.jsonl
```
