<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ian L. Paterson</title>
    <description>The latest articles on Forem by Ian L. Paterson (@ianlpaterson).</description>
    <link>https://forem.com/ianlpaterson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3796710%2F44ffae12-52df-4924-82e1-40341621e8e0.png</url>
      <title>Forem: Ian L. Paterson</title>
      <link>https://forem.com/ianlpaterson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ianlpaterson"/>
    <language>en</language>
    <item>
      <title>Building llama.cpp from source on a Dell Precision T5820 with an RTX 3090 Ti (after seven power cycles)</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 20:09:10 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/building-llamacpp-from-source-on-a-dell-precision-t5820-with-an-rtx-3090-ti-after-seven-power-200i</link>
      <guid>https://forem.com/ianlpaterson/building-llamacpp-from-source-on-a-dell-precision-t5820-with-an-rtx-3090-ti-after-seven-power-200i</guid>
      <description>&lt;p&gt;I pulled a Quadro M4000 out of a used Dell Precision T5820, dropped in an RTX 3090 Ti, and turned the box into a homelab inference node running Qwen3.6-27B at 42 tok/s. Getting there took seven BIOS power cycles before the PCIe link would train. The Dell forum threads and the LLM-generated answers all miss the same thing: the fix is patience.&lt;/p&gt;

&lt;p&gt;This post has the working recipe, the from-source llama.cpp build, the 12VHPWR connector physics that nobody explains, and the long-context tricks that let a $700 used GPU serve 262K-token windows on a single 24 GB card. Numbers are from May 2026 against driver 580.142, Qwen3.6-27B Q4_K_M, and llama.cpp at the commit current at publish. Versions in this stack move fast, so treat the specific numbers as a snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The working recipe
&lt;/h2&gt;

&lt;p&gt;If you landed here from a Dell forum thread and just need the answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;BIOS 2.41 or newer on the T5820. Verify in System Information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disable Secure Boot, set boot mode to UEFI only, and leave Primary Video on Auto.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3090 Ti in slot 1 (top, CPU lanes) or slot 4 (also CPU lanes). Slot 1 is x8 on a Xeon W-2223 build and slot 4 is x16, but PCIe Gen3 x8 does not bottleneck a single GPU inference workload. Pick on clearance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;12VHPWR seated until you hear the latch click. Three separate PSU cables to the 3-to-1 adapter. Y-splitters and pigtails are a fire hazard at 450 W, so all three 8-pin inputs need to be populated from three independent rails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Both PSUs powered before you press the Dell power button. If you are running dual-PSU, bring up the GPU PSU first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;First boot may power-cycle five to seven times before POST.&lt;/strong&gt; Do not abort early. The BIOS is retraining the PCIe link.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After Linux boots, &lt;code&gt;sudo apt install nvidia-driver-580&lt;/code&gt;, reboot, then verify with &lt;code&gt;nvidia-smi&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 6 is the step most forum advice skips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building llama.cpp from source for the 3090 Ti
&lt;/h2&gt;

&lt;p&gt;For a single-user 24 GB box, the right answer is to build llama.cpp from source against your exact GPU compute capability. Ollama, Docker images, and prebuilt binaries all lag on features and hide tuning flags. Building from source picks up upstream improvements the same day they land, runs faster on this hardware, and gives you access to every knob.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ggml-org/llama.cpp ~/llama.cpp
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/llama.cpp
cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;-DGGML_CUDA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="nt"&gt;-DCMAKE_CUDA_ARCHITECTURES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;86 &lt;span class="nt"&gt;-DLLAMA_CURL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;86&lt;/code&gt; is sm_86, Ampere, the 3090 Ti's compute capability (same number for the regular 3090). Build takes about fifteen minutes on eight threads, with CUDA kernel codegen (nvcc, ptxas, cicc) doing most of the work. Subsequent rebuilds are quick if you set up ccache before the first build.&lt;/p&gt;

&lt;p&gt;Pull a model and start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;huggingface-cli download unsloth/Qwen3.6-27B-GGUF Qwen3.6-27B-Q4_K_M.gguf &lt;span class="nt"&gt;--local-dir&lt;/span&gt; ~/models
~/llama.cpp/build/bin/llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-m&lt;/span&gt; ~/models/Qwen3.6-27B-Q4_K_M.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-ngl&lt;/span&gt; 99 &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; 8192 &lt;span class="nt"&gt;--jinja&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you an OpenAI-compatible API on &lt;code&gt;localhost:8080&lt;/code&gt;. &lt;code&gt;-ngl 99&lt;/code&gt; puts all layers on the GPU and &lt;code&gt;--jinja&lt;/code&gt; is mandatory if you want tool calling to work, which is covered in its own section below. At this baseline configuration the box uses 17.3 GiB of VRAM and serves Qwen3.6-27B Q4_K_M at 42 tok/s on a 200-word generation, with 86% GPU utilization and 445 W under load.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seven-power-cycle install diary
&lt;/h2&gt;

&lt;p&gt;Order matters, so here is what actually happened.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Fresh Ubuntu 25.10 install on the box, SSH access via Tailscale.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lspci&lt;/code&gt; saw only the Quadro M4000 (PCH-attached, x4) on bus 04. The 3090 Ti did not enumerate, both CPU PCIe root ports were empty, and the card was inert with no fan twitch on power-on and no LED.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;False lead. I assumed PSU sequencing was the issue, pulled the card to the bench, and jumped PS_ON on the 1 kW supply. The card stayed inert. Twenty minutes in I remembered that most retail Add-In Board (AIB) cards refuse to light or spin until they are seated in a PCIe slot, which makes bench tests unreliable for "is the card alive" checks on Ampere-class GPUs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real cause: the 12VHPWR connector was loose. I reseated it firmly until it clicked, the card LED lit up, and it was alive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installed in slot 1 of the T5820, powered up the GPU PSU first, then pressed the Dell power button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Boot loop. The box power-cycled four, five, six, seven times before settling into another black screen with no SSH, and I aborted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Searched Dell forums and LinusTechTips and found multiple unresolved threads. Dell's official guidance qualifies the RTX 3090 for slots 2 and 4 of the T5820, the two x16 CPU slots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tried slot 4, same boot-loop pattern, aborted again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pulled the M4000 entirely and booted to BIOS on the 3090 Ti to confirm Secure Boot disabled, UEFI only, Primary Video on Auto. The BIOS 2.41 System Setup screen does not expose a user-facing toggle for memory-mapped I/O (MMIO) above 4 GB on this revision, and the firmware appears to handle the mapping automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reinstalled in slot 1 (more clearance for the 3.5-slot girth than slot 4, accepting the x8 lane drop). The boot loop returned, but this time I waited instead of aborting and the box POSTed cleanly on the seventh cycle. SSH came back. &lt;code&gt;lspci&lt;/code&gt; showed &lt;code&gt;0000:b3:00.0 GA102 [GeForce RTX 3090 Ti] [10de:2203]&lt;/code&gt; on a CPU root complex, which is what was supposed to happen all along.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installed &lt;code&gt;nvidia-driver-580&lt;/code&gt; via apt, rebooted, and &lt;code&gt;nvidia-smi&lt;/code&gt; came up clean.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why this is not a software fix
&lt;/h3&gt;

&lt;p&gt;The BIOS gets there or it doesn't, and on a fresh install with a high-power GPU it takes more attempts than feels reasonable. The Dell community threads stay unresolved because the resolution lives in the firmware's PCIe link-training routine, which runs on its own schedule. Once you understand that, the right move is to wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixes that don't work (and why the forums keep recommending them)
&lt;/h2&gt;

&lt;p&gt;Every Dell community thread I read in the BIOS 2.41 era, every LLM-generated answer I asked for, and every YouTube tutorial in the first two pages of results converges on the same four pieces of advice. None of them matched what I was seeing on this build. They are worth naming, because if you are debugging this in the moment, you will burn an hour on each before you give up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 10-pin CPU2 to 8-pin adapter
&lt;/h3&gt;

&lt;p&gt;This is a real Dell part, and on a build where the GPU draws power from the workstation's stock 950 W supply it does matter. Once you put the 3090 Ti on a separate dedicated PSU (which you should, given the 450 W thermal design power, or TDP), the CPU2 adapter becomes irrelevant because the GPU is no longer pulling from the Dell rail at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Above 4G Decoding in BIOS
&lt;/h3&gt;

&lt;p&gt;The toggle is not in my BIOS 2.41 System Setup, and on this revision the firmware appears to handle MMIO above 4 GB automatically. The screenshots in those forum threads are from older Dell consumer BIOSes or other workstation lines, so if your firmware does expose the toggle, leave it on. The qualification doesn't break anything either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use slot 2 or slot 4, not slot 1
&lt;/h3&gt;

&lt;p&gt;All three options are CPU lanes (slots 2 and 4 at x16, slot 1 at x8 on Xeon W-2223 builds), and the practical difference for a single-GPU inference workload is negligible because PCIe Gen3 x8 is not the bottleneck. I tried slot 1 first, hit the boot loop, moved to slot 4 because that's what the table said, hit the same boot loop, and went back to slot 1 because it has more clearance for the 3.5-slot girth. Slot choice was a dead end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep power reset (hold the power button for thirty seconds)
&lt;/h3&gt;

&lt;p&gt;This drains residual charge from the PSU capacitors and addresses stuck power states, which is a real failure mode for some hardware but the wrong diagnosis here. The boot loop is the BIOS taking multiple cycles to negotiate PCIe link training with a card outside its qualification database, and holding the power button is harmless against that but also does nothing to speed it up.&lt;/p&gt;

&lt;p&gt;The link-training cycles run on firmware time, and every shortcut the forums recommend (BIOS flags, adapters, slot moves) leaves that time unchanged. If the card is seated correctly and the power is right, stop aborting after cycle four and let it run to seven. The forum threads stay open because most people abort before the BIOS finishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  12VHPWR: the connector that fails silently
&lt;/h2&gt;

&lt;p&gt;The 16-pin 12VHPWR connector is its own category of pain, and most write-ups about 3090 Ti, 4090, or 5090 problems are downstream of it. The Founders Edition adapter that ships with most cards is a 3-to-1 setup where three 8-pin PCIe inputs collapse into a single 16-pin output that plugs into the GPU. Three rules the marketing material does not stress:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;All three 8-pin inputs need to be populated&lt;/strong&gt; from three separate PSU rails with three separate cables. Y-splitters and pigtails are a fire hazard at 450 W. The 2023 CableMod recall covered angled adapters where the connector could shift loose under cable tension, but the underlying physics (partial contact at 35-40 A) is the same failure mode you create when you split rails or share them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The 12VHPWR latch needs to audibly click.&lt;/strong&gt; A connector seated 95% of the way will pass continuity tests, fail under load, and on some cards melt the connector housing. The audible click is the only reliable signal, so push until it clicks. If it does not click, the card is not seated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The card will not light on the bench.&lt;/strong&gt; Ampere-generation cards keep the fans and the LED off until they are seated in a PCIe slot, so you cannot validate "is this card alive" by jumping PS_ON on the PSU and looking for fan spin. The card has to be installed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third rule cost me the most time. I pulled the card to the bench, jumped PS_ON, watched it sit dark and inert, and concluded the card was DOA. It was fine all along, just waiting for a slot before it would wake up.&lt;/p&gt;

&lt;h2&gt;
  
  
  262K context on a single 24 GB card
&lt;/h2&gt;

&lt;p&gt;This is the upgrade nobody mentions in the homelab threads. The 8 K baseline is what the recipe ships with, but Q4 KV cache and flash attention rewrite the memory math entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/llama.cpp/build/bin/llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-m&lt;/span&gt; ~/models/Qwen3.6-27B-Q4_K_M.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-ngl&lt;/span&gt; 99 &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; 262144 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fa&lt;/span&gt; on &lt;span class="nt"&gt;-ctk&lt;/span&gt; q4_0 &lt;span class="nt"&gt;-ctv&lt;/span&gt; q4_0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parallel&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jinja&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Qwen3.6-27B Q4_K_M holds &lt;strong&gt;262,144 tokens of context on a single 24 GB 3090 Ti&lt;/strong&gt; at 39 tok/s eval and 86 tok/s prompt processing. VRAM use sits at 21.3 GiB with 2.7 GiB headroom against the 24,564 MiB cap, which is 3 tok/s slower than the 8K baseline and rounding error for most workloads.&lt;/p&gt;

&lt;p&gt;KV cache type is what makes this possible. Most setups leave KV at fp16 default, or push it to q8 thinking "more bits equals more quality." On Qwen3.6-27B dense at 262 K context:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;KV cache type&lt;/th&gt;
&lt;th&gt;VRAM at 262K&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fp16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;does not fit&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q8_0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;23 GiB (just fits)&lt;/td&gt;
&lt;td&gt;extrapolated ~3x slower from 96K measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q4_0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;21.3 GiB&lt;/td&gt;
&lt;td&gt;39 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I verified the q8 trap on this rig at the context length where I could measure it directly. At 96 K context I observed a 23% throughput hit on the q4 to q8 swap, which dropped from 39 tok/s to 30 tok/s. The penalty scales with context length because more KV cells means more per-token dequant work. The 3x slowdown at 262 K is an extrapolation from that scaling rather than a head-to-head measurement, since q8 barely fits at 262K and I did not push a long run through it. Either way, the direction is consistent: q8 KV is a trap on consumer 24 GB hardware.&lt;/p&gt;

&lt;p&gt;A couple of tradeoffs worth flagging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--parallel 1&lt;/code&gt; is a single slot, which is fine for solo use. Concurrent users will queue, which is rarely what you want.&lt;/li&gt;
&lt;li&gt;KV cache quality at q4 over very long contexts is empirically untested for this model. Long-document recall could degrade in ways that throughput numbers will never show, so a needle-in-haystack pass is a precondition for trusting this configuration for real long-document work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hobbyist-tier hardware can now serve frontier-tier context lengths if you accept single-user throughput. A $700 used 3090 Ti running 262 K context locally breaks even against Claude Sonnet's $15/M output pricing after about two weeks of pegged inference. Add roughly $18 of electricity during those two weeks at $0.12 per kWh, or about $39 a month if you keep the card pegged. The ceiling shifted, and most homelab write-ups have not caught up.&lt;/p&gt;

&lt;p&gt;For comparison, as measured on my Mac Studio M2 Max with 32 GB unified memory, MLX 1.6.0 runs Qwen3.6-35B-A3B (UD-Q4_K_XL, 35B total / 3B active per token) at 49 tok/s on a 32K context window. That is roughly the same throughput as the 3090 Ti on dense Qwen3.6-27B at 39 tok/s, with a smaller context ceiling and a larger model. The mixture-of-experts (MoE) bandwidth-divided-by-active-params speedup math (&lt;code&gt;3B active / 400 GB/s&lt;/code&gt;) does not translate cleanly at Max-class memory bandwidth, since the headline MoE wins live on M-Ultra (800 GB/s) and leave Studio Max behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The silent OOM: context checkpoints and prompt cache
&lt;/h2&gt;

&lt;p&gt;Two runtime allocations can add up to 19 GiB on top of the model and don't appear in the static VRAM math everyone publishes. Context checkpoints and the slot prompt cache absolutely show up at peak load, and the failure mode is silent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context checkpoints&lt;/strong&gt; (&lt;code&gt;-ctxcp&lt;/code&gt; or &lt;code&gt;--ctx-checkpoints&lt;/code&gt;) cache intermediate KV states so the server can rewind without reprocessing the prefix. The default is 32 per slot. Each checkpoint on Qwen3.6-27B runs roughly 150 MiB, so 4 parallel slots × 32 checkpoints × 150 MiB gives a worst case of 19 GiB on top of the model. That is not headroom anyone is publishing about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slot prompt cache&lt;/strong&gt; caches recent prompts (default 8 GiB limit) so reused prefixes skip reprocessing. Invisible in the "model plus KV" math, very visible at peak.&lt;/p&gt;

&lt;p&gt;A 128 K q8-KV configuration typically reports 21 GiB at startup with 3 GiB free and runs fine for a dozen short turns, until a long context-heavy turn lands. The checkpoint cache spikes 4-5 GiB, the prompt cache takes another 2-3 GiB, and the server dies with &lt;code&gt;cudaMalloc&lt;/code&gt; failing on the next 200 MiB allocation. The log shows happy request handling and then &lt;code&gt;srv operator(): cleaning up before exit...&lt;/code&gt; followed by silence, with no OOM trace and no backtrace. The CUDA layer just quits.&lt;/p&gt;

&lt;p&gt;Two flags neither tutorial mentions will fix this. &lt;code&gt;-np 1&lt;/code&gt; collapses the parallel slot pool to one (the pool is just an OOM multiplier on every per-slot cache when you are the only user), and &lt;code&gt;-ctxcp 4&lt;/code&gt; caps context checkpoints at 4 per slot, which drops that allocation from 4.8 GiB to 600 MiB. With both caps plus q4 KV at 128 K context, the configuration holds at 18.6 GiB used and 6 GiB free across long-context sessions. Without them, the same flags die on the first long-prompt turn.&lt;/p&gt;

&lt;p&gt;If your llama-server "randomly" dies under load and the log tail shows &lt;code&gt;cleaning up before exit&lt;/code&gt; with no error, you are probably hitting this. Watch GPU memory during a real workload rather than only at startup, since startup numbers underreport the peak by several GiB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your tool calls are hallucinating: the --jinja flag
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;--jinja&lt;/code&gt; to the llama-server launch command. Without it, llama-server falls back to a C++ template path that silently drops the &lt;code&gt;tools&lt;/code&gt; parameter before the model ever sees the request, so any request that depends on tool schemas behaves as if no tools were declared. I verified this with a direct curl. Same weights, same prompt, flag on versus off: with the flag off the model roleplayed the tool call as plain text, and with the flag on it returned a proper &lt;code&gt;tool_calls&lt;/code&gt; array. One server flag, entirely different observable behavior.&lt;/p&gt;

&lt;p&gt;To confirm the flag is doing what it should, grep the launch command for &lt;code&gt;--jinja&lt;/code&gt; and check the log for &lt;code&gt;chat template, thinking = 1&lt;/code&gt;. If the template line shows but the flag is absent, that is the bug.&lt;/p&gt;

&lt;p&gt;A related Qwen3-specific gotcha. The &lt;code&gt;/no_think&lt;/code&gt; sentinel as a system-prompt string is silently ignored by Qwen3.6-27B, and the working lever is &lt;code&gt;chat_template_kwargs.enable_thinking=false&lt;/code&gt; in the request body. The intuitive next move ("turn off thinking on leaf subagents to save time-to-first-token") does not survive a controlled test. I ran 24 trials of parent-with-thinking vs subagent-without across two task types, both modes always hit max quality, and thinking-ON was consistently faster end-to-end. The intuition was noise. A &lt;a href="https://ianlpaterson.com/blog/3090-ti-qwen-speedup-dflash-mtp/" rel="noopener noreferrer"&gt;separate post on speculative decoding&lt;/a&gt; covers that bench in detail. For this article, leave thinking on for both roles and add &lt;code&gt;--jinja&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers in one place
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;metric&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;RTX 3090 Ti, GA102, sm_86, 24,564 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Qwen3.6-27B Q4_K_M (unsloth GGUF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput at 8K context&lt;/td&gt;
&lt;td&gt;42 tok/s eval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput at 262K context&lt;/td&gt;
&lt;td&gt;39 tok/s eval, 86 tok/s prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VRAM at 262K context&lt;/td&gt;
&lt;td&gt;21.3 GiB used, 2.7 GiB headroom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4→Q8 KV penalty at 96K&lt;/td&gt;
&lt;td&gt;23% throughput hit (39 → 30 tok/s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power under load&lt;/td&gt;
&lt;td&gt;445 W, 86% GPU util, 57°C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle, no model loaded&lt;/td&gt;
&lt;td&gt;32°C, 10 W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle, model resident at P8&lt;/td&gt;
&lt;td&gt;50°C, 26 W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold boot to first POST&lt;/td&gt;
&lt;td&gt;7 BIOS power cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build time, llama.cpp from source&lt;/td&gt;
&lt;td&gt;~15 min on 8 threads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Will a Dell Precision T5820 accept an RTX 3090 Ti?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Dell qualifies the 3090 for slots 2 and 4 (the x16 CPU slots), but slot 1 works just as well on the x8 lanes. PCIe Gen3 x8 does not bottleneck single-GPU inference, so the slot choice comes down to clearance rather than throughput. The card is a 3.5-slot girth, so slot 1 has the most physical clearance. Run the 3090 Ti off a separate dedicated PSU rather than the Dell 950 W stock supply, because the Dell rails are not designed for the 450 W transient spikes the card pulls on the 12 V line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does my Dell Precision boot-loop with a new GPU?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The BIOS is retraining the PCIe link with a card that is outside its qualification database. On BIOS 2.41 with a 3090 Ti in a fresh install, five to seven power cycles is normal. The official advice (10-pin CPU2 adapter, Above 4G Decoding, slot 2/4, deep power reset) does not change the outcome, so the correct response is to let the system cycle until it POSTs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At what point should I give up and assume the boot loop is a real fault?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ten cycles without POST is my own abort threshold. Beyond that, check 12VHPWR seating first (audible latch click), then PSU rail integrity (continuity test on each 8-pin input from the wall to the adapter), then try a different slot. If all three pass and it still loops on a fresh install, you may have a genuine PCIe fault or a card with degraded power delivery, which is a return-to-vendor situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the T5820 950 W PSU enough for a 3090 Ti?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Technically yes, but practically you want a separate PSU for the GPU. The Dell stock supply has the cable connectors, but the 12 V rail was not designed for the 450 W transient spikes the 3090 Ti pulls under load. A dedicated 1 kW supply with PS_ON jumped to ground costs about $80 and removes the entire failure class. (PS_ON is the green wire on a 24-pin ATX connector. Tied to a black ground, it tells the PSU to stay on without a motherboard.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is sm_86 in the CUDA build command?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;sm_86 is the compute capability identifier for Nvidia's Ampere generation, which covers the RTX 3090, 3090 Ti, A40, A100, and a few others. The &lt;code&gt;-DCMAKE_CUDA_ARCHITECTURES=86&lt;/code&gt; flag tells nvcc to generate kernels for that target only, which keeps build time down and avoids fat-binary bloat. 4090 owners use 89, H100 owners use 90.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does &lt;code&gt;-ngl 99&lt;/code&gt; do in llama-server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-ngl&lt;/code&gt; is the number of model layers to offload to the GPU. Setting it to 99 means "all of them," since no current model has more than 99 layers, so the entire model lives in VRAM. Lower numbers split the model between CPU RAM and VRAM, which costs throughput badly. On a 24 GB card with a 27B Q4 model, 99 fits comfortably and there is no reason to do anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the Above 4G Decoding toggle on a Dell Precision T5820?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not in BIOS 2.41 System Setup as a user-facing toggle. On this revision the firmware appears to handle MMIO above 4 GB automatically. Older Dell consumer BIOSes and other workstation lines expose it, which is what the forum screenshots are showing. If your firmware does expose it, leave it on, since the qualification does not break anything either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;llama.cpp vs Ollama on a 3090 Ti, which should I run?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;llama.cpp from source is the right call for single-user latency and tuning headroom. Ollama works fine for a "just works" start, but it ships pre-built binaries that lag on features, wraps llama.cpp anyway, and hides flags like &lt;code&gt;--jinja&lt;/code&gt;, &lt;code&gt;-ctk&lt;/code&gt;, and &lt;code&gt;-ctxcp&lt;/code&gt; that materially change throughput and VRAM behavior on a 24 GB card. Build llama.cpp yourself and you get the same backend and every knob.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left on this box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nvidia-smi -pl 350&lt;/code&gt; power-limit to drop heat with a marginal throughput cost. The card is still running at the 450 W default.&lt;/li&gt;
&lt;li&gt;vLLM comparison on the same model. llama.cpp wins on single-user latency. vLLM should win on batched throughput, so it is worth measuring.&lt;/li&gt;
&lt;li&gt;RAM upgrade in transit. 16 GB is anemic for a Skylake-W board, so 4×32 GB RDIMMs are ordered to take the C422 chipset to its quad-channel ceiling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note for anyone copying this verbatim: my production unit since this writeup has swapped the vanilla &lt;code&gt;build/bin/llama-server&lt;/code&gt; for the MTP branch (&lt;code&gt;build-mtp/bin/&lt;/code&gt;) with &lt;code&gt;--spec-type mtp&lt;/code&gt; for speculative decoding, and the bind moved from &lt;code&gt;127.0.0.1&lt;/code&gt; to &lt;code&gt;0.0.0.0&lt;/code&gt; so a separate agent host on the Tailscale mesh can reach it. The recipe above is still the right starting point, and the &lt;a href="https://ianlpaterson.com/blog/3090-ti-qwen-speedup-dflash-mtp/" rel="noopener noreferrer"&gt;companion post on DFlash vs MTP benchmarks&lt;/a&gt; covers the swap in full detail.&lt;/p&gt;

&lt;p&gt;For the "so what do I actually run on this box" question, see the &lt;a href="https://ianlpaterson.com/blog/inference-arbitrage-llm-routing-playbook/" rel="noopener noreferrer"&gt;Inference Arbitrage write-up&lt;/a&gt;, which covers how I route calls across this box, Mac Studio, and cloud frontier models based on task type and cost.&lt;/p&gt;

&lt;p&gt;Companion post: for the speculative decoding benchmarks on this build (DFlash vs MTP, decode rates across output lengths, lossless probe results), see &lt;a href="https://ianlpaterson.com/blog/3090-ti-qwen-speedup-dflash-mtp/" rel="noopener noreferrer"&gt;Three Months of Speed-Up Experiments on a 3090 Ti&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.dell.com/community/en/conversations/precision-fixed-workstations/precision-5820-with-rtx3090-boot-loop/6725279289492753ba20aeea" rel="noopener noreferrer"&gt;Dell Community: Precision 5820 with RTX 3090 boot loop&lt;/a&gt;: unresolved&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dell.com/community/en/conversations/precision-fixed-workstations/anyone-running-a-3090-ti-in-a-5820/647f9f32f4ccf8a8de3f5be3" rel="noopener noreferrer"&gt;Dell Community: Anyone running a 3090 Ti in a 5820&lt;/a&gt;: speculative, no working fix&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dell.com/community/en/conversations/precision-fixed-workstations/dell-precision-5820-tower-boot-loop-when-nvidia-tesla-p100-is-installed/651891ac217515760f3983d1" rel="noopener noreferrer"&gt;Dell Community: 5820 boot loop when Tesla P100 installed&lt;/a&gt;: same pattern, different GPU&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dell.com/support/manuals/en-us/precision-5820-workstation/precision_5820_om_pub/pcie-slots" rel="noopener noreferrer"&gt;Dell Precision 5820 Owner's Manual, PCIe slots&lt;/a&gt;: slot lane assignments&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/keturk/llm_on_rtx_3090" rel="noopener noreferrer"&gt;keturk/llm_on_rtx_3090&lt;/a&gt;: closest competitor (Ubuntu + Docker + Ollama, does not address the boot loop)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;: primary upstream&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>linux</category>
    </item>
    <item>
      <title>Anti-detect browser benchmark 2026: 7 stealth tools, 31 Cloudflare targets, 651 verdicts</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 20:00:02 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/anti-detect-browser-benchmark-2026-7-stealth-tools-31-cloudflare-targets-651-verdicts-4361</link>
      <guid>https://forem.com/ianlpaterson/anti-detect-browser-benchmark-2026-7-stealth-tools-31-cloudflare-targets-651-verdicts-4361</guid>
      <description>&lt;h2&gt;
  
  
  I built a scraper. Cloudflare killed it in 48 hours.
&lt;/h2&gt;

&lt;p&gt;I built a web scraper for Canadian small-cap stock data and Cloudflare blocked it within 48 hours. After testing seven popular stealth-browser libraries against that gate, three times each from a real residential network, only one of the seven got through.&lt;/p&gt;

&lt;p&gt;The one that worked drives Chrome directly, without going through Playwright (the standard Python library for browser automation). Every other browser in the test fails the same gate, regardless of whether it uses Playwright, a patched Chromium fork, a Firefox fork, or raw HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the contenders
&lt;/h2&gt;

&lt;h3&gt;
  
  
  nodriver
&lt;/h3&gt;

&lt;p&gt;nodriver drives system Chrome over a direct WebSocket connection to the browser's DevTools port. There is no Playwright shim in the control plane, no &lt;code&gt;Runtime.enable&lt;/code&gt; call sequence at startup, and no middleware layer between the Python code and the browser process. It is the successor to undetected-chromedriver, from the same author (ultrafunkamsterdam). The key feature in plain terms: removing Playwright from the loop means the browser's automation footprint looks different to a detection gate, because the CDP handshake sequence no longer has Playwright's fingerprint on it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/ultrafunkamsterdam/nodriver&lt;/li&gt;
&lt;li&gt;License: AGPL-3.0 (note: AGPL requires services using nodriver to open-source modifications. Different from undetected-chromedriver's MIT license.)&lt;/li&gt;
&lt;li&gt;Why I tested it: it scored 28 OK / 0 blocked, the only browser with zero blocked cells across all 31 targets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CloakBrowser
&lt;/h3&gt;

&lt;p&gt;CloakBrowser is a patched Chromium fork with, per CloakHQ's documentation, 49 source-level C++ modifications targeting automation signals. It ships its own bundled Chromium build, so the browser binary itself has been modified before launch, not patched via Python hooks at runtime. It has a drop-in Playwright-compatible API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/CloakHQ/CloakBrowser (13.5k stars, as of 2026-05-17)&lt;/li&gt;
&lt;li&gt;License: MIT (Python wrapper); custom CloakBrowser binary license&lt;/li&gt;
&lt;li&gt;Note: the macOS darwin-arm64 build is pinned to Chromium 145 as of 2026-03-04. CloakHQ has shipped 14 Linux/Windows releases since then with no macOS update.&lt;/li&gt;
&lt;li&gt;In this bench because: it is the most-starred free anti-detect browser and makes the strongest marketing claims. Also because &lt;code&gt;@hasantoxr&lt;/code&gt;'s 14/14 claim needed production verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  curl_cffi
&lt;/h3&gt;

&lt;p&gt;curl_cffi is a Python HTTP library that wraps curl-impersonate, which replaces the entire HTTPS client stack with one shaped like a real Chrome installation. It has no JavaScript engine, it is an HTTP-only tool, but the TLS handshake it sends looks indistinguishable from Chrome to a fingerprinting gate. Version 0.15.0 with &lt;code&gt;impersonate="chrome"&lt;/code&gt; (the launch flag that selects which browser shape to imitate) defaults to Chrome 145/146 shape.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/lexiforest/curl_cffi&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;li&gt;In this bench because: it is the correct "raw HTTP floor" baseline, and the interesting question is how many production targets require JavaScript at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Patchright
&lt;/h3&gt;

&lt;p&gt;Patchright is a Playwright fork that patches the CDP-leak signals Playwright exposes during browser startup, specifically the &lt;code&gt;Runtime.enable&lt;/code&gt; and &lt;code&gt;Target.setAutoAttach&lt;/code&gt; call sequences that anti-bot systems can detect in the protocol handshake. It supports &lt;code&gt;channel=chrome&lt;/code&gt;, which tells it to drive the system's installed Google Chrome binary instead of a bundled Chromium, giving it a real Chrome 148 TLS fingerprint and version stamp.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/Kaliiiiiiiiii-Vinyzu/patchright (3.2k stars, as of 2026-05-17)&lt;/li&gt;
&lt;li&gt;License: Apache-2.0&lt;/li&gt;
&lt;li&gt;In this bench because: it is the most actively maintained patched Playwright fork and the most technically credible alternative to vanilla Playwright.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Camoufox
&lt;/h3&gt;

&lt;p&gt;Camoufox is a Firefox fork modified at the C level to spoof fingerprinting APIs (canvas, WebGL, screen geometry, navigator properties) using randomized but internally consistent values. Because it is Firefox-derived, its TLS handshake has a Firefox shape (a different cipher suite order than Chrome), which is detectable but also whitelisted on many targets that block Chrome-shaped automation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/daijro/camoufox (8.4k stars, as of 2026-05-17)&lt;/li&gt;
&lt;li&gt;License: MPL-2.0&lt;/li&gt;
&lt;li&gt;In this bench because: Firefox-derived TLS is a genuinely different attack surface from the Chromium-based tools, and the question of whether that difference helps or hurts on production targets is worth measuring.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  vanilla Playwright
&lt;/h3&gt;

&lt;p&gt;Vanilla Playwright is the baseline. Chromium 147, no stealth patches, Microsoft's official automation library. It does not attempt to hide that it is an automation tool. Its TLS handshake is a Chromium handshake, its CDP startup sequence is stock, and its navigator properties advertise &lt;code&gt;webdriver: true&lt;/code&gt; unless patched.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/microsoft/playwright&lt;/li&gt;
&lt;li&gt;License: Apache-2.0&lt;/li&gt;
&lt;li&gt;Included as: the baseline every other browser has to beat.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  rebrowser-playwright
&lt;/h3&gt;

&lt;p&gt;rebrowser-playwright is a Playwright fork that applies CDP-leak patches similar to Patchright's approach, but with a different patch strategy and different bundled Chromium version (136, which is twelve versions behind Patchright's Chrome 148). The repository's last code commit was September 2024 (the GitHub "pushed_at" of 2025-05-09 reflects a metadata-only touch), so it is effectively unmaintained.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: github.com/rebrowser/rebrowser-playwright&lt;/li&gt;
&lt;li&gt;License: unspecified (no LICENSE file in the repo)&lt;/li&gt;
&lt;li&gt;Reason for inclusion: it is the second major patched Playwright fork. The head-to-head with Patchright and vanilla was the direct question the bench was built to answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;p&gt;Seven browsers, 31 targets across four categories (JS-layer detection panels, TLS fingerprint endpoints, live Cloudflare and other anti-bot production sites, and high-traffic content sites that fingerprint quietly), three independent sweeps from one residential Mac Studio IP across one night. Headed mode, all free or already on hand. Full source and raw records at github.com/ianlpaterson/anti-detect-browser-bench.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which browser wins the bench?
&lt;/h2&gt;

&lt;p&gt;nodriver wins outright with zero blocked targets. Patchright, CloakBrowser, and Camoufox cluster in the middle. Vanilla and rebrowser tie at the bottom. Raw curl_cffi ties CloakBrowser at 26 OK: a 21-line wrapper performs identically to a Chromium fork with 49 source-level C++ patches.&lt;/p&gt;

&lt;p&gt;The signal driving most disagreement is &lt;strong&gt;automation-protocol fingerprinting&lt;/strong&gt; (anti-bot gates checking HOW the browser is being driven, not what the browser claims to be). nodriver wins because it drives Chrome over CDP directly, with no Playwright shim. Playwright leaves protocol-level traces that fingerprint-patch tools do not address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-browser totals (identical across N=3)
&lt;/h2&gt;

&lt;p&gt;651 records (217 cells × 3 runs), zero verdict drift across five hours from one residential IP.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;OK&lt;/th&gt;
&lt;th&gt;Gated&lt;/th&gt;
&lt;th&gt;Blocked&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;nodriver&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Google Chrome 148.0.7778.168 (system browser)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cloak&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Chromium 145.0.7632.109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;curl_baseline&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;curl_cffi 0.15.0 (impersonate=chrome)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;patchright&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Chrome 148.0.7778.168 (channel=chrome)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;camofox&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Firefox 135.0.1-beta.24 (camoufox)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vanilla&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Chromium 147.0.7727.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rebrowser&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Chromium 136.0.7103.25 (rebrowser bundle v1169)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Per-target verdict matrix
&lt;/h2&gt;

&lt;p&gt;Twenty-five of thirty-one targets agree across all seven browsers. The six that disagree are where the signal lives.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;vanilla&lt;/th&gt;
&lt;th&gt;patch&lt;/th&gt;
&lt;th&gt;cloak&lt;/th&gt;
&lt;th&gt;camo&lt;/th&gt;
&lt;th&gt;rebro&lt;/th&gt;
&lt;th&gt;nodrv&lt;/th&gt;
&lt;th&gt;curl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;amazon-product&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;booking-search&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bot-incolumitas&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;browserleaks&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;browserleaks-tls&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;browserscan-bot&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canadianinsider&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ceo-ca&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;creepjs&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;crunchbase-cf&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;devto&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;github-explore&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;glassdoor&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;google-search&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;indeed-jobs&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;instagram-post&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;linkedin-jobs&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newsfilecorp&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nowsecure-cf&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pixelscan-bot&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pixelscan-fp&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rebrowser-detector&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reddit&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sannysoft&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sedarplus&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;td&gt;gated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stackoverflow&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;BLK&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stockwatch&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tiktok-user&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tls-peet&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x-explore&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which six targets disagree across browsers?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;canadianinsider:&lt;/strong&gt; nodriver passes, every other browser hard-blocked. Six Chromium and Firefox stealth approaches fail the same Cloudflare-Turnstile-protected page, while Chrome 148 driven over plain CDP with no Playwright passes. I retried canadianinsider across all three sweeps to confirm nodriver wasn't catching a brief Cloudflare window. The pass reproduced every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;medium:&lt;/strong&gt; nodriver alone passes OK. Patchright/Cloak/Camoufox/curl hit a Cloudflare interstitial, vanilla and rebrowser hard-blocked. Same axis as canadianinsider, softer landing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;glassdoor:&lt;/strong&gt; nodriver gets a soft DataDome challenge, the other six hit a 403.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;google-search:&lt;/strong&gt; Cloak, Camoufox, nodriver, and curl_cffi pass. Patchright, vanilla, and rebrowser hard-block. Patchright joining the unpatched-browsers club is notable: the patches don't address whatever this gate keys on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dev.to:&lt;/strong&gt; six browsers pass, Camoufox alone blocked, consistent with a Firefox TLS quirk the CDN flags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;stackoverflow:&lt;/strong&gt; vanilla and rebrowser hard-blocked, every other browser passes. The clearest single-finding evidence that rebrowser's CDP-leak patches do not change outcomes. rebrowser fails exactly where vanilla fails, on this one cell and across the rest of the matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  rebrowser-playwright vs vanilla Playwright: identical block sets
&lt;/h2&gt;

&lt;p&gt;rebrowser-playwright and vanilla Playwright both score 24 OK, 2 gated, 5 blocked. Block sets identical: canadianinsider, medium, google-search, stackoverflow, glassdoor. On these 31 targets, rebrowser is functionally vanilla.&lt;/p&gt;

&lt;p&gt;stackoverflow is the clearest evidence: vanilla and rebrowser both blocked while every other browser passes. One caveat: rebrowser ships Chromium 136, eleven versions behind vanilla's Chromium 147 and twelve behind Patchright's Chrome 148. Some of the identical-to-vanilla performance is plausibly a version effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patchright vs vanilla Playwright: +1 OK, same google-search blind spot
&lt;/h2&gt;

&lt;p&gt;Patchright is +1 OK and -2 blocked versus vanilla (25 vs 24 OK, 3 vs 5 blocked). The gain comes from stackoverflow. Patchright does not recover canadianinsider or google-search.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;channel=chrome&lt;/code&gt; flag matters as much as the patches: running system Chrome 148 delivers fingerprint protection at a layer no patch can replicate. I expected the patches to be the bigger lever. The matrix showed &lt;code&gt;channel=chrome&lt;/code&gt; is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Camoufox vs Patchright: tied at 25 OK, different failure modes
&lt;/h2&gt;

&lt;p&gt;Camoufox runs Firefox 135.0.1-beta.24. Blocks: canadianinsider (every browser except nodriver fails), sedarplus (every browser fails, F5 BIG-IP ASM), and dev.to (only Camoufox fails, Firefox TLS quirk).&lt;/p&gt;

&lt;p&gt;In the camoufox vs playwright comparison, Camoufox passes google-search where three Chromium-based browsers (including vanilla Playwright) block, and passes medium's gate where vanilla and rebrowser are hard-blocked. The Firefox TLS shape loses on one cell (dev.to) and wins on one (google-search). When I started this bench I assumed Firefox would lose harder than it did on Chromium-shaped gates. It didn't. The matrix driver is automation-protocol fingerprinting, not cipher lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  curl_cffi vs CloakBrowser: identical scoreboards on 31 targets
&lt;/h2&gt;

&lt;p&gt;26 OK, 2 blocked, 3 gated each. They share the same failures (canadianinsider, glassdoor) and the same gates (medium, sedarplus, bot-incolumitas), differing only in which specific cells fall where.&lt;/p&gt;

&lt;p&gt;CloakBrowser is a 130MB Chromium fork with 49 C++ patches. curl_cffi is a 6.4MB Python wheel with a Chrome-shaped HTTPS stack. Same matrix result. I triple-checked the curl_baseline column because it reads like a typo. It isn't. If a 21-line wrapper ties a 130MB patched fork, that fork is paying for something the matrix doesn't measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is CloakBrowser's macOS build stuck on Chromium 145?
&lt;/h2&gt;

&lt;p&gt;The CloakBrowser darwin-arm64 build at version 0.3.28 ships Chromium 145.0.7632.109. CloakHQ's GitHub releases page shows fourteen Linux and Windows releases since then, the most recent at 146.0.7680.177.4 on 2026-04-28. The macOS pipeline has been dead for two months.&lt;/p&gt;

&lt;p&gt;The bench measures what's actually shipping. The old version is the only version macOS users can install. rebrowser's bundled Chromium 136 is the same story: twelve versions behind stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  nodriver vs Playwright: why automation-protocol fingerprinting beats patches
&lt;/h2&gt;

&lt;p&gt;nodriver passes canadianinsider while every patched Chromium fails it. nodriver connects to system Chrome's DevTools port over a plain WebSocket, without Playwright's accessibility layer, without the &lt;code&gt;Runtime.enable&lt;/code&gt; and &lt;code&gt;Target.setAutoAttach&lt;/code&gt; sequence Playwright issues at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why fingerprint patches don't reach the protocol layer
&lt;/h3&gt;

&lt;p&gt;The gate checks the protocol handshake the browser exposes on the way to rendering. Static fingerprints (TLS handshake, JA4 hash, navigator properties, canvas readback) are the wrong surface. CDP through Playwright leaves a recognizable shape. A fingerprint-patch tool can rewrite navigator properties all day without touching this layer.&lt;/p&gt;

&lt;p&gt;canadianinsider, medium, and glassdoor confirm this across three vendors. Cloudflare on canadianinsider doesn't look at the JS-runtime layer because the automation-protocol layer already gave the browser away. nodriver still gets gated on sedarplus, bot-incolumitas, and glassdoor (soft, not hard-blocked).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a residential proxy doesn't help: shape coherence
&lt;/h2&gt;

&lt;p&gt;The gate cross-checks layers for consistency. The Mac Studio is shape-coherent: residential British Columbia IP, macOS Chrome TLS handshake, macOS Chrome JavaScript fingerprints, macOS Chrome HTTP/2 SETTINGS frame ordering (each HTTP/2 client advertises tuning parameters in a predictable order that fingerprints the client library), all from the same host, with no mismatched signal for a gate to flag. A proxy only rewrites the source IP, and everything above TCP still leaks the real client.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anti-bot signal&lt;/th&gt;
&lt;th&gt;Where it actually comes from&lt;/th&gt;
&lt;th&gt;Does a proxy fix it?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IP address (residential vs datacenter ASN)&lt;/td&gt;
&lt;td&gt;TCP source&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS / JA4 handshake fingerprint&lt;/td&gt;
&lt;td&gt;The HTTPS client&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP/2 SETTINGS frame ordering&lt;/td&gt;
&lt;td&gt;The HTTPS client&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;navigator.platform, navigator.userAgent&lt;/td&gt;
&lt;td&gt;The browser process&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canvas / WebGL / audio fingerprints&lt;/td&gt;
&lt;td&gt;Browser process, host GPU, host fonts&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;screen.width, screen.height, devicePixelRatio&lt;/td&gt;
&lt;td&gt;The browser process&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;navigator.connection rtt + effectiveType&lt;/td&gt;
&lt;td&gt;Network conditions&lt;/td&gt;
&lt;td&gt;No (round-trip time gets worse through a proxy)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Linux server behind a residential proxy manufactures a fresh contradiction the gate uses against it.&lt;/p&gt;

&lt;p&gt;For HTTP-only scraping, the TLS layer alone is recoverable with curl_cffi. For JavaScript-rendered scraping, the browser process has to live on the host that owns the residential IP. There is no proxy shortcut that delivers macOS-shape JavaScript fingerprints from a Linux server. I tried a residential proxy in front of a Linux VPS during an earlier sweep, and every Cloudflare-protected target blocked it harder than the unproxied Mac Studio.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the bench classifies each cell
&lt;/h2&gt;

&lt;p&gt;Methodology lives in the bench repo at github.com/ianlpaterson/anti-detect-browser-bench. Highlights: a four-way verdict classifier (ok/gated/blocked/error) checking title regexes, body vendor signatures, and short-body shim pages. Each cell gets up to 3 attempts with early-exit on 2 consecutive matching non-error verdicts. The seven browsers run in randomized order each sweep to prevent reputation accumulation. Phase 6 wall clock was 5h11m across N=3 from 22:39:39 PT through 03:51:17 PT. Peak RSS ranged 57.9MB (curl_baseline) to 13306MB (Patchright). Eighty unit and regression tests cover the classifier, response-frame logic, retry logic, and the curl_baseline parser. The README has the full details.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this bench can't tell you
&lt;/h2&gt;

&lt;p&gt;The matrix is one residential IP, one operating system, thirty-one targets, one night. Patterns that survived three independent runs are stable inside that frame. Outside it, several axes can flip the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rotating proxies change the ranking
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;techinz/browsers-benchmark&lt;/code&gt; repository tests with rotating residential proxies and up to 3 retries per cell. Their headline: Camoufox bypasses 100%, CloakBrowser 83.3% headed and 50.0% headless. The order is reversed from mine. Rotating proxies prevent IP reputation from accumulating against Camoufox's identifiable Firefox fingerprint. My single-IP setup is the worst case for Camoufox in that one respect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Targets shift mid-test
&lt;/h3&gt;

&lt;p&gt;sedarplus.ca swapped anti-bot vendors mid-session during Phase 5: F5 BIG-IP ASM early, Radware Shieldsquare later, gated on every browser by Phase 6. A matrix is a snapshot, not a permanent fact. Cloudflare Turnstile is also site-state-dependent (the v2 stub reported all browsers failed nowsecure-cf, Phase 6 shows all pass).&lt;/p&gt;

&lt;h3&gt;
  
  
  Version effects masquerade as patch quality
&lt;/h3&gt;

&lt;p&gt;Browser versions span Chrome 136 (rebrowser) to Chrome 148 (Patchright via &lt;code&gt;channel=chrome&lt;/code&gt; and nodriver via system Chrome), with Firefox 135 (Camoufox) in the mix. Some apparent stealth is "this build happens to match what users have installed."&lt;/p&gt;

&lt;h3&gt;
  
  
  The durable finding
&lt;/h3&gt;

&lt;p&gt;Automation-protocol fingerprinting is a separate problem from TLS fingerprinting and JS-layer detection. The stealth-browser ecosystem is mostly solving the first two. Defeating the third requires a control plane that is not Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which anti-detect browser should you use?
&lt;/h2&gt;

&lt;p&gt;The most important decision happens before browser choice: identify which layer your target gates on. JS-fingerprint targets are where current Chromium passes unpatched. TLS-fingerprint targets reward Camoufox's Firefox shape and curl_cffi's &lt;code&gt;impersonate=chrome&lt;/code&gt; about equally. Automation-protocol-fingerprinting targets are the cliff: Playwright forks fail regardless of patch quality.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you're scraping...&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare-gated production targets where canadianinsider matters&lt;/td&gt;
&lt;td&gt;nodriver&lt;/td&gt;
&lt;td&gt;The only browser on the matrix with zero blocked cells&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A drop-in Playwright replacement and your stack is locked in&lt;/td&gt;
&lt;td&gt;Patchright with channel=chrome&lt;/td&gt;
&lt;td&gt;Real Chrome 148 + the smallest patch-quality risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sites that whitelist Firefox or fingerprint Chrome shape&lt;/td&gt;
&lt;td&gt;Camoufox&lt;/td&gt;
&lt;td&gt;Firefox 135 stealth, beats Chromium forks on google-search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML you can parse without JavaScript execution&lt;/td&gt;
&lt;td&gt;curl_cffi&lt;/td&gt;
&lt;td&gt;26 of 31 targets in a 21-line wrapper, no browser process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A turnkey patched Chromium and you'll write the cookie layer&lt;/td&gt;
&lt;td&gt;CloakBrowser&lt;/td&gt;
&lt;td&gt;Matches curl_baseline on this matrix, real product, real build chain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;nodriver&lt;/strong&gt; is the only browser through canadianinsider. Tradeoff: its asyncio object model requires an adapter layer in any Playwright codebase. AGPL-3.0 license has commercial implications worth reviewing with counsel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patchright with &lt;code&gt;channel=chrome&lt;/code&gt;&lt;/strong&gt; beats vanilla by one OK and unblocks stackoverflow by running system Chrome 148 instead of bundled Chromium.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camoufox&lt;/strong&gt; beats Chromium forks on google-search and medium's gate but loses dev.to on a Firefox TLS quirk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;curl_cffi&lt;/strong&gt; is &lt;code&gt;pip install curl_cffi&lt;/code&gt; + &lt;code&gt;requests.get(url, impersonate="chrome")&lt;/code&gt;. The TLS handshake defaults to Chrome 145/146 shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloakBrowser&lt;/strong&gt; is a real product with cookie handling and Turnstile auto-resolve worth paying for. Raw stealth headroom over curl_cffi is not supported by the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;rebrowser-playwright&lt;/strong&gt; posts the same OK set as vanilla and has had no real code commits since September 2024. Skip.&lt;/p&gt;

&lt;p&gt;In production, I run nodriver for canadianinsider scraping, Patchright with &lt;code&gt;channel=chrome&lt;/code&gt; everywhere else, and curl_cffi for the targets that don't need JavaScript at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is automation-protocol fingerprinting and how does it differ from JA4 or JS-layer detection?
&lt;/h3&gt;

&lt;p&gt;JA4 hashes the TLS handshake. JS-layer detection inspects navigator properties after the page loads. Automation-protocol fingerprinting sits between them, detecting the protocol shape used to drive the browser. Fingerprint patches do not reach this layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does nodriver pass canadianinsider when every patched Chromium fails?
&lt;/h3&gt;

&lt;p&gt;nodriver drives system Chrome over a plain CDP connection with no Playwright in the loop, which removes the protocol-handshake shape the gate keys on at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I just use a residential proxy with my existing Playwright on a VPS?
&lt;/h3&gt;

&lt;p&gt;No. A proxy rewrites only the IP layer. TLS handshake, HTTP/2 frames, navigator properties, and canvas fingerprints all originate from the actual host. A Linux server behind a residential proxy still advertises a Linux-shape browser, which is the contradiction gates flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is CloakBrowser actually no better than curl?
&lt;/h3&gt;

&lt;p&gt;On 31 targets, OK counts are identical (26 each). CloakBrowser has features the matrix does not test: cookie handling, Turnstile auto-resolve, human-cursor modeling. For HTML parsing, curl_cffi is sufficient. For persistent browser sessions, Cloak is the better tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Did Cloudflare Turnstile pass for every browser? The v2 stub said no browser passed.
&lt;/h3&gt;

&lt;p&gt;Yes, every browser passed nowsecure-cf in Phase 6. The v2 stub measured the same target two weeks earlier and got the opposite result. Turnstile is site-state-dependent. Treat any single-session result as transient.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about rebrowser passing JS-layer detection panels its README highlights?
&lt;/h3&gt;

&lt;p&gt;rebrowser passes browserscan-bot, pixelscan-bot, and rebrowser-detector. So does every other browser including vanilla. Those panels are saturated by current Chromium. The disagreement happens on production targets, where rebrowser fails identically to vanilla.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I run rotating residential proxies?
&lt;/h3&gt;

&lt;p&gt;If your target list overlaps with mine, yes. Camoufox's Firefox TLS fingerprint is identifiable on repeated single-IP hits, and rotation solves that. Proxies are a larger lever than browser choice for sustained workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why didn't you include real Mozilla Firefox?
&lt;/h3&gt;

&lt;p&gt;Manual Firefox 150 on canadianinsider passed Cloudflare Turnstile from a fresh private window. Selenium-driving the same Firefox with &lt;code&gt;dom.webdriver.enabled=false&lt;/code&gt; got blocked. The gate keys on detectable automation, not Firefox itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between Patchright and vanilla Playwright?
&lt;/h3&gt;

&lt;p&gt;Patchright patches CDP-leak signals at startup and supports &lt;code&gt;channel=chrome&lt;/code&gt; to drive system Chrome 148. On Phase 6 it scores +1 OK over vanilla (25 vs 24) and unblocks stackoverflow. The version advantage does as much work as the patches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does curl_cffi bypass Cloudflare?
&lt;/h3&gt;

&lt;p&gt;Sometimes. On Phase 6 it passed 26 of 31 targets by replacing the HTTPS client stack with one shaped like current Chrome. It fails on canadianinsider, glassdoor, and any target requiring JavaScript execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is JA4 fingerprinting?
&lt;/h3&gt;

&lt;p&gt;JA4 hashes the TLS handshake: cipher suites, extensions, ALPN, and their order. Anti-bot services match that hash against known browser profiles. A headless-Chrome JA4 differs measurably from a real-Chrome JA4.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Cloudflare detect headless browsers?
&lt;/h3&gt;

&lt;p&gt;Cloudflare layers signals: TLS handshake (JA3/JA4), HTTP/2 SETTINGS frame ordering, JavaScript runtime properties, behavioral patterns, and automation-protocol shape. The Phase 6 matrix shows automation-protocol shape is the layer most patched browsers ignore.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is nodriver safe to use in production?
&lt;/h3&gt;

&lt;p&gt;Functionally yes. nodriver is actively maintained and scored 28 of 31 with zero blocked cells. Caveats: its asyncio object model means no Playwright drop-in, cookies carry across targets by default, and AGPL-3.0 has commercial-license implications worth checking with counsel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Patchright work with Cloudflare Turnstile?
&lt;/h3&gt;

&lt;p&gt;Patchright passes some Cloudflare-gated targets but not canadianinsider or google-search in Phase 6 testing. It scores +1 OK over vanilla Playwright by recovering stackoverflow, but the CDP-leak patches do not address the automation-protocol layer that Cloudflare Turnstile keys on for its hardest challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I install curl_cffi?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;pip install curl_cffi&lt;/code&gt;. Then &lt;code&gt;from curl_cffi import requests&lt;/code&gt; and &lt;code&gt;requests.get(url, impersonate="chrome")&lt;/code&gt;. The &lt;code&gt;impersonate="chrome"&lt;/code&gt; flag selects Chrome 145/146 TLS shape (curl_cffi 0.15.0). No browser binary required. The full library is 6.4MB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is nodriver a drop-in replacement for Playwright?
&lt;/h3&gt;

&lt;p&gt;No. nodriver uses an asyncio object model incompatible with Playwright's sync API and page/context/browser hierarchy. Migrating requires rewriting control flow, not just swapping the import. The payoff is zero Playwright in the protocol stack, which is what gets through canadianinsider and similar Cloudflare-Turnstile-protected targets.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools mentioned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;nodriver&lt;/strong&gt; (&lt;a href="https://github.com/ultrafunkamsterdam/nodriver" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - Direct-CDP successor to undetected-chromedriver. No Playwright shim in the control plane. License: AGPL-3.0. Pricing: free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloakBrowser&lt;/strong&gt; (&lt;a href="https://github.com/CloakHQ/CloakBrowser" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - Patched Chromium fork with 49 source-level C++ fingerprint modifications. Drop-in Playwright API. License: MIT (wrapper), custom binary license. Pricing: free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;curl_cffi&lt;/strong&gt; (&lt;a href="https://curl-cffi.readthedocs.io/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; | &lt;a href="https://github.com/lexiforest/curl_cffi" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - Python HTTP client wrapping curl-impersonate. Spoofs TLS/JA4 fingerprints without a JS engine. License: MIT. Pricing: free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patchright&lt;/strong&gt; (&lt;a href="https://github.com/Kaliiiiiiiiii-Vinyzu/patchright" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - Playwright fork that patches CDP-leak signals at startup. Supports &lt;code&gt;channel=chrome&lt;/code&gt; for real Chrome TLS. License: Apache-2.0. Pricing: free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camoufox&lt;/strong&gt; (&lt;a href="https://camoufox.com" rel="noopener noreferrer"&gt;site&lt;/a&gt; | &lt;a href="https://github.com/daijro/camoufox" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - Firefox fork with C-level canvas/WebGL/navigator spoofing. Firefox TLS shape. License: MPL-2.0. Pricing: free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright&lt;/strong&gt; (&lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;site&lt;/a&gt; | &lt;a href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - Microsoft's reference browser automation library. The unpatched baseline. License: Apache-2.0. Pricing: free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rebrowser-playwright&lt;/strong&gt; (&lt;a href="https://github.com/rebrowser/rebrowser-playwright" rel="noopener noreferrer"&gt;github&lt;/a&gt;) - CDP-patch Playwright fork. Last code commit September 2024, Chromium 136. License: unspecified. Pricing: free.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Three Months of Speed-Up Experiments on a 3090 Ti: Autoregressive DFlash MTP for Qwen3.6-27B</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 19:59:51 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/three-months-of-speed-up-experiments-on-a-3090-ti-autoregressive-dflash-mtp-for-qwen36-27b-59ef</link>
      <guid>https://forem.com/ianlpaterson/three-months-of-speed-up-experiments-on-a-3090-ti-autoregressive-dflash-mtp-for-qwen36-27b-59ef</guid>
      <description>&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The starting line was 43 tokens per second decode on vanilla llama.cpp. The finishing line, three months later, is 39 to 49 tokens per second decode that doesn't collapse at long context, using a completely different speculative decoding technique than the one Claude and Ian started with. This box runs Qwen3.6-27B Q4_K_M on a single RTX 3090 Ti (&lt;a href="https://ianlpaterson.com/blog/llama-cpp-3090-ti-dell-t5820/" rel="noopener noreferrer"&gt;the T5820 build notes are here&lt;/a&gt;), serving an agent stack (Hermes/k2) over the OpenAI-compatible llama.cpp HTTP API.&lt;/p&gt;

&lt;p&gt;The full audit trail is below: autoregressive baseline, DFlash via the BeeLlama fork, then MTP via vanilla llama.cpp once the workload reality caught up to the bench. Every knob got measured, most got rejected, and the production state at the end is simpler than what it replaced.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Terms used:&lt;/strong&gt; &lt;em&gt;Autoregressive&lt;/em&gt; = baseline generation, one token at a time, no speculation. &lt;em&gt;Drafter&lt;/em&gt; = small model that proposes tokens for the target to verify. &lt;em&gt;KV cache&lt;/em&gt; = stored key/value pairs from previous tokens so attention doesn't recompute every step. &lt;em&gt;Prefill&lt;/em&gt; = the model reading the prompt before generating. &lt;em&gt;Decode&lt;/em&gt; = generating tokens after prefill. &lt;em&gt;TTFT&lt;/em&gt; = time to first token. &lt;em&gt;MTP&lt;/em&gt; = multi-token prediction (extra head layers on the target that predict several tokens in parallel).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's running in prod right now
&lt;/h2&gt;

&lt;p&gt;End state as of 2026-05-15:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binary:&lt;/strong&gt; vanilla llama.cpp, &lt;code&gt;build-mtp/bin/llama-server&lt;/code&gt;, built from the MTP PR branch (commit &lt;code&gt;ebe4fca&lt;/code&gt;, &lt;a href="https://github.com/ggml-org/llama.cpp/pull/22673" rel="noopener noreferrer"&gt;PR #22673&lt;/a&gt;). PR #22673 merged to &lt;code&gt;master&lt;/code&gt; on 2026-05-16, so any &lt;code&gt;master&lt;/code&gt; checkout after that date ships &lt;code&gt;--spec-type mtp&lt;/code&gt; natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;code&gt;Qwen3.6-27B-Q4_K_M-mtp.gguf&lt;/code&gt; from &lt;a href="https://huggingface.co/unsloth/Qwen3.6-27B-MTP-GGUF" rel="noopener noreferrer"&gt;&lt;code&gt;unsloth/Qwen3.6-27B-MTP-GGUF&lt;/code&gt;&lt;/a&gt; (MTP heads baked into the weights, no separate drafter file).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flags:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--spec-type&lt;/span&gt; mtp &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--spec-draft-n-max&lt;/span&gt; 6 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--spec-draft-p-min&lt;/span&gt; 0.75 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--reasoning-budget&lt;/span&gt; 256 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt; 131072 &lt;span class="nt"&gt;-fa&lt;/span&gt; on &lt;span class="nt"&gt;-ctk&lt;/span&gt; q4_0 &lt;span class="nt"&gt;-ctv&lt;/span&gt; q4_0 &lt;span class="nt"&gt;--jinja&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--alias&lt;/span&gt; qwen3.6-27b-q4_k_m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VRAM:&lt;/strong&gt; ~20.4 to 20.9 GiB depending on measurement state (idle vs under light load on a 24 GiB card).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decode rate:&lt;/strong&gt; 39 to 49 tok/s across tested output lengths (100, 500, 1000, 2000 tokens), low end at out=500, high end at out=2000. Plain autoregressive's flat ~29 tok/s plus its faster prefill still beats MTP on wall clock below output ~900 tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wall time at output 2000:&lt;/strong&gt; 91 seconds, vs 112 seconds on DFlash and 107 on plain autoregressive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reversibility:&lt;/strong&gt; previous-state backups under &lt;code&gt;~/.config/systemd/user/llama-server.service.pre-*&lt;/code&gt;. Each swap in the table below is one &lt;code&gt;cp&lt;/code&gt; away from a revert.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hermes hits the same alias it always did. Zero client changes through five binary swaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MTP wins on wall clock above output ~900 tokens.&lt;/strong&gt; Below that, plain autoregressive is faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The DFlash Decode Collapse.&lt;/strong&gt; DFlash decode drops from 46.9 to 30.1 tok/s as output grows from 100 to 2000 tokens. MTP holds 39 to 49 tok/s flat across the same range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speculative decoding is NOT bit-for-bit lossless at temp=0 on free-form prose.&lt;/strong&gt; Tool-call schema integrity is preserved identically. True for both DFlash and MTP. Backed empirically (the lossless probe, N=3 per workload) and academically (&lt;a href="https://arxiv.org/abs/2605.09992" rel="noopener noreferrer"&gt;arxiv:2605.09992&lt;/a&gt; on drafter attention drift).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--spec-draft-p-min 0.75&lt;/code&gt; is the vanilla llama.cpp flag&lt;/strong&gt; that changed the MTP verdict from "buried at Phase 2" to "shipped at Phase 9." Filter lives in &lt;a href="https://github.com/ggml-org/llama.cpp/pull/22397" rel="noopener noreferrer"&gt;PR #22397&lt;/a&gt; (April 28).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--reasoning-budget 256&lt;/code&gt;&lt;/strong&gt; saves ~10 seconds per request on Qwen3.6 with no quality regression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-prompt vendor benchmarks overstate DFlash gains by 30 to 60%.&lt;/strong&gt; Distribution evidence (N=10+) is non-optional. N=1 misleads even at the right prompt size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache reuse beats every decode optimization&lt;/strong&gt; when it hits. ~60x prefill speedup on realistic varying traffic with a stable prefix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR #22673 (MTP) merged 2026-05-16.&lt;/strong&gt; Builds from &lt;code&gt;master&lt;/code&gt; after that date have &lt;code&gt;--spec-type mtp&lt;/code&gt; natively.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What got tested
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Knob&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Bigbatch (&lt;code&gt;-ub 256 -b 2048&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;+19% decode, +10% prompt, +186 MiB VRAM&lt;/td&gt;
&lt;td&gt;✅ kept&lt;/td&gt;
&lt;td&gt;Free win on prompt-eval kernels. Carried into every later config.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;DFlash (BeeLlama fork, default &lt;code&gt;n_max=16&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;43 → 148 tok/s on linked-list code&lt;/td&gt;
&lt;td&gt;✅ shipped 2026-05-12&lt;/td&gt;
&lt;td&gt;First big swap. Same OpenAI/jinja API surface, same flags Hermes needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;TurboQuant KV (&lt;code&gt;turbo4&lt;/code&gt; K + &lt;code&gt;turbo3_tcq&lt;/code&gt; V)&lt;/td&gt;
&lt;td&gt;Decode parity (178 vs 176), &lt;strong&gt;prompt -37% on sm_86&lt;/strong&gt; (Ampere, RTX 3090/3090 Ti)&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;Cross-stack corroboration: Red Hat AI researchers (Kurtić, Goin, Marques) reach the same verdict on H100 in &lt;a href="https://vllm.ai/blog/turboquant" rel="noopener noreferrer"&gt;the TurboQuant writeup on the vLLM blog&lt;/a&gt;. Hopper FP8 path not available on Ampere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DDTree branch verify (&lt;code&gt;--spec-branch-budget 22&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;-49% to -58% decode across workloads&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;Anbeeld (BeeLlama maintainer) flags DDTree as "very much work in progress" in the README. Confirmed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;enable_thinking:false&lt;/code&gt; server-wide&lt;/td&gt;
&lt;td&gt;+30% peak decode&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;Hermes/k2 hallucinated within minutes. Qwen3.6 reasoning is load-bearing. Reverted within ~15 min.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--spec-draft-n-max&lt;/code&gt; sweep (4 to 16)&lt;/td&gt;
&lt;td&gt;12 wins by a hair; surface flat ±5% across 10-14&lt;/td&gt;
&lt;td&gt;✅ kept 12&lt;/td&gt;
&lt;td&gt;Initial sharp-peak finding flattened on the N=10 sweep. Same prod number, more nuance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Q8_0 drafter (1.77 GB)&lt;/td&gt;
&lt;td&gt;Tied Q4_K_M at N=10&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;N=3 looked like a +10% surprise; N=10 collapsed it to noise.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Q5_K_S target (18 GB) + Q4_K_M drafter&lt;/td&gt;
&lt;td&gt;+5% code, &lt;strong&gt;-10% chat&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;"Precision combo" wins on pure code. Hermes traffic is reasoning + chat heavy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;q8_0 KV cache (instead of q4_0)&lt;/td&gt;
&lt;td&gt;-25% throughput; VRAM +1.8 GB&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;Re-confirmed pre-DFlash lesson under DFlash. Workload-consistent penalty.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;CopySpec (suffix matching, no drafter)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&amp;gt; 300x slowdown&lt;/strong&gt;: 150-token prompt timed out at 600s&lt;/td&gt;
&lt;td&gt;❌ rejected&lt;/td&gt;
&lt;td&gt;The drafter is load-bearing: on workloads without repetitive structure, CopySpec timed out in 600 seconds. The drafter is the entire performance story for speculative decoding on such workloads.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;MTP, first pass (&lt;code&gt;n_max=3&lt;/code&gt;, no &lt;code&gt;p_min&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1.4x autoregressive; ~2x slower than DFlash on long workloads&lt;/td&gt;
&lt;td&gt;❌ buried (Phase 2)&lt;/td&gt;
&lt;td&gt;Tested at short context with original flags. Same prose drift profile as DFlash. Looked dead. Wasn't.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MTP, second pass (&lt;code&gt;n_max=6 --spec-draft-p-min 0.75&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.8x autoregressive; &lt;strong&gt;decode doesn't collapse at long context&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;✅ shipped 2026-05-15&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;--spec-draft-p-min&lt;/code&gt; filter (vanilla llama.cpp, &lt;a href="https://github.com/ggml-org/llama.cpp/pull/22397" rel="noopener noreferrer"&gt;PR #22397&lt;/a&gt;, April 28) changed the verdict. Decode holds 39 to 49 tok/s across every output length while DFlash drops 47 → 30 tok/s.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--reasoning-budget 256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Saves ~10 seconds per request, no quality regression&lt;/td&gt;
&lt;td&gt;✅ shipped 2026-05-15&lt;/td&gt;
&lt;td&gt;Caps runaway reasoning chains at 256 tokens. Highest-impact, lowest-risk flag in the sweep.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;End-state decode rates:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;output_len&lt;/th&gt;
&lt;th&gt;Autoregressive tok/s&lt;/th&gt;
&lt;th&gt;DFlash tok/s&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;MTP (prod) tok/s&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Wall-clock winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;28.9&lt;/td&gt;
&lt;td&gt;46.9&lt;/td&gt;
&lt;td&gt;44.6&lt;/td&gt;
&lt;td&gt;Autoregressive (prefill dominates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;29.1&lt;/td&gt;
&lt;td&gt;37.0&lt;/td&gt;
&lt;td&gt;39.1&lt;/td&gt;
&lt;td&gt;Autoregressive by 4-7s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;29.1&lt;/td&gt;
&lt;td&gt;30.2&lt;/td&gt;
&lt;td&gt;44.4&lt;/td&gt;
&lt;td&gt;MTP/autoregressive tied within 86ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;29.0&lt;/td&gt;
&lt;td&gt;30.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MTP by 16-21s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What does "speed up a local LLM" actually mean
&lt;/h2&gt;

&lt;p&gt;Three numbers that decouple at production scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decode rate (tok/s):&lt;/strong&gt; how fast tokens come out once generation starts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTFT (time-to-first-token):&lt;/strong&gt; how long until the first visible character appears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wall clock (TTFT + decode × length):&lt;/strong&gt; what users actually feel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most speculative decoding marketing optimizes the first number. Production users feel the third. At 43K of context (Hermes-shape traffic, N=10 sampling), prefill is roughly 80% of wall clock, reasoning is ~12%, and decode is the remaining ~8%. A 2x decode improvement doesn't double the wall clock. It nudges the smallest of three timescales.&lt;/p&gt;

&lt;p&gt;Three months of decode tuning got production from 43 to 124 tok/s on a synthetic linked-list benchmark, then a single Hermes-shape bench at 43K context showed the gains evaporating at the real workload. The fix was changing the speculative decoding technique to one that doesn't collapse at long context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plain autoregressive: the baseline
&lt;/h2&gt;

&lt;p&gt;Plain autoregressive means generating one token at a time with no speculation, no drafter, no MTP heads. It's the reference every measurement here compares against, and it's a live competitor: on certain workloads it still wins on wall clock.&lt;/p&gt;

&lt;p&gt;On Qwen3.6-27B Q4_K_M with &lt;code&gt;-c 131072 -fa on -ctk q4_0 -ctv q4_0&lt;/code&gt;, plain autoregressive decodes at ~29 tok/s and stays there regardless of output length. It has the fastest prefill of the three modes (~37.5s at 43K context, vs MTP's ~49s and DFlash's ~44s). No drafter KV cache means no bandwidth contention and no collapse at long output. It also never speeds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  How DFlash got here (2026-05-12)
&lt;/h2&gt;

&lt;p&gt;The Dell T5820 install was the hardware story (companion post forthcoming). DFlash was the software follow-up. Initial scan of &lt;a href="https://github.com/Luce-Org/lucebox-hub" rel="noopener noreferrer"&gt;Luce-Org/lucebox-hub&lt;/a&gt; (advertising 3.43x decode + 10x TTFT on RTX 3090) ran into the same blocker: their daemon is a raw generate primitive with no OpenAI API, no jinja chat templates, no tool calling. Slotting it behind Hermes/k2 would need a chat-template shim written from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Anbeeld/beellama.cpp" rel="noopener noreferrer"&gt;BeeLlama.cpp&lt;/a&gt; by Anbeeld already had the shim baked in: DFlash speculative decoding, TurboQuant KV cache, and CopySpec fallback layered onto the OpenAI server with &lt;code&gt;--jinja&lt;/code&gt; and tool-call detection preserved. Different binary. Same flags Hermes needed.&lt;/p&gt;

&lt;p&gt;The clean A/B: same Qwen3.6-27B Q4_K_M target, same KV quant, same &lt;code&gt;-c 131072&lt;/code&gt;, same &lt;code&gt;-fa on&lt;/code&gt;. Same workload (1200-token Python linked-list class, temperature 0, seed 42). BeeLlama with &lt;code&gt;--spec-type dflash&lt;/code&gt; vs the same BeeLlama with no &lt;code&gt;--spec-*&lt;/code&gt; flags. DFlash was the only variable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Decode tok/s&lt;/th&gt;
&lt;th&gt;Prompt tok/s&lt;/th&gt;
&lt;th&gt;VRAM MiB&lt;/th&gt;
&lt;th&gt;vs Autoregressive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Autoregressive baseline&lt;/td&gt;
&lt;td&gt;43.15&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;td&gt;18634&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DFlash, q4_0 KV&lt;/td&gt;
&lt;td&gt;148.46&lt;/td&gt;
&lt;td&gt;189&lt;/td&gt;
&lt;td&gt;19760&lt;/td&gt;
&lt;td&gt;3.44x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DFlash + bigbatch, thinking ON&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~124&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~210&lt;/td&gt;
&lt;td&gt;20372&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.88x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DFlash + bigbatch, thinking OFF (peak)&lt;/td&gt;
&lt;td&gt;176.02&lt;/td&gt;
&lt;td&gt;209&lt;/td&gt;
&lt;td&gt;19946&lt;/td&gt;
&lt;td&gt;4.08x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headline is the third row. Server-wide &lt;code&gt;enable_thinking:false&lt;/code&gt; was tested, ran for ~15 minutes in production, and reverted because the model started narrating work it never did ("running on a Pi, give it a second" on a 3090 Ti) and made up status messages. Qwen3.6 is a reasoning model. Server-wide thinking-off tanks output quality across the agent stack, and reasoning back on costs ~30% of the peak.&lt;/p&gt;

&lt;p&gt;Tool calling stayed intact through the swap. Standard OpenAI-shape &lt;code&gt;tools&lt;/code&gt; array request came back with &lt;code&gt;finish_reason: "tool_calls"&lt;/code&gt; and a clean &lt;code&gt;tool_calls&lt;/code&gt; array. No shim needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The drafter knobs
&lt;/h2&gt;

&lt;p&gt;DFlash's default &lt;code&gt;--spec-draft-n-max&lt;/code&gt; is 16: the drafter guesses up to 16 tokens per round, the target verifies them all at once. Anything the drafter got right is free; anything past the first wrong guess is wasted compute. A sweep across 4, 8, 12, 16 (plus a fine pass at 10, 11, 13, 14 a week later) put the optimum at &lt;strong&gt;12 on this workload&lt;/strong&gt; , a wide valley flat to ±5% across n_max 10-14.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;latency&lt;/th&gt;
&lt;th&gt;tool-call&lt;/th&gt;
&lt;th&gt;chat-short&lt;/th&gt;
&lt;th&gt;code-long&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;prod (n_max=16, cross=1024, adaptive ON)&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nmax-8 noadapt&lt;/td&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;nmax-12 noadapt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;111&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;73&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nmax-16 noadapt&lt;/td&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;crossctx-2048&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;157&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The chat-short bump (+24% decode, -27% wall clock) came from making each wrong guess cheaper. With &lt;code&gt;n_max=16&lt;/code&gt;, every rejected draft on &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; content burned 12-15 wasted tokens. At &lt;code&gt;n_max=12&lt;/code&gt;, the same rejections cost 8-11 tokens. Multiplied across thousands of speculation cycles per response, that's the 24%.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lossless probe
&lt;/h2&gt;

&lt;p&gt;The textbook claim: speculative decoding with rejection sampling (the mechanism that accepts draft tokens matching the target's distribution and corrects ones that don't) at temperature 0 produces output bit-for-bit identical to autoregressive. At temp=0 there's no random sampling, so every token should land on the target model's argmax. Pure speed optimization, zero quality impact.&lt;/p&gt;

&lt;p&gt;Nobody on either side of the Qwen 3.6 and BeeLlama conversation had published a measurement, so Claude and Ian ran one: same target, temp=0, fixed seed 42, five deterministic prompts. We cached the autoregressive baseline, then ran DFlash against it with a character-level Levenshtein diff.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Identical to autoregressive?&lt;/th&gt;
&lt;th&gt;Median char drift&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;latency (single digit "4")&lt;/td&gt;
&lt;td&gt;YES (3/3)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Trivial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool-call (&lt;code&gt;get_weather&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;YES (3/3)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Schema + args match exactly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chat-short (TCP handshake, ~1300 chars)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NO (0/3)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~86% of reference length. Semantically similar, textually distinct.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;code-long (Python class, ~2600 chars)&lt;/td&gt;
&lt;td&gt;NO (0/3)&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;~3-6% drift. Variable names + docstrings varied.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lossless held narrowly for short deterministic answers and structured outputs like tool calls. It broke for sustained prose past a few hundred tokens. Probable cause: drafter distributional drift. The DFlash drafter is not an exact match for the target's logit distribution, so at rejection-sampling boundaries where two tokens sit at near-equal probability, even small drafter drift flips the accepted token. One flipped token branches into a different sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agentic-stack consequence:&lt;/strong&gt; tool-call schema integrity is preserved (&lt;code&gt;tool_call_schema_match_all = true&lt;/code&gt; across all iterations). Function names, argument keys, JSON shape all stay identical run-to-run. Free-form chat text varies at temp=0, the same way any non-deterministic backend would.&lt;/p&gt;

&lt;p&gt;The MTP head-to-head (Phase 2) ran the same probe a week later and got the &lt;strong&gt;same drift profile&lt;/strong&gt; : ~1000 chars on chat-short, lossless on tool calls. Two implementations, same theoretical guarantee failing the same way. Academic backing arrived at the right time: &lt;a href="https://arxiv.org/abs/2605.09992" rel="noopener noreferrer"&gt;arxiv:2605.09992 "Attention Drift in Autoregressive Speculative Decoding Drafters"&lt;/a&gt; measured the same phenomenon at the model-internal level. Our Levenshtein probe and their attention-pattern analysis are pointing at the same thing from opposite ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: what lost
&lt;/h2&gt;

&lt;p&gt;Before committing to MTP testing, four more configs against the n_max=12 baseline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;latency&lt;/th&gt;
&lt;th&gt;tool-call&lt;/th&gt;
&lt;th&gt;chat-short&lt;/th&gt;
&lt;th&gt;code-long&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;prod (nmax=12)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;105.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;69.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;131.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nmax-4&lt;/td&gt;
&lt;td&gt;77.6 (-26%)&lt;/td&gt;
&lt;td&gt;64.9 (-16%)&lt;/td&gt;
&lt;td&gt;58.7 (-16%)&lt;/td&gt;
&lt;td&gt;82.4 (-37%)&lt;/td&gt;
&lt;td&gt;Regression on every workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nmax-8&lt;/td&gt;
&lt;td&gt;105.7 (tie)&lt;/td&gt;
&lt;td&gt;74.7 (-3%)&lt;/td&gt;
&lt;td&gt;63.2 (-9%)&lt;/td&gt;
&lt;td&gt;124.4 (-5%)&lt;/td&gt;
&lt;td&gt;Strictly worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q8-kv (nmax=12)&lt;/td&gt;
&lt;td&gt;97.3 (-8%)&lt;/td&gt;
&lt;td&gt;68.0 (-12%)&lt;/td&gt;
&lt;td&gt;63.9 (-8%)&lt;/td&gt;
&lt;td&gt;100.2 (-24%)&lt;/td&gt;
&lt;td&gt;25% penalty confirmed, VRAM +1.8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;copyspec&lt;/td&gt;
&lt;td&gt;TIMED OUT at &amp;gt;600s&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Catastrophic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The Unsloth MTP configuration guide's recommendation of n_max=2 does not transfer to DFlash.&lt;/strong&gt; The nmax curve is monotonically worse going smaller. MTP and DFlash are different techniques with different optimal draft windows. &lt;strong&gt;CopySpec without a drafter is a 300x slowdown, not a floor.&lt;/strong&gt; BeeLlama's README describes it as model-free suffix matching. On the 150-token latency prompt it didn't complete in 600 seconds. The drafter is load-bearing; the drafter is the entire performance story for speculative decoding on workloads with no repetitive structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: MTP buried
&lt;/h2&gt;

&lt;p&gt;Multi-token prediction in vanilla llama.cpp (PR #22673, am17an) predicts multiple target-model tokens in parallel without a separate draft model. Simpler architecture, smaller VRAM footprint, comparable advertised speedup.&lt;/p&gt;

&lt;p&gt;The first MTP test ran at short context with &lt;code&gt;--spec-draft-n-max 3&lt;/code&gt; (no &lt;code&gt;--spec-draft-p-min&lt;/code&gt; flag existed yet). Same target weights as DFlash, same q4_0 KV, reasoning ON. Three findings: MTP wasn't lossless on prose either (~1000 chars drift on chat-short, same magnitude as DFlash); MTP ran ~1.3-1.4x slower than DFlash on matched chat-short workloads (52-57 tok/s vs ~73 tok/s); MTP preserved tool-call schema integrity (safe for Hermes).&lt;/p&gt;

&lt;p&gt;Conclusion at the time: same drift profile on prose, lower throughput on matched workloads, no operational win. DFlash stays. Phase 2 looked dead. It was on the wrong settings, at the wrong context size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 6: prefill is most of TTFT
&lt;/h2&gt;

&lt;p&gt;Production was running DFlash + bigbatch + nmax=12. Hermes felt slow. The decode bench said 70 tok/s on chat-short, which should have been ~10 seconds wall-clock on a typical answer. Real Hermes traffic was hitting 12-32 second TTFT. The numbers didn't add up.&lt;/p&gt;

&lt;p&gt;So Ian and Claude ran one bench at the actual Hermes workload shape: ~43K context (system message + multi-turn history + tools array), reasoning ON, then sent the same body twice in a row.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Iter 1 (cold)&lt;/th&gt;
&lt;th&gt;Iter 2 (warm, identical body)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wall TTFT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.48s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.32s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server &lt;code&gt;prompt_ms&lt;/code&gt; (prefill)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.90s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.24s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens evaluated (&lt;code&gt;prompt_n&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;43,241&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens reused (&lt;code&gt;cache_n&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43,237&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode rate&lt;/td&gt;
&lt;td&gt;33.9 tok/s&lt;/td&gt;
&lt;td&gt;43.2 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Cold prefill was most of TTFT.&lt;/strong&gt; The model spent 46.9 seconds reading the prompt before generating anything. The whole decode-throughput investigation had been tuning the smallest slice of the wall clock. &lt;strong&gt;Cache reuse was the entire game when it worked&lt;/strong&gt; : second request: 43,237 of 43,241 tokens reused, prefill dropped to 0.24s, ~200x speedup on the smallest slice that suddenly mattered.&lt;/p&gt;

&lt;p&gt;Phase 7 immediately re-validated at N=10-20 and corrected the headlines: the 200x cache speedup required byte-identical bodies, realistic Hermes traffic (varying user message turn-to-turn) gets closer to 60x. The "96.7% prefill share of TTFT" was a high-tail observation; at N=10 the median is 88%. Reasoning budget effect, originally measured as &amp;lt;5%, was actually 20.7% with proper sampling. Three Phase 6 numbers off by 1.5x to 4x from a single observation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 7: DFlash hurts prefill
&lt;/h2&gt;

&lt;p&gt;The Phase 6 framing was "speculative decoding optimizes the wrong axis." It implicitly assumed DFlash was neutral on prefill. Phase 7 actually measured it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Autoregressive (N=10)&lt;/th&gt;
&lt;th&gt;DFlash (N=10)&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prefill at 43K context&lt;/td&gt;
&lt;td&gt;37,498 ms&lt;/td&gt;
&lt;td&gt;44,153 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DFlash is 17.8% SLOWER&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode at 43K context (out=100)&lt;/td&gt;
&lt;td&gt;29.3 tok/s&lt;/td&gt;
&lt;td&gt;38.4 tok/s&lt;/td&gt;
&lt;td&gt;DFlash +30%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DFlash trades prefill speed for decode speed: the drafter prefills alongside the target, adding 17.8% wall time at 43K context. On a TTFT-dominated workload (large system message, mostly tool calls + short replies, which is roughly what Hermes runs), DFlash was making user-felt latency worse, not better. Production was about to swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 8: the DFlash Decode Collapse
&lt;/h2&gt;

&lt;p&gt;Two sweeps, four output lengths each at N=3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The DFlash Decode Collapse:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;output_len&lt;/th&gt;
&lt;th&gt;Autoregressive wall (ms)&lt;/th&gt;
&lt;th&gt;DFlash wall (ms)&lt;/th&gt;
&lt;th&gt;DFlash decode tok/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;41,888&lt;/td&gt;
&lt;td&gt;48,394&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;55,785&lt;/td&gt;
&lt;td&gt;59,995&lt;/td&gt;
&lt;td&gt;37.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;72,966&lt;/td&gt;
&lt;td&gt;79,528&lt;/td&gt;
&lt;td&gt;30.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;107,476&lt;/td&gt;
&lt;td&gt;112,567&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DFlash decode at out=100 is 46.9 tok/s. At out=2000 it's 30.1, basically autoregressive speed. The drafter's KV cache grows alongside the target's, the small drafter is more bandwidth-bound, and the speedup erodes as the conversation gets longer. By out=2000, DFlash is paying its 6-7 second prefill tax for no decode benefit. &lt;a href="https://arxiv.org/abs/2604.26412" rel="noopener noreferrer"&gt;arxiv:2604.26412 "When Hidden States Drift: KV Caches and Long-Range Speculative Decoding"&lt;/a&gt; names this drafter-bandwidth bottleneck at the research level; the table above is the practitioner measurement. Three months of tuning a drafter ratio that evaporates at output &amp;gt; 1000 tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MTP, with the &lt;code&gt;--spec-draft-p-min 0.75&lt;/code&gt; filter on drafter logits:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;output_len&lt;/th&gt;
&lt;th&gt;Autoregressive wall (ms)&lt;/th&gt;
&lt;th&gt;DFlash wall (ms)&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;new MTP wall (ms)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;MTP decode tok/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;41,888&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;48,394&lt;/td&gt;
&lt;td&gt;52,609&lt;/td&gt;
&lt;td&gt;44.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55,785&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;59,995&lt;/td&gt;
&lt;td&gt;62,951&lt;/td&gt;
&lt;td&gt;39.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;72,966&lt;/td&gt;
&lt;td&gt;79,528&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;73,089&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;44.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;107,476&lt;/td&gt;
&lt;td&gt;112,567&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91,427&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MTP's decode rate &lt;strong&gt;does not collapse&lt;/strong&gt;. It holds 39 to 49 tok/s across every output length tested. MTP has no separate drafter model: the multi-token heads share the target's hidden state and its KV cache. No second KV cache to feed, no bandwidth contention, no drafter-KV-grows-with-output bottleneck.&lt;/p&gt;

&lt;p&gt;Crossover math: MTP has worse prefill (~49s vs autoregressive's 37.5s) but sustained-high decode (~46 tok/s vs autoregressive's 29). MTP overcomes its 11.5s prefill tax at output ~900 tokens: &lt;code&gt;11.5 / (1/29 - 1/46) ≈ 902&lt;/code&gt;. Below that, autoregressive wins. Above, MTP wins. DFlash is below both at every tested length.&lt;/p&gt;

&lt;p&gt;Phase 2 buried MTP at short context with the wrong settings. The &lt;code&gt;p_min=0.75&lt;/code&gt; filter plus a long-context workload exhumed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a second GPU help?
&lt;/h2&gt;

&lt;p&gt;The first instinct on the bandwidth-starved-drafter problem is to throw a second GPU at it: drafter on one card, target on the other, in parallel. The math doesn't reward it. Within a single draft/verify cycle there's no parallelism (target verification depends on drafter output). Across cycles, async or lookahead spec decoding gives a theoretical speedup ceiling of &lt;code&gt;1 + min(time_d, time_t) / max(time_d, time_t)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At long context the drafter dominates the cycle, so parallel speedup tops out around 1.125x. A second 3090 Ti buys a 1.2x win for ~$700. MTP gives the same architectural win for $0 via shared KV and no bandwidth contention. llama.cpp doesn't support async 2-GPU spec decoding anyway. vLLM and TensorRT-LLM do, which means buying hardware AND switching the inference stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 9: production switch (2026-05-15)
&lt;/h2&gt;

&lt;p&gt;The llama-server unit on &lt;code&gt;ubuntu1&lt;/code&gt; got rewritten. BeeLlama out, vanilla llama.cpp &lt;code&gt;build-mtp&lt;/code&gt; in. Standard Q4_K_M GGUF out, MTP-variant Q4_K_M in. Separate drafter file dropped entirely. Hermes didn't need touching: same alias, same OpenAI shape, zero client changes. About 5 minutes total wall-time.&lt;/p&gt;

&lt;p&gt;Verification bench against the live prod unit, N=3, four output lengths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;output_len&lt;/th&gt;
&lt;th&gt;wall median (ms)&lt;/th&gt;
&lt;th&gt;decode tok/s&lt;/th&gt;
&lt;th&gt;reasoning_chars median&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;53,138&lt;/td&gt;
&lt;td&gt;44.6&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;62,997&lt;/td&gt;
&lt;td&gt;39.1&lt;/td&gt;
&lt;td&gt;186&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;73,052&lt;/td&gt;
&lt;td&gt;44.4&lt;/td&gt;
&lt;td&gt;803&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;91,304&lt;/td&gt;
&lt;td&gt;48.9&lt;/td&gt;
&lt;td&gt;816&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Numbers match Phase 8 standalone within 1%. &lt;code&gt;--reasoning-budget 256&lt;/code&gt; introduces no measurable regression. Production VRAM 20,902 MiB. The swap deleted code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sidebar: Tailscale relay vs LAN throughput
&lt;/h2&gt;

&lt;p&gt;The MTP-variant model swap was painful the first time. Mac Studio and ubuntu1 are on the same Tailscale network, so the obvious move is &lt;code&gt;scp&lt;/code&gt; over the 100.x address. That tops out at 1 to 2 MB/s because Tailscale routes through a DERP relay in Seattle. The 16 GB swap would have taken hours.&lt;/p&gt;

&lt;p&gt;Both boxes are also physically Ethernet-bridged on the local LAN: ubuntu1 at &lt;code&gt;192.168.2.2&lt;/code&gt;, Mac Studio at &lt;code&gt;192.168.2.1&lt;/code&gt;. Same &lt;code&gt;scp&lt;/code&gt; command pointed at the LAN address: 100 MB/s, ~3 minutes for the 16 GB model. &lt;strong&gt;50 to 100x speedup&lt;/strong&gt; over the Tailscale path.&lt;/p&gt;

&lt;p&gt;The DigitalOcean droplet pulled MTP weights directly from HuggingFace at 67 MB/s when the Tailscale-from-Mac-Studio attempt hung on auth for 30 minutes. Public-internet egress was 30x faster than the mesh peer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distribution evidence is mandatory.&lt;/strong&gt; Single-prompt benchmarks inflated DFlash gains 30 to 60% versus an 8-prompt diversity sweep. N=1 misleads even at the right prompt size: Phase 6 → 7 had three headline numbers off by 1.5x to 4x from one observation. When a vendor publishes "3.4x," assume the median is closer to 1.5 to 2x. And match the bench prompt size to production: Phase 5 was the right experiment on the wrong workload (Phase 7 redid it at 43K context and got a 4x larger effect).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decode tok/s and TTFT decouple on reasoning models at production context.&lt;/strong&gt; At 43K context they're three timescales (prefill, reasoning, decode) at roughly 80% / 12% / 8% of wall clock. Optimize what users feel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spec decoding is workload-dependent, and "workload" includes output length.&lt;/strong&gt; DFlash is 1.6x on chat-short (out~700) and 1.04x at out=2000. Autoregressive holds ~29 tok/s flat. MTP holds 39 to 49 tok/s under prod flags. Pick the technique whose curve fits your traffic. Cache reuse is binary: ~60x prefill speedup with a stable prefix, full cost when it isn't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spec decoding at temp=0 is NOT bit-for-bit lossless on prose.&lt;/strong&gt; Identical on tool calls and one-token answers, completely different on free-form prose. True for both DFlash and MTP. Both implementations fail the textbook lossless guarantee on prose-heavy workloads.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is multi-token prediction (MTP)?
&lt;/h3&gt;

&lt;p&gt;MTP adds "head" layers to the target model that predict several tokens in parallel with the main next-token prediction. Drafts get verified on the next forward pass: accepted tokens are free, the first rejected one cuts off the rest. No separate drafter model, shared KV cache, same speculative-decoding mechanism as DFlash but with drafts coming from inside the target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does MTP change the output?
&lt;/h3&gt;

&lt;p&gt;On tool calls and short structured answers, bit-for-bit identical to autoregressive at temp=0 (verified N=3 per workload). On free-form prose past a few hundred tokens, ~1000 characters of textual drift on chat-short, same magnitude as DFlash. Semantically equivalent, textually different. For regression tests that diff against gold outputs, disable speculative decoding entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  MTP vs DFlash on a 3090 Ti, which one's faster?
&lt;/h3&gt;

&lt;p&gt;Depends on output length. Below output 500, autoregressive &amp;gt; DFlash &amp;gt; MTP because prefill dominates. By output 1000, autoregressive ≈ MTP &amp;gt; DFlash. By output 2000, MTP &amp;gt; autoregressive &amp;gt; DFlash by 16 to 21 seconds wall clock. DFlash decode collapses from 47 to 30 tok/s as output grows because its drafter's KV cache competes for bandwidth. MTP shares the target's KV, no collapse.&lt;/p&gt;

&lt;p&gt;InsiderLLM has a DFlash-vs-MTP head-to-head on the same hardware that benches a single short-output point. The DFlash Decode Collapse only appears past output 500-1000 tokens, which is why it doesn't surface in short-prompt comparisons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does MTP work on Qwen3.6-27B dense?
&lt;/h3&gt;

&lt;p&gt;Yes. Unsloth ships &lt;code&gt;Qwen3.6-27B-MTP-GGUF&lt;/code&gt; with the heads baked in. The HackMD MoE benchmark initially said "MTP doesn't help" on Qwen3.6-35B-A3B, then a &lt;a href="https://hackmd.io/ODXuOQNzSiyUITz7g9mtBw" rel="noopener noreferrer"&gt;May 8 2026 update&lt;/a&gt; flipped to +27.5% with corrected flags. The MoE story is still evolving. Dense 27B with &lt;code&gt;--spec-type mtp --spec-draft-p-min 0.75 --spec-draft-n-max 6&lt;/code&gt; runs at 39 to 49 tok/s across the tested output lengths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does MTP need a custom llama.cpp build?
&lt;/h3&gt;

&lt;p&gt;PR #22673 (am17an, opened May 4 2026, merged to master May 16 2026) added &lt;code&gt;--spec-type mtp&lt;/code&gt;. Builds from &lt;code&gt;master&lt;/code&gt; after that date have it natively. For earlier checkouts, fetch the PR branch and build with &lt;code&gt;-DGGML_CUDA_FA_ALL_QUANTS=ON -DGGML_NATIVE=ON&lt;/code&gt;. Takes ~4 to 13 minutes depending on cache state.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about Apple Silicon?
&lt;/h3&gt;

&lt;p&gt;This post is RTX 3090 Ti specific. MTP on Metal has different gotchas (issue &lt;a href="https://github.com/ggml-org/llama.cpp/issues/23011" rel="noopener noreferrer"&gt;#23011&lt;/a&gt; flags "MTP slower than baseline on Apple Metal despite high acceptance" on the 35B-A3B variant). The MLX backend is a separate story. For the Apple Silicon side of local inference (LM Studio tuning, KV-cache quantization, the sysctl GPU-memory-cap fix), see &lt;a href="https://ianlpaterson.com/blog/lm-studio-fix-cannot-truncate-prompt-n-keep-n-ctx/" rel="noopener noreferrer"&gt;LM Studio Errors on Apple Silicon&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money quotes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"Same box, same model, same flags. Different binary. Nearly three times the throughput."&lt;/li&gt;
&lt;li&gt;"Speculative decoding is supposed to be lossless at temp=0. We measured it. It isn't, on prose. Tool calls survive."&lt;/li&gt;
&lt;li&gt;"CopySpec without the drafter is a 300x slowdown. The drafter isn't overhead. The drafter is the entire performance story."&lt;/li&gt;
&lt;li&gt;"Three months of tuning a drafter ratio that evaporates at output &amp;gt; 1000 tokens."&lt;/li&gt;
&lt;li&gt;"MTP doesn't have a separate drafter. The heads are part of the same forward pass. There's no second KV cache to feed. So when the context gets long, MTP doesn't slow down. DFlash does."&lt;/li&gt;
&lt;li&gt;"Production went from BeeLlama + DFlash + custom drafter back to vanilla llama.cpp + the MTP-variant GGUF. The swap deleted code."&lt;/li&gt;
&lt;li&gt;"Hermes never noticed. We changed the binary, the model, the spec-decoding technique, and the drafter situation, and Hermes kept hitting the same alias."&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Companion post forthcoming: Building a 3090 Ti Homelab Inference Node on a Dell Precision T5820. All commands and configs reproducible.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>performance</category>
    </item>
    <item>
      <title>LLM Benchmark Rankings 2026: 15 Models Tested on 38 Real Coding Tasks</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 19:59:50 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/llm-benchmark-rankings-2026-15-models-tested-on-38-real-coding-tasks-40kn</link>
      <guid>https://forem.com/ianlpaterson/llm-benchmark-rankings-2026-15-models-tested-on-38-real-coding-tasks-40kn</guid>
      <description>&lt;p&gt;Most LLM benchmarks measure raw intelligence. Real deployment decisions also depend on latency, format reliability, and data boundaries, including when a task should stay on-prem instead of going to a public cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49187q59vtgmyh4aca5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49187q59vtgmyh4aca5c.png" alt="Scatter plot showing 15 LLM models in four natural clusters by quality score and cost per run" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most LLM benchmarks measure raw intelligence. Real deployment decisions also depend on response speed, format reliability, and data boundaries, including when a task should stay on-prem instead of going to a public cloud. And while every model vendor says "test on your own data," but almost nobody publishes those results with cost, latency, and pass-rate data attached.&lt;/p&gt;

&lt;p&gt;This is that test. Total cost: $2.29.&lt;/p&gt;

&lt;p&gt;Fifteen models across thirty-eight tasks from my daily work as a tech executive, five hundred seventy API calls scored deterministically with an LLM judge pass for QA.&lt;/p&gt;

&lt;p&gt;And the winning model is not a model, but a maxim:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing beats model selection.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For most daily tasks, the cheap models are good enough, and the routing decision is worth more than picking the "best" model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key findings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opus &amp;amp; Sonnet both scores 100%.&lt;/li&gt;
&lt;li&gt;Gemini Flash scores 97% at $0.003/run.&lt;/li&gt;
&lt;li&gt;GPT-oss-20b scores 98.3% running locally on-prem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The surprises:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MiniMax M2.5 is not in most people's rotation, and yet scores 98.6% with 100% pass rate and returns clean structured output (bare JSON, no wrapper text) on 23 of 38 tests.&lt;/li&gt;
&lt;li&gt;And GPT-oss-20b, which barely exists on public leaderboards, outscored Haiku, R1, and GPT-5-Nano while costing nothing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Problem I Was Solving
&lt;/h2&gt;

&lt;p&gt;I run a publicly traded cybersecurity company and use AI in production every day. This benchmark started as a cost-control question: when is a budget model good enough that wasting money on frontier prices becomes indefensible? I do not need a model that wins one-shot snake games. I need a model that completes production work reliably, is quick to respond and doesn't break the bank.&lt;/p&gt;

&lt;p&gt;LLM inference prices have fallen 10-50x per year since 2022 (&lt;a href="https://epoch.ai/data-insights/llm-inference-price-trends" rel="noopener noreferrer"&gt;Epoch AI&lt;/a&gt;), which makes the routing question worth actually answering with real data.&lt;/p&gt;

&lt;p&gt;Scope: text-only, single-shot prompts routed by task type. No agent harness. The constraints (speed, cost, data sovereignty, on-prem option) determined which 15 models made the list. Thirty-eight tasks is a small sample, and a different practitioner's workload would produce different rankings. The test suite and harness are published on GitHub so anyone can extend it with their own tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Stack
&lt;/h2&gt;

&lt;p&gt;This benchmark is a routing guide, not a ranking. The data consistently points to three tiers working together, not one model doing everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed and cost&lt;/strong&gt; (Flash, GPT-oss-20b, Haiku, DeepSeek V3): extraction, batch jobs, classification, health checks. Gemini Flash at 1.1s and $0.003/run handles 97.1% of tasks. GPT-oss-20b hits 98.3% for free. These models cover the high-volume, low-stakes layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General purpose workhorse&lt;/strong&gt; (Sonnet): 100% accuracy, $0.20/run, 4.6s median. The model you reach for when the task matters and you don't want to think about routing. MiniMax M2.5 belongs here too for batch pipelines that need clean structured output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-end reasoner&lt;/strong&gt; (Opus, Codex CLI, Kimi K2.5): multi-step causal chains, complex planning, style-constrained writing. This is where cheaper models drop to 60-80% and frontier models earn their cost. Codex is free with a ChatGPT Pro subscription.&lt;/p&gt;

&lt;p&gt;One latency caveat: Kimi K2.5 (29s median), DeepSeek R1 (23s), and MiniMax M2.5 (16s) are thinking models. Accurate, but impractical for interactive agent loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 38 Tasks and 15 Models
&lt;/h2&gt;

&lt;p&gt;I had Claude analyze two weeks of my session logs to build the test suite. Coding and data dominate my workload, so they got the most tests. A few tasks use Canadian context (TSX-V press releases, regulatory classification) since that's my actual data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Tests&lt;/th&gt;
&lt;th&gt;What it tests&lt;/th&gt;
&lt;th&gt;Real-world example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E - Extraction&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Pull structured data from messy text&lt;/td&gt;
&lt;td&gt;Mining press releases with null traps - hallucinating a missing Cu grade corrupts a downstream database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C - Code&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Write and fix code&lt;/td&gt;
&lt;td&gt;Bash, Python, Rust, TypeScript - my actual production stack, in my order of preference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R - Reasoning&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Multi-step logic, contradiction detection&lt;/td&gt;
&lt;td&gt;Cause-effect chains and root cause analysis - the group that differentiates models most in practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W - Writing&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Style-constrained drafting&lt;/td&gt;
&lt;td&gt;Tested against my specific rules (no em dashes, no "Not X. Y." device)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P - Planning&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Task decomposition, spec writing&lt;/td&gt;
&lt;td&gt;Edge case enumeration for production systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I - Investments&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Prediction markets, options extraction&lt;/td&gt;
&lt;td&gt;Portfolio signals from financial text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D - Data&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;CSV/JSON manipulation&lt;/td&gt;
&lt;td&gt;Transformation and normalization from real automation pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H - Health&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Ops parsing&lt;/td&gt;
&lt;td&gt;Cron log analysis, schema drift detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L - Letter Counting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Character-level processing&lt;/td&gt;
&lt;td&gt;"How many e's in nevertheless?" The trap: it's 4, not 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M - Math&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Multi-step arithmetic&lt;/td&gt;
&lt;td&gt;Modular arithmetic chain where step 1 wrong cascades through everything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Llama, Mistral, and Cohere aren't in my daily rotation so they're absent, but the suite is published for anyone to extend.&lt;/p&gt;

&lt;p&gt;Scored deterministically, with an Opus judge pass for QA. Every test is rerunnable. 14 of 15 models score above 85%, which suggests the tasks are broadly achievable rather than shaped to favor any one provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  2026 Benchmark Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Pass Rate&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Median Time&lt;/th&gt;
&lt;th&gt;Total Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Claude Sonnet 4.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;38/38 (100%)&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;4.6s&lt;/td&gt;
&lt;td&gt;3.6 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Claude Opus 4.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;38/38 (100%)&lt;/td&gt;
&lt;td&gt;$0.69&lt;/td&gt;
&lt;td&gt;4.1s&lt;/td&gt;
&lt;td&gt;3.3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Kimi K2.5&lt;/td&gt;
&lt;td&gt;98.6%&lt;/td&gt;
&lt;td&gt;38/38 (100%)&lt;/td&gt;
&lt;td&gt;$0.13&lt;/td&gt;
&lt;td&gt;29.2s&lt;/td&gt;
&lt;td&gt;33 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;MiniMax M2.5&lt;/td&gt;
&lt;td&gt;98.6%&lt;/td&gt;
&lt;td&gt;38/38 (100%)&lt;/td&gt;
&lt;td&gt;$0.07&lt;/td&gt;
&lt;td&gt;15.9s&lt;/td&gt;
&lt;td&gt;19 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;98.3%&lt;/td&gt;
&lt;td&gt;37/38 (97%)&lt;/td&gt;
&lt;td&gt;$0.71&lt;/td&gt;
&lt;td&gt;13.8s&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;GPT-5.2-codex (Codex CLI)&lt;/td&gt;
&lt;td&gt;98.3%&lt;/td&gt;
&lt;td&gt;37/38 (97%)&lt;/td&gt;
&lt;td&gt;$0.16&lt;/td&gt;
&lt;td&gt;4.6s&lt;/td&gt;
&lt;td&gt;3.9 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;GPT-oss-20b&lt;/td&gt;
&lt;td&gt;98.3%&lt;/td&gt;
&lt;td&gt;37/38 (97%)&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;4.1s&lt;/td&gt;
&lt;td&gt;3.3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;GPT-5.2&lt;/td&gt;
&lt;td&gt;98.0%&lt;/td&gt;
&lt;td&gt;37/38 (97%)&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;td&gt;3.0s&lt;/td&gt;
&lt;td&gt;2.5 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;97.1%&lt;/td&gt;
&lt;td&gt;35/38 (92%)&lt;/td&gt;
&lt;td&gt;$0.003&lt;/td&gt;
&lt;td&gt;1.1s&lt;/td&gt;
&lt;td&gt;52s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;DeepSeek R1&lt;/td&gt;
&lt;td&gt;96.8%&lt;/td&gt;
&lt;td&gt;37/38 (97%)&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;td&gt;23.1s&lt;/td&gt;
&lt;td&gt;22 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;95.9%&lt;/td&gt;
&lt;td&gt;37/38 (97%)&lt;/td&gt;
&lt;td&gt;$0.04&lt;/td&gt;
&lt;td&gt;2.2s&lt;/td&gt;
&lt;td&gt;1.6 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;GPT-5-Nano&lt;/td&gt;
&lt;td&gt;94.8%&lt;/td&gt;
&lt;td&gt;35/38 (92%)&lt;/td&gt;
&lt;td&gt;$0.03&lt;/td&gt;
&lt;td&gt;11.1s&lt;/td&gt;
&lt;td&gt;11 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;DeepSeek V3 (Chat)&lt;/td&gt;
&lt;td&gt;88.7%&lt;/td&gt;
&lt;td&gt;34/38 (89%)&lt;/td&gt;
&lt;td&gt;$0.008&lt;/td&gt;
&lt;td&gt;5.8s&lt;/td&gt;
&lt;td&gt;4.6 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Qwen 3.5 35B (local)&lt;/td&gt;
&lt;td&gt;85.8%&lt;/td&gt;
&lt;td&gt;33/38 (87%)&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;6.1s&lt;/td&gt;
&lt;td&gt;4.4 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Gemma 3 12B (local)&lt;/td&gt;
&lt;td&gt;80.6%&lt;/td&gt;
&lt;td&gt;32/38 (84%)&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;5.8s&lt;/td&gt;
&lt;td&gt;5.8 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All models run through OpenRouter for apples-to-apples comparison. Differences under ~2 percentage points are within the noise floor - treat models within that band as statistically tied.&lt;/p&gt;

&lt;p&gt;Gemini Flash is the speed/cost champion: 1.1s median, $0.003 total, 92% pass rate. If you can tolerate 3 failures out of 38, this is absurd value.&lt;/p&gt;

&lt;p&gt;GPT-oss-20b at $0.00 and 98.3% is the free-tier surprise. It ties Gemini Pro and Codex on points while costing nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Findings
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyv1bnkntl4t0ywr9x1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyv1bnkntl4t0ywr9x1h.png" alt="Model family comparison" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Sonnet is the benchmark ceiling on value
&lt;/h3&gt;

&lt;p&gt;172.5/172.5 points, 38/38 pass rate, $0.20 total cost, 4.6s median response time. Opus matches Sonnet on accuracy (100.0%) but costs 3.5x more ($0.69). No other model matched Sonnet's combination of perfect accuracy, reasonable cost, and fast response.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. MiniMax M2.5 is the format compliance champion
&lt;/h3&gt;

&lt;p&gt;100% pass rate under both deterministic scoring and Opus-as-judge. MiniMax responses are extremely concise: 23 of 38 are JSON-only with zero explanation text, 14 under 200 characters. It never triggers must_not_contain penalties because there is no text to penalize. Format compliance is a real, separate capability that matters in production pipelines.&lt;/p&gt;

&lt;p&gt;MiniMax is doing something operationally important here. Most models add wrapper text ("Here's the JSON..."), markdown fences, or extra rationale that breaks deterministic parsers. MiniMax mostly does not. It returns parseable payloads with almost no conversational scaffolding, which means fewer downstream failures in automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Gemini Flash redefines the cost floor
&lt;/h3&gt;

&lt;p&gt;$0.003 for 97.1% quality. 1.1-second median response. 110.6 tok/s. For extraction, data transformation, batch jobs, and health checks, Flash is the rational default and the best Brains per Buck in this benchmark. The 3 tests it fails (E4, R1, R4) are all reasoning-adjacent. On pure data tasks, Flash is perfect.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The free tier is competitive
&lt;/h3&gt;

&lt;p&gt;GPT-oss-20b at $0.00 outscores Haiku, R1, and GPT-5-Nano. The free tier is no longer an afterthought.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Thinking models pay a steep latency tax for marginal gains
&lt;/h3&gt;

&lt;p&gt;Kimi K2.5 matches Sonnet's 100% pass rate but takes 33 minutes vs 3.6 minutes (9.3x slower) and produces 4.8x more output tokens. MiniMax M2.5 is the fastest thinker at 19 minutes, still 5.3x slower than Sonnet. The quality improvement from extended thinking is marginal on these tasks, but the latency cost is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Reasoning is the only category with a hard quality split
&lt;/h3&gt;

&lt;p&gt;R-group has a 13.3% failure rate, the highest of any category. Four models failed R1 (gold production calculation), four failed R4 (root cause identification). The models that fail reasoning tasks are predictable: smaller models and budget options. If your task involves multi-step causal chains or root cause analysis, that is where frontier models justify their price.&lt;/p&gt;

&lt;h3&gt;
  
  
  2026 Category Difficulty by Task Type
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vun9nt80m725o037syd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vun9nt80m725o037syd.png" alt="Category difficulty heatmap" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reasoning and Math are where models split. Planning and Code are essentially solved at 0-1% failure rates across all 15 models. Writing (5.3%) and Reasoning (13.3%) are where routing decisions matter most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want a personalized recommendation?&lt;/strong&gt; Try the &lt;a href="https://ianlpaterson.com/llm-picker/" rel="noopener noreferrer"&gt;LLM Picker tool&lt;/a&gt; to find the right model for your specific use case, budget, and priorities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is This Benchmark Too Easy?
&lt;/h3&gt;

&lt;p&gt;A fair critique is that this benchmark looks easy. Twenty-six of thirty-eight tests had zero failures across all fifteen models. A skeptic could call that too easy, and they would be missing the point.&lt;/p&gt;

&lt;p&gt;Most daily LLM work is already easy for current models. The core decision is rarely "which model can solve olympiad-level physics." The core decision is "which model can handle Tuesday afternoon production tasks with predictable quality, speed, and cost." A $0.002-per-task model scoring 98.6% on real work is the result that matters.&lt;/p&gt;

&lt;p&gt;MiniMax M2.5 is the clearest example. It sits at #27 on LiveBench, where competition math and agentic coding dominate the ranking. On SWE-bench Verified it ranks #4 (80.2%), and in this benchmark it scores 98.6% with 100% format compliance. LiveBench asks if a model can do IMO-style problems. This benchmark asks whether it can extract Q3 revenue from an earnings call and return clean structured output. The gap between academic benchmarks and practical benchmarks is not a bug in either one. It is evidence that the market has segmented. Frontier intelligence is one product, reliable task completion is another, and most teams are buying the first when they need the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Actual Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  LLM Model Routing: Which Tier for Which Task?
&lt;/h3&gt;

&lt;p&gt;The benchmark data clusters into four natural tiers. In practice, LLM model routing means sending each task to the cheapest model that reliably clears your quality bar, then escalating only when the task type needs more reasoning depth or stricter output quality.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Models&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Cost/Run&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;GPT-oss-20b, Qwen 3.5 35B, Gemma 12B&lt;/td&gt;
&lt;td&gt;80-98%&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;Extraction (GPT-oss: 98.3%), local-only workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Budget&lt;/td&gt;
&lt;td&gt;Gemini Flash, DeepSeek V3&lt;/td&gt;
&lt;td&gt;88-97%&lt;/td&gt;
&lt;td&gt;$0.003-$0.008&lt;/td&gt;
&lt;td&gt;Batch jobs, health checks, data transforms, speed-critical agentic loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid&lt;/td&gt;
&lt;td&gt;Haiku, MiniMax M2.5, GPT-5-Nano&lt;/td&gt;
&lt;td&gt;94-99%&lt;/td&gt;
&lt;td&gt;$0.03-$0.07&lt;/td&gt;
&lt;td&gt;Code, data, most production tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontier&lt;/td&gt;
&lt;td&gt;Sonnet, Opus, GPT-5.2-codex, Kimi K2.5&lt;/td&gt;
&lt;td&gt;98-100%&lt;/td&gt;
&lt;td&gt;$0.13-$0.69&lt;/td&gt;
&lt;td&gt;Reasoning, writing with style constraints, complex planning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is &lt;strong&gt;&lt;a href="https://ianlpaterson.com/blog/inference-arbitrage-llm-routing-playbook/" rel="noopener noreferrer"&gt;Inference Arbitrage&lt;/a&gt;&lt;/strong&gt; in practice: route each task to the cheapest model that still clears your bar.&lt;/p&gt;

&lt;p&gt;For a session where the same tier mapping ran headfirst into a three-month LLM hallucination loop, and only a deterministic tool caught the real bug, see &lt;a href="https://ianlpaterson.com/blog/llm-kept-saying-fixed-three-months/" rel="noopener noreferrer"&gt;The LLM Kept Saying ‘Fixed.’ For Three Months, It Wasn’t.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a second optimization layer most teams miss, &lt;strong&gt;Remnant Tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you pay for Google Workspace, you get a daily bucket of Gemini calls. If you pay for ChatGPT Pro ($200/month), Codex usage is covered by subscription. Those allowances are prepaid capacity, and unused capacity expires.&lt;/p&gt;

&lt;p&gt;A practical routing policy is: burn remnant token capacity first, then use the tiered routing table for overflow and for tasks that need higher reasoning depth.&lt;/p&gt;

&lt;p&gt;The actual routing logic, including escalation thresholds, fallback patterns, and cost guardrails for agent loops, will be covered in a follow-up.&lt;/p&gt;

&lt;p&gt;This test was run in March of 2026, and with the rate of change in AI land, could be out of date quickly. Including a reminder of the year for future users reference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best LLM for Coding Tasks (2026)
&lt;/h3&gt;

&lt;p&gt;For coding tasks, Sonnet and GPT-5.2-codex both scored 100%, and planning/code categories were near-solved across the full field (0-1% failure rates). The ranking differences come mostly from reasoning and style-constrained writing, not core code generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cheapest LLM for Production Use (2026)
&lt;/h3&gt;

&lt;p&gt;Gemini 2.5 Flash posted 97.1% quality for $0.003 per 38-test run with a 1.1s median response time, making it the cheapest paid production option in this benchmark. DeepSeek V3 is also cheap at $0.008 but trails on quality at 88.7%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Open Source LLM (2026)
&lt;/h3&gt;

&lt;p&gt;GPT-oss-20b scored 98.3% with a 97% pass rate at $0.00, outperforming the other local/open models in this benchmark. Qwen 3.5 35B scored 85.8% and Gemma 3 12B scored 80.6%, so GPT-oss-20b is the strongest free open model here.&lt;/p&gt;

&lt;p&gt;GPT-oss barely exists on public leaderboards. Artificial Analysis tracks the larger 120B variant as the highest-ranked American open-weight model (Intelligence Index 33). The 20B version we tested locally is not independently ranked anywhere we found. This benchmark may be its first independent public evaluation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'm Actually Going to Use
&lt;/h3&gt;

&lt;p&gt;The benchmark confirmed some choices and changed others. Here's my actual routing plan going forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opus 4.6 as the orchestrator for main work.&lt;/strong&gt; This is the model I sit in front of for interactive sessions: managing plans, interactive dialogue, coordinating subagents. Opus ties Sonnet on batch accuracy but costs 3.5x more, though interactive debugging rewards extended context handling and multi-turn coherence over single-shot accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extensive Sonnet subagent work.&lt;/strong&gt; Sonnet scored 100% and costs $0.20 per run. There's very little downside to forking Sonnet agents to grind in the background on research, code review, data analysis, and file processing. The benchmark confirmed what I'd already suspected: Sonnet is the workhorse. Sonnet matching Opus on quality at one-third the price is the clearest signal in the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini Flash for quick classification, web searches, and batch extraction.&lt;/strong&gt; Paid Google accounts come with a generous bucket of free API calls and OAuth calls per day. Flash at 1.1s median and 97.1% quality is effectively free at my usage volume. For anything where I need a fast answer and can tolerate the occasional reasoning miss, Flash is the default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-prem: keep Qwen 3.5 35B as the primary local model, but explore GPT-oss-20b.&lt;/strong&gt; Qwen runs on my Mac Studio through &lt;a href="https://ianlpaterson.com/blog/lm-studio-fix-cannot-truncate-prompt-n-keep-n-ctx/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt;, powered by &lt;a href="https://ianlpaterson.com/blog/openclaw-setup-apple-silicon-local-llm/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; as the agent framework, and handles basic tasks at 20.3 tok/s for $0.00. GPT-oss-20b's 98.3% score is hard to ignore, though. I'll be running both against real-world scenarios over the coming weeks to see if GPT-oss-20b holds up outside the benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you have ChatGPT Pro ($200/month), use Codex CLI.&lt;/strong&gt; GPT-5.2-codex scored 98.3% with 97% pass rate. The Pro subscription covers the API cost, so every Codex call is effectively free. For coding tasks especially, this is a strong ceiling at no additional per-call cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on batch scores vs interactive debugging
&lt;/h3&gt;

&lt;p&gt;These benchmark results measure batch accuracy: give a model a well-defined task, score the output deterministically. They do not fully predict how a model performs in an interactive debugging session where context accumulates, the goal shifts mid-conversation, and the model needs to track its own prior reasoning.&lt;/p&gt;

&lt;p&gt;After running this benchmark, Claude Code was switched to Haiku as the default model (95.9% here, $0.036/run). Claude Code is my daily operating system, with a &lt;a href="https://ianlpaterson.com/blog/claude-code-memory-architecture/" rel="noopener noreferrer"&gt;persistent memory architecture&lt;/a&gt; that carries context across sessions. The first real test was a cron job that had stopped firing. Haiku circled the problem, made plausible-looking changes, and failed to fix it across multiple turns. Switching to Sonnet with extended thinking resolved it in one exchange. The routing table holds for batch API work.&lt;/p&gt;

&lt;p&gt;For interactive debugging with long context chains, the benchmark scores understate the gap between tiers. Treat them as a floor, not a ceiling.&lt;/p&gt;

&lt;p&gt;This benchmark covers 38 tasks from one practitioner's workflow. It does not cover creative writing, image analysis, long-context document tasks, or multi-turn conversation. The routing table above is a starting point, not a universal prescription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Cost to Run
&lt;/h3&gt;

&lt;p&gt;Total benchmark cost: &lt;strong&gt;$2.29&lt;/strong&gt; for 570 calls across 15 models via OpenRouter. That is the entire cost to rank 15 models against 38 tasks and compute Inference ROI from real workloads instead of assumptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72o5aqdbzfwlhucxh549.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72o5aqdbzfwlhucxh549.png" alt="Cost comparison bar chart" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The table tells the story: spending $0.20 (Sonnet) gets you perfect marks. Spending $0.69 (Opus) gets you the same marks at 3.5x the price. The marginal return on spending above Sonnet is zero, not negative.&lt;/p&gt;

&lt;p&gt;On academic benchmarks the ranking is reversed. LiveBench places Opus at #3 (76.33) and Sonnet at #17 (68.19). SWE-bench Verified has Opus at #1 (80.8%) and Sonnet mid-pack (77.2%). Aider Polyglot has Opus at #14 (72.0%) and Sonnet at #22 (61.3%). The parity in our results reflects task difficulty: for structured daily work, Sonnet's instruction-following precision matches Opus's reasoning depth. The gap reopens on competition math and multi-file refactoring.&lt;/p&gt;

&lt;p&gt;Gemini Flash at $0.003 per 38-test run delivers 97.1% quality. Opus at $0.69 delivers 100.0%. That is The 265x Question: when is a 265x cost difference worth a 2.9 percentage point quality gap? That ratio holds for data tasks where Flash scores 100%. On reasoning tasks, Flash drops to 60% while Opus stays near 100%. The routing thesis is that the 265x stat applies to some tasks and not others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How the Benchmark Actually Ran
&lt;/h3&gt;

&lt;p&gt;The 500-line Python harness: runner, scorer, adapters, report generator, schema validator. Adapters handle auth, ID mapping, response normalization per model. The runner fires 38 prompts per model in parallel threads, capturing &lt;code&gt;time.monotonic()&lt;/code&gt; per call, writing raw results to JSON. One model, 38 calls, one output file. All models tested at default weights with no fine-tuning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;METHODOLOGY
===========

Step 1: Task Inventory
  |
  +--&amp;gt; Pulled 38 tasks from Ian's own Claude Code
  |    session history (not academic benchmarks)
  |
  +--&amp;gt; 10 groups: E/C/R/W/P/H/I/D/L/M
  |    (extraction, code, reasoning, writing,
  |     planning, health, investments, data,
  |     letter counting, math)
  |
  +--&amp;gt; Canadian context built in: TSX-V drill
       results, prediction markets, cron ops
         |
         v
Step 2: Test Harness
  |
  +--&amp;gt; 5 model adapters built:
  |    Anthropic SDK / Gemini REST /
  |    OpenRouter / LM Studio / Codex CLI MCP
  |
  +--&amp;gt; 11 deterministic scorer types:
  |    json_object, code_exec,
  |    writing_constraints, json_array...
  |    (NO LLM judge - can't test with the
  |     same tool you're evaluating)
  |
  +--&amp;gt; Runner: parallel threads, wall_time
       captured via time.monotonic()
         |
         v
Step 3: Benchmark Run (March 1-8, 2026)
  |
  +--&amp;gt; 15 models x 38 tests = 570 calls
  |
  +--&amp;gt; Captured per-call:
  |    quality score, wall time,
  |    tok/s, cost (USD)
  |
  +--&amp;gt; Total cost: $2.29
         |
         v
Step 4: QA Pass (parallel)
  |
  +--[Codex subagent]--&amp;gt; Automated integrity:
  |    completeness, score sanity, cost
  |    &amp;gt;&amp;gt; Found 3 scorer bugs (CSV parser,
  |       JSON regex, R1 format instruction)
  |
  +--[Opus subagent]---&amp;gt; Manual review:
       every failure examined
       &amp;gt;&amp;gt; Found 4 subtler bugs (wrong extract
          fn, I2 all-pass regression, haiku
          penalized for showing work, R2 narrow)
         |
         v
Step 5: Results
  |
  +--&amp;gt; Corrected rankings published
  +--&amp;gt; Raw results + all 38 prompts on GitHub
  +--&amp;gt; Routing table derived from group scores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scoring is deterministic. The 11 scorer types (&lt;code&gt;json_object&lt;/code&gt;, &lt;code&gt;code_exec&lt;/code&gt;, &lt;code&gt;writing_constraints&lt;/code&gt;, etc.) score raw responses against defined criteria. Pass/fail is computed, not judged. Every result is verifiable by rerunning the same call.&lt;/p&gt;

&lt;p&gt;All 15 models were called through the same OpenRouter API with identical parameters: a system prompt, a user prompt, and max_tokens=8192. No model received special reasoning configuration, thinking budgets, or elevated inference modes. MiniMax M2.5, Kimi K2.5, and DeepSeek R1 are architecturally thinking models. They produce reasoning traces by default on every API call. There is no "turn it off" switch the way Gemini Flash offers a thinkingBudget parameter. SWE-bench separately evaluates these models in a "high reasoning" configuration that boosts reasoning effort beyond the default. We did not use high reasoning mode. What you see in the results is what you would get if you called each model's API today with a standard prompt and no special parameters. The latency and token cost of that default reasoning (19 minutes for MiniMax, 33 minutes for Kimi, 22 minutes for R1 on 38 tests vs Sonnet's 3.6 minutes) is documented in the Speed and Key Findings sections above.&lt;/p&gt;

&lt;p&gt;LiveBench's ICLR 2025 research showed that LLM-as-judge scoring has 21-46% error rates on hard tasks, which is one reason this benchmark uses deterministic scoring for all 38 tests, with the Opus judge as a QA layer rather than the primary scorer.&lt;/p&gt;

&lt;p&gt;No model refused any of the 38 prompts. Refusal rate testing (how often safety filters block production-style requests) matters for deployment but is outside this benchmark's scope.&lt;/p&gt;

&lt;p&gt;The full test harness, all 38 prompts, and raw model responses are on [GitHub repo - coming soon].&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Your LLM Benchmark Infrastructure Matters More Than the Models
&lt;/h3&gt;

&lt;p&gt;The original benchmark ran 15 models through five different adapters: &lt;code&gt;claude -p&lt;/code&gt; for Anthropic, &lt;code&gt;codex exec&lt;/code&gt; for GPT-5.2 variants, the Gemini CLI for Google, LM Studio for open-source, and OpenRouter for everything else. The v2 rerun routed seven of those models through OpenRouter instead. Same prompts. Same scorer. Different plumbing.&lt;/p&gt;

&lt;p&gt;Four Gemini responses turned out to be CLI artifacts, not model output. C3 came back as &lt;code&gt;&amp;lt;/code&amp;gt;&lt;/code&gt;. D2 came back as "I have completed the task as requested." These aren't bad answers. They're the CLI capturing a status message while the model's actual output went to a tool-use sandbox the CLI never surfaced. Through OpenRouter, all four returned valid, high-scoring responses.&lt;/p&gt;

&lt;p&gt;Fourteen tests flipped between v1 and v2 on identical prompts. R4 (root cause analysis) was the most contested: three models changed their answer on different days. GPT-5.2-codex and Gemini Flash went from correct to incorrect. Gemini Pro went from incorrect to correct. That's the inherent noise floor of LLM evaluation, and any benchmark that doesn't acknowledge it is reporting signal mixed with static.&lt;/p&gt;

&lt;p&gt;The lesson: if your models aren't all going through the same adapter path, your numbers contain an unknown amount of infrastructure noise. You might be ranking adapters, not models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every QA Layer Caught Something the Previous Layer Missed
&lt;/h3&gt;

&lt;p&gt;The benchmark went through five QA passes before the numbers were publishable. Each one found problems the previous missed.&lt;/p&gt;

&lt;p&gt;The Codex pass ran in 9 minutes, the Opus pass in 13, and they caught completely different categories of bugs. Automated testing found structural problems (CSV parser brittleness, JSON regex overreach, a max_score calculation that produced quality scores over 100%). LLM-powered review found semantic ones (a wrong answer key in the letter-counting test, one model giving the right answer in prose and getting zero because the scorer required JSON, "Haiku beats Sonnet" turning out to be a pure scorer artifact). Neither alone sufficed.&lt;/p&gt;

&lt;p&gt;The takeaway for anyone running their own benchmark: parallel QA with different model types catches different failure modes. Single-pass evaluation ships errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Format Compliance Is a Real Capability (and Small Models Don't Have It)
&lt;/h3&gt;

&lt;p&gt;Gemma (12B) and Qwen 3.5 (35B) both returned correct answers in formats the scorer couldn't parse. Repeatedly, on different test types, despite explicit format instructions.&lt;/p&gt;

&lt;p&gt;Gemma returned Python code for D3 (a CSV transformation task). The prompt said "Return CSV." Gemma wrote a Python script that would produce the correct CSV if executed. The csv_transform scorer tried to parse Python as CSV: 0% quality. Gemma did the same thing on L1 (letter counting), returning a Python function instead of JSON. Qwen returned a Markdown table for D3 instead of CSV. The values were correct. The parser crashed.&lt;/p&gt;

&lt;p&gt;MiniMax M2.5, by contrast, returned bare JSON on 23 of 38 responses. No explanations, no code blocks, no Markdown wrapping. It scored 100% pass rate under both the deterministic scorer and the Opus LLM-as-judge. That discipline, understanding that the consumer of your output is a machine and not a human who can interpret Python as "approximately JSON," is itself a form of intelligence.&lt;/p&gt;

&lt;p&gt;No other benchmark we reviewed (Artificial Analysis, SWE-bench, Aider, LiveBench, SEAL, Epoch AI) scores format compliance as an independent capability. They either penalize it silently or ignore it. For production pipelines where the consumer of model output is a parser, not a human, this gap in benchmark coverage is significant.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does Claude 4 Compare?
&lt;/h2&gt;

&lt;p&gt;Claude-specific queries are the 4th largest search cluster for this post, so here is the dedicated breakdown. Opus 4.6 and Sonnet 4.6 both scored 100.0% on all 38 tasks with a 100% pass rate. The difference is cost: Sonnet costs $0.20 per run, Opus costs $0.69. That is 3.5x the price for identical accuracy on these tasks. Unless you need Opus-tier reasoning for competition math or multi-file refactoring (where academic benchmarks show a gap), Sonnet is the better allocation.&lt;/p&gt;

&lt;p&gt;Haiku 4.5 scored 95.9% at $0.04 per run with a 97% pass rate (37/38). It failed one reasoning task (R4) and lost partial credit on a few others, but for batch classification, extraction, and health-check jobs it handles the workload at one-fifth the cost of Sonnet. The real-world test was less encouraging: Haiku circled a broken cron job for multiple turns without fixing it, while Sonnet with extended thinking resolved it in one exchange. Haiku is a solid batch workhorse. For anything requiring multi-step debugging, escalate to Sonnet.&lt;/p&gt;

&lt;p&gt;If you want the full Claude family routing logic: Haiku for batch API work under $0.04/call, Sonnet for everything interactive or reasoning-heavy at $0.20/call, and Opus only when you have confirmed that Sonnet fails on the specific task type. In this benchmark, that confirmation never came.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM Price-Performance Rankings
&lt;/h2&gt;

&lt;p&gt;Sorted by cost-per-correct-task from cheapest to most expensive, with a minimum 90% quality threshold to filter out models that are cheap but unreliable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.00/run:&lt;/strong&gt; GPT-oss-20b scored 98.3% running locally. Zero API cost. If you have the hardware, this is the price-performance champion by definition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.003/run:&lt;/strong&gt; Gemini 2.5 Flash hit 97.1% quality at 110.6 tok/s. Three-tenths of a cent per run. For extraction, batch jobs, and data transforms, Flash is the rational default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.04/run:&lt;/strong&gt; Claude Haiku 4.5 posted 95.9% with a 2.2s median response. Reliable for production tasks that need Anthropic-level instruction following without Sonnet pricing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.07/run:&lt;/strong&gt; MiniMax M2.5 scored 98.6% with 100% format compliance. The most structured output of any model tested, which matters when your downstream consumer is a parser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.13/run:&lt;/strong&gt; Kimi K2.5 matched MiniMax at 98.6% quality. Strong across all categories with a 100% pass rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.20/run:&lt;/strong&gt; Claude Sonnet 4.6 scored 100.0%. Perfect marks. Every dollar above this bought zero additional accuracy in this benchmark.&lt;/p&gt;

&lt;p&gt;The gap between $0.003 (Flash) and $0.20 (Sonnet) is 67x in cost for 2.9 percentage points of quality. On pure data tasks, Flash scores 100% and that 67x gap buys nothing. On reasoning tasks, Flash drops to 60% and Sonnet stays near 100%. The routing decision depends entirely on the task type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which LLMs Work Best for AI Agents?
&lt;/h2&gt;

&lt;p&gt;Agentic reliability is not just accuracy. It is pass rate (does the model produce usable output every time?), format compliance (does the output parse without manual cleanup?), and consistency under multi-step reasoning. From the benchmark data, three tiers emerge for agent use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1 (ship it):&lt;/strong&gt; Sonnet 4.6 (100% pass rate, 100% quality), Opus 4.6 (same marks), MiniMax M2.5 (100% pass rate, 98.6% quality with near-zero wrapper text), and Kimi K2.5 (100% pass rate, 98.6%). These four models never failed to produce scoreable output and rarely lost partial credit. For autonomous loops where a single malformed response can cascade into retry spirals, that pass rate matters more than marginal quality differences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2 (reliable with guardrails):&lt;/strong&gt; GPT-5.2-codex (97% pass rate, 98.3% quality), Haiku 4.5 (97%, 95.9%), and Gemini 2.5 Pro (97%, 98.3%). Each failed one task outright. In an agent loop with retry logic, these are fine. Without retries, expect occasional dropped steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3 (batch only):&lt;/strong&gt; Gemini Flash (92% pass rate), GPT-5-Nano (92%), DeepSeek V3 (89%), and the local models (84-87%). These models skip or malform output often enough that unsupervised agent loops will accumulate errors. They excel in supervised batch pipelines where a human or a Tier 1 model reviews the output.&lt;/p&gt;

&lt;p&gt;The single best predictor of agentic reliability in this benchmark was not accuracy, it was pass rate. A model that scores 95% but always returns parseable output is more useful in a pipeline than one that scores 98% but occasionally returns unparseable responses that require exception handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do These Compare to Artificial Analysis?
&lt;/h2&gt;

&lt;p&gt;Artificial Analysis runs synthetic benchmarks (MMLU, HumanEval, MATH, and similar) across a large model set with excellent speed and pricing data. Their rankings reflect performance on academic-style questions. This benchmark uses 38 tasks pulled from my actual Claude Code session history: regex extraction, API debugging, cron troubleshooting, writing with style constraints, prediction market parsing. The methodology is different, and so are some of the rankings.&lt;/p&gt;

&lt;p&gt;For example, Artificial Analysis ranks Opus well above Sonnet on most metrics. In this benchmark, they tied at 100%. The gap in academic benchmarks reflects tasks (competition math, multi-file refactoring, PhD-level science) that did not appear in my workload. Conversely, this benchmark surfaces format compliance and deterministic scoring failures that synthetic benchmarks typically ignore.&lt;/p&gt;

&lt;p&gt;Neither benchmark is wrong. They measure different things. If you are choosing a model for academic research or competition coding, Artificial Analysis and SWE-bench are more relevant. If you are choosing a model for production pipelines, data extraction, agent loops, and daily coding tasks, this benchmark is closer to what your actual usage will look like. Use both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are Kimi K2.5's benchmark results in 2026?
&lt;/h3&gt;

&lt;p&gt;Kimi K2.5 scored 98.6% quality with a 100% pass rate for $0.13 per run and a 29.2-second median response time. It is a thinking model from Moonshot AI, tying MiniMax M2.5 on quality and pass rate. The catch is latency: 2,002 seconds total for 38 tests (33 minutes). It generates 57,569 output tokens, 4.8x more than Sonnet, because extended reasoning traces are included in the output count.&lt;/p&gt;

&lt;p&gt;Kimi scored 100% in the earlier 37-test version. The additional test didn't change the picture. At $0.13 per run, Kimi is cheaper than Sonnet but the latency tax (29s median) makes it impractical outside of batch processing where wall-clock time is irrelevant.&lt;/p&gt;

&lt;p&gt;Kimi K2.5 is the most consistently validated model across benchmarks. Artificial Analysis ranks it #2 among open-weight models (Intelligence Index 47). SEAL places it #2 on MultiChallenge. In our benchmark: 98.6% quality, 100% pass rate. The only thing holding it back is speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does DeepSeek R1 compare to Claude Sonnet in 2026?
&lt;/h3&gt;

&lt;p&gt;DeepSeek R1 scored 96.8% with a 97% pass rate for $0.12 per run, versus Claude Sonnet 4.6 at 100.0% and $0.20 on the same 38 tasks. Every existing "DeepSeek R1 vs Claude" comparison references Claude 3.5 or 3.7 Sonnet. This DeepSeek R1 review is the first head-to-head with Opus 4.6 and Sonnet 4.6 on the same test suite.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;DeepSeek R1&lt;/th&gt;
&lt;th&gt;Claude Sonnet 4.6&lt;/th&gt;
&lt;th&gt;Claude Opus 4.6&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;96.8%&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass Rate&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost/Run&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median Time&lt;/td&gt;
&lt;td&gt;23.1s&lt;/td&gt;
&lt;td&gt;4.6s&lt;/td&gt;
&lt;td&gt;4.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Time&lt;/td&gt;
&lt;td&gt;22 min&lt;/td&gt;
&lt;td&gt;3.6 min&lt;/td&gt;
&lt;td&gt;3.3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;R1 is close on accuracy (96.8% vs Sonnet's 100%) but the latency gap is the story. R1 takes 5x longer per call than Sonnet. In agentic loops where calls chain sequentially, that compounds into minutes of dead time per task.&lt;/p&gt;

&lt;p&gt;DeepSeek V3 (Chat) is the faster alternative at 5.8s median, but drops to 88.7% quality with 89% pass rate. The R1/V3 split within DeepSeek's own lineup mirrors the Flash/Pro split within Google's: the cheaper model is dramatically faster, the expensive model buys reasoning accuracy at a steep latency cost.&lt;/p&gt;

&lt;p&gt;DeepSeek R1's weakness is consistent across benchmarks. Artificial Analysis ranks the original R1 at #32 of 65 open-weight models (Intelligence Index 27), though the updated R1 0528 scores significantly higher. Aider Polyglot has it at 56.9%. The thinking architecture buys correctness on reasoning tasks at the cost of format compliance and speed everywhere else.&lt;/p&gt;

&lt;p&gt;If latency doesn't matter and you need to minimize cost, R1 at $0.12 is competitive with Sonnet at $0.20. If response time matters at all, Sonnet wins on every dimension except price.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Codex CLI compare to Claude Code for coding tasks?
&lt;/h3&gt;

&lt;p&gt;GPT-5.2-codex scored 98.3% with a 97% pass rate for $0.16 per run, while Sonnet scored 100.0% with a 100% pass rate for $0.20. GPT-5.2-codex is the model powering Codex CLI, and Sonnet is the default model in Claude Code. The gap is small: 1.7 percentage points and one additional failed test.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Codex CLI (GPT-5.2-codex)&lt;/th&gt;
&lt;th&gt;Claude Code (Sonnet 4.6)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;98.3%&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass Rate&lt;/td&gt;
&lt;td&gt;97% (37/38)&lt;/td&gt;
&lt;td&gt;100% (38/38)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost/Run&lt;/td&gt;
&lt;td&gt;$0.16&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median Time&lt;/td&gt;
&lt;td&gt;4.6s&lt;/td&gt;
&lt;td&gt;4.6s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Tasks (C-group)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On coding tasks specifically, both scored 100%. The difference shows up in reasoning: Codex failed R4 (root cause identification), picking the proximate trigger ("traffic spike") instead of the underlying vulnerability ("pool config"). Sonnet passed it.&lt;/p&gt;

&lt;p&gt;The cost story matters more than the accuracy gap. Codex CLI is subscription-backed with ChatGPT Pro ($200/month), so usage is a predictable fixed-cost envelope instead of an open-ended per-call API bill. Sonnet costs $0.20 per 38-test run through the API, or is available via Claude Code Pro ($20/month, rate-limited) or Max ($100-200/month, higher limits). If you're already paying for ChatGPT Pro and your work is code-heavy, Codex CLI is strong value.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are MiniMax M2.5's benchmark results in 2026?
&lt;/h3&gt;

&lt;p&gt;MiniMax M2.5 scored 98.6% with a 100% pass rate for $0.069 per run and a 15.9-second median response time. It is the least-discussed model in this benchmark and one of the strongest performers, making it one of four models (alongside Sonnet, Opus, and Kimi K2.5) to pass every test.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;MiniMax M2.5&lt;/th&gt;
&lt;th&gt;Claude Sonnet 4.6&lt;/th&gt;
&lt;th&gt;Kimi K2.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;98.6%&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;98.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass Rate&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost/Run&lt;/td&gt;
&lt;td&gt;$0.069&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median Time&lt;/td&gt;
&lt;td&gt;15.9s&lt;/td&gt;
&lt;td&gt;4.6s&lt;/td&gt;
&lt;td&gt;29.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Tokens&lt;/td&gt;
&lt;td&gt;55,856&lt;/td&gt;
&lt;td&gt;11,985&lt;/td&gt;
&lt;td&gt;57,569&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Where MiniMax stands out is format compliance. It returned bare JSON on 23 of 38 responses with zero explanation text, no code blocks, no Markdown wrapping. &lt;strong&gt;No other model was that disciplined&lt;/strong&gt;. For batch pipelines where you need reliable structured output and the downstream consumer is a parser (not a human), MiniMax at $0.069 is hard to beat.&lt;/p&gt;

&lt;p&gt;MiniMax M2.5 independently ranks #4 on SWE-bench Verified (80.2%), confirming this result is not a fluke of our benchmark design.&lt;/p&gt;

&lt;p&gt;The speed penalty is real: 15.9s median, 19 minutes total for 38 tests, making it 4.4x slower than Sonnet. The thinking-model architecture generates 55,856 output tokens (4.7x more than Sonnet) because reasoning traces are included, which inflates both cost and wall-clock time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Claude Haiku as good as Sonnet?
&lt;/h3&gt;

&lt;p&gt;For most tasks, yes. Haiku scored 95.9% and passed 37 of 38 tests, handling extraction, code, data, and planning cleanly. The gap shows up in reasoning: Haiku failed R4 (root cause analysis), where it picked the surface-level trigger instead of the underlying config flaw. I ran Haiku as my default model for a week after this benchmark, and the pattern held. Batch work was fine, but multi-step debugging sessions needed Sonnet.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the cheapest LLM that actually works?
&lt;/h3&gt;

&lt;p&gt;Gemini 2.5 Flash at $0.003 for a 38-test run, with 97.1% quality and a 1.1-second median response. I use it for all my batch classification and health-check jobs because the Google Workspace free tier covers most of my volume. If you want true $0.00, GPT-oss-20b scored 98.3% running locally on a Mac Studio with 192GB RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is DeepSeek as good as Claude?
&lt;/h3&gt;

&lt;p&gt;R1 scored 96.8% vs Sonnet's 100%, so accuracy is close. The real gap is speed: R1's 23-second median means a 10-call agent chain takes nearly 4 minutes of dead time, compared to 46 seconds with Sonnet. I tested R1 in an agentic loop for cron debugging and the wait times made it unusable for interactive work, though it would be fine for overnight batch jobs where nobody is watching.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Codex CLI compare to Claude Code?
&lt;/h3&gt;

&lt;p&gt;Both hit 100% on coding tasks (C-group). The divergence is reasoning: Codex failed R4 by identifying "traffic spike" as the root cause instead of "connection pool misconfiguration." At $0.16 per run vs Sonnet's $0.20, the cost difference is marginal, but Codex's subscription model (ChatGPT Pro at $200/month) makes per-call cost predictable in a way API billing does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GPT-5.2 better than Claude Sonnet?
&lt;/h3&gt;

&lt;p&gt;In this benchmark, Sonnet scored 100.0% (38/38) and GPT-5.2 scored 98.0% (37/38). GPT-5.2 is faster at 3.0s median vs Sonnet's 4.6s, which adds up in high-volume loops. On the one test GPT-5.2 failed (W3, style-constrained writing), it produced an em dash despite the prompt explicitly prohibiting them, a formatting discipline issue rather than a reasoning failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which LLM is best for reasoning tasks?
&lt;/h3&gt;

&lt;p&gt;Sonnet 4.6 and Opus 4.6 both scored 100% across all 38 tests, including the R-group where 13.3% of all model responses failed. The specific tests that separate models are R1 (multi-step gold production calculation, where four models got the math wrong) and R4 (root cause identification, where four models picked symptoms over causes). Kimi K2.5 and MiniMax M2.5 are close at 98.6%, but they take 6-8x longer per call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is MiniMax M2.5 worth using?
&lt;/h3&gt;

&lt;p&gt;For batch pipelines, absolutely. MiniMax returned bare JSON on 23 of 38 tests with zero wrapper text, which means fewer parser failures downstream. At $0.069 per run with 100% pass rate, I'm adding it to my overnight batch rotation for structured extraction jobs. The 15.9-second median makes it too slow for interactive work, but for anything where the output feeds a script rather than a human, it is hard to beat on value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I just pay for Claude Max and stop worrying about model selection?
&lt;/h3&gt;

&lt;p&gt;The Max plan removes per-call anxiety, but three constraints remain. Data sovereignty is the biggest: some of my workloads (Canadian securities data, defense-adjacent analysis) cannot leave my infrastructure, period. Second, parallel subagents burn tokens fast, and even "unlimited" plans have effective rate limits that throttle heavy concurrent usage. Third, GPT-oss-20b at $0.00 outscores Haiku, R1, and GPT-5-Nano on these tasks. Routing by task type took me about 30 minutes to set up and saves real money every day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmarks Worth Reading
&lt;/h3&gt;

&lt;p&gt;This benchmark tests 38 practical tasks from one person's workflow. For broader coverage, these are the benchmarks and leaderboards I actually reference when evaluating models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://artificialanalysis.ai/leaderboards/models" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt;&lt;/strong&gt; - Independent quality, speed, latency, and price measurements across 100+ models, updated daily. The best single-page comparison of the dimensions practitioners actually care about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.swebench.com/" rel="noopener noreferrer"&gt;SWE-bench&lt;/a&gt;&lt;/strong&gt; - Tests whether models can resolve real GitHub issues from popular open-source repos, end-to-end. The standard measure of agentic coding capability. The Verified subset is human-validated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aider.chat/docs/leaderboards/" rel="noopener noreferrer"&gt;Aider Polyglot Leaderboard&lt;/a&gt;&lt;/strong&gt; - 225 coding exercises across 6 languages, testing both generation and iterative debugging. Polyglot scope reflects real developer work better than Python-only benchmarks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://livebench.ai/" rel="noopener noreferrer"&gt;LiveBench&lt;/a&gt;&lt;/strong&gt; - Monthly-refreshed questions with verifiable ground-truth answers, designed to combat data contamination. No LLM-as-judge, objective scoring only. Top models still score below 70%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://scale.com/leaderboard" rel="noopener noreferrer"&gt;SEAL Leaderboards (Scale AI)&lt;/a&gt;&lt;/strong&gt; - Expert-driven evaluations including software engineering, professional reasoning (finance, law), and agentic tool use. Enterprise-oriented benchmarks that other leaderboards lack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://epoch.ai/benchmarks" rel="noopener noreferrer"&gt;Epoch AI Benchmarks&lt;/a&gt;&lt;/strong&gt; - Historical performance tracking across major benchmarks, covering 3,200+ models. Useful for seeing where the trajectory is heading, not just where it is today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://magazine.sebastianraschka.com/p/a-dream-of-spring-for-open-weight" rel="noopener noreferrer"&gt;Sebastian Raschka: A Dream of Spring for Open-Weight LLMs (Jan-Feb 2026)&lt;/a&gt;&lt;/strong&gt; - Comprehensive roundup of 10 open-weight models, covering the Qwen, Gemma, and GPT-oss families this benchmark tests locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Ian Paterson is CEO of a publicly traded cybersecurity company in Canada, and has used GenAI in production since 2022 for extraction pipelines, trading automation, investment analysis, and writing workflows. He runs a motley collection of personal infrastructure ranging from cheap VPS providers, repurposesed Mac Studios and old PCs on their 6th life, mostly talking to Anthropic, Google, and OpenRouter APIs. This benchmark came out of frustration at not knowing whether his API spend was optimally allocated.&lt;/p&gt;

&lt;p&gt;For more on managing costs across multiple models, see &lt;a href="https://ianlpaterson.com/blog/inference-arbitrage-llm-routing-playbook/" rel="noopener noreferrer"&gt;how I route 200+ daily LLM calls across five models&lt;/a&gt;. I also built &lt;a href="https://ianlpaterson.com/blog/tracking-claude-codex-gemini-quotas-from-one-script/" rel="noopener noreferrer"&gt;a unified quota tracker&lt;/a&gt; that monitors rate limits across Claude, Codex, and Gemini from one script.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>benchmarking</category>
    </item>
    <item>
      <title>Inference Arbitrage: How I Route 200+ Daily LLM Calls Across Five Models</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 19:58:51 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/inference-arbitrage-how-i-route-200-daily-llm-calls-across-five-models-19ni</link>
      <guid>https://forem.com/ianlpaterson/inference-arbitrage-how-i-route-200-daily-llm-calls-across-five-models-19ni</guid>
      <description>&lt;p&gt;Inference arbitrage means routing each AI task to the cheapest model that can handle it at acceptable quality, instead of sending everything to the most expensive one. No benchmark tells you which model to use for which task at which price point. I published a &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;38-task benchmark across 15 models&lt;/a&gt; last week and the top finding was a routing principle, not a model name: match the model to the task, and most of your tasks don't need the expensive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does My AI Workday Look Like?
&lt;/h2&gt;

&lt;p&gt;I was on a flight last month, SSH'd into a cloud server over spotty airplane wifi, half a dozen subagents running in parallel. I watched my weekly token allocation drain faster than I'd planned, and by the time I landed I was rationing for the rest of the week.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz0b46u8vus1et5zikwq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz0b46u8vus1et5zikwq.png" alt="Claude Code status line showing token consumption percentage" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I now plan heavy jobs around &lt;a href="https://ianlpaterson.com/blog/tracking-claude-codex-gemini-quotas-from-one-script/" rel="noopener noreferrer"&gt;the weekly reset cycle&lt;/a&gt;. Monday, when the budget is flush, I queue the expensive reasoning tasks. By Thursday, everything possible routes to cheaper models or defers to the next cycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop1laqoe9huv7snogutl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop1laqoe9huv7snogutl.png" alt="Token tracking dashboard showing daily consumption and weekly budget" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I parsed my Claude Code logs from Feb 28 through Mar 2 and categorized every session by task type.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task Type&lt;/th&gt;
&lt;th&gt;Sessions&lt;/th&gt;
&lt;th&gt;Avg Duration&lt;/th&gt;
&lt;th&gt;Estimated Calls/Day&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coding / system work / ops&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;~45m&lt;/td&gt;
&lt;td&gt;50-80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data analysis&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;~45m&lt;/td&gt;
&lt;td&gt;25-40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;~35m&lt;/td&gt;
&lt;td&gt;15-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing / content&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;~60m&lt;/td&gt;
&lt;td&gt;20-35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email / comms&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;~30m&lt;/td&gt;
&lt;td&gt;5-10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A typical day runs 80-120 API calls during interactive work, plus 50-200 from automated scripts. Peak days during benchmark development spiked to 7,700 calls (during benchmark automation, not typical usage). I'm a Claude Max subscriber, so take the daily-driver recommendation with that context.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Five-Model Stack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sonnet (Claude Code, daily driver).&lt;/strong&gt; Where I spend most of my time. Sonnet handles everything interactive: coding, debugging, file edits, writing, planning. It scored 100% on my benchmark at $0.20/run with a 4.6s median response, and for my call volume the quality-to-cost ratio is unmatched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opus (escalation model).&lt;/strong&gt; When Sonnet gets something wrong or I'm debugging a genuinely hard problem, I escalate. Opus also scored 100%, but at $0.69/run, a 3.5x premium for zero additional quality on most tasks. Where it earns that premium: ambiguous reasoning, multi-step causal chains, and problems where the first answer needs to be right because verification is expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Codex subagents (cross-checking and cost spreading).&lt;/strong&gt; I run OpenAI's Codex CLI as a deliberately separate inference channel, spreading token consumption across subscription plans and cross-checking Opus's work. Same problem, both models, compare answers: agreement means high confidence, disagreement tells me where to dig. GPT-5.2-codex scored 98.3% on the benchmark, and a second opinion from a differently-architected model has caught real bugs that single-model workflows miss. During one refactor last week, Codex flagged a race condition in a monitoring script that Sonnet had approved twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini Flash CLI (research and file reads).&lt;/strong&gt; Gemini reads local files via &lt;code&gt;@file&lt;/code&gt; syntax, has built-in Google Search, and runs fast enough that I've burned through 1,000 calls in a single research sprint. I once needed founding dates and employee counts for 100 companies, and Gemini had it done in five minutes flat while Claude's budget stayed untouched. Every Gemini query is one that doesn't count against my Claude budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qwen 3.5 35B on-prem (Mac Studio, async work).&lt;/strong&gt; The slowest model in my stack, running through &lt;a href="https://ianlpaterson.com/blog/openclaw-setup-apple-silicon-local-llm/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; on a Mac Studio. Qwen handles cron jobs, overnight batch processing, and anything I can queue and forget: sovereignty (nothing leaves the machine) and cost (free after hardware), scoring 85.8% on the benchmark. Solid for extraction and code, but only 60% on reasoning. I tried it on a reasoning-heavy debugging session once and lost 20 minutes before escalating to Sonnet.&lt;/p&gt;

&lt;p&gt;Full &lt;a href="https://ianlpaterson.com/blog/lm-studio-fix-cannot-truncate-prompt-n-keep-n-ctx/" rel="noopener noreferrer"&gt;LM Studio setup and tuning guide&lt;/a&gt; for Apple Silicon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Routing Decision Tree
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                ┌──────────────────┐
                                │      OPUS        │
                                │ Complex reasoning│
                                │ $0.69/call       │
                                └────────┬─────────┘
                                         │
┌────────────────────┐          ┌────────┴────────┐          ┌────────────────────┐
│   QWEN LOCAL       │──────────│     SONNET      │──────────│     GEMINI         │
│   Sensitive data   │          │    (default)     │          │  Research, free    │
│   Overnight batch  │          │   $100/mo Max    │          │  @file, web search │
│   $0 (on-prem)     │          │   100% quality   │          │  1,000 calls/day   │
└────────────────────┘          └────────┬────────┘          └────────────────────┘
                                         │
                                ┌────────┴─────────┐
                                │     CODEX        │
                                │  Cross-check     │
                                │  Diff. arch.     │
                                │  $20/mo Plus     │
                                └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three heuristics drive most of the routing (38 tasks, so treat these percentages as directional):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive data stays on-prem.&lt;/strong&gt; Anything touching client work or regulated industries goes to Qwen local, regardless of quality scores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning tasks pay for frontier.&lt;/strong&gt; Extraction and simple code score 100% on every model including free ones, but reasoning and planning show a 20-44 point gap between free and premium.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else defaults to Sonnet.&lt;/strong&gt; 100% across all categories at $0.20/run, and Claude Code's native file access makes it the only option for agentic coding loops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I choose models at session and tool level, while Claude Code handles sub-call routing internally. In my session data, 12.2% of calls were auto-routed to Haiku for simple tasks like file reads and short bash commands, regardless of the parent session's model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Models, Costs, and Why Each One's There
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Tasks&lt;/th&gt;
&lt;th&gt;Subscription&lt;/th&gt;
&lt;th&gt;Per-Call Cost&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Sonnet&lt;/strong&gt; (Claude Code)&lt;/td&gt;
&lt;td&gt;Interactive coding, debugging, file edits, writing, planning&lt;/td&gt;
&lt;td&gt;$100/mo (Max 5x)&lt;/td&gt;
&lt;td&gt;~$0.20/call&lt;/td&gt;
&lt;td&gt;100% quality, 4.6s median, native file access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Opus&lt;/strong&gt; (Claude Code)&lt;/td&gt;
&lt;td&gt;Complex reasoning, ambiguous problems, escalation&lt;/td&gt;
&lt;td&gt;Included in Max ($0.69 at API rates)&lt;/td&gt;
&lt;td&gt;~$0.69/call&lt;/td&gt;
&lt;td&gt;3.5x premium justified on multi-step reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;GPT-5.2-codex&lt;/strong&gt; (Codex CLI)&lt;/td&gt;
&lt;td&gt;Cross-checking critical decisions, parallel work&lt;/td&gt;
&lt;td&gt;$20/mo (ChatGPT Plus)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;Different architecture catches different bugs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Gemini Flash&lt;/strong&gt; (CLI + API)&lt;/td&gt;
&lt;td&gt;Research, web lookups, file summaries, bulk classification&lt;/td&gt;
&lt;td&gt;$0 (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Built-in search, 1.1s response, 1,000 calls/day free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Qwen 3.5&lt;/strong&gt; (Mac Studio, local)&lt;/td&gt;
&lt;td&gt;Overnight batch, cron jobs, extraction&lt;/td&gt;
&lt;td&gt;$0 (on-prem)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Sovereign, 100% on extraction, 97% on code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total monthly spend: $120/mo. At API rates, my typical 80-120 daily interactive Sonnet calls alone would cost $480-720/mo, so the Max subscription pays for itself on volume before accounting for Opus access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capability Constraints and Quality Gaps by Task Type
&lt;/h3&gt;

&lt;p&gt;Not every model can do everything. Before routing by quality, check whether the model even supports the capability you need.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Web Search&lt;/th&gt;
&lt;th&gt;Usage Limits&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 3.5 local&lt;/td&gt;
&lt;td&gt;Needs API key&lt;/td&gt;
&lt;td&gt;Single-threaded&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini Flash CLI&lt;/td&gt;
&lt;td&gt;Yes (built-in Google)&lt;/td&gt;
&lt;td&gt;1,000/day free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code (Sonnet/Opus)&lt;/td&gt;
&lt;td&gt;Limited (WebFetch)&lt;/td&gt;
&lt;td&gt;225/5hr (Max 5x)&lt;/td&gt;
&lt;td&gt;$100/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex (via ChatGPT Plus)&lt;/td&gt;
&lt;td&gt;Yes (browser)&lt;/td&gt;
&lt;td&gt;Quota-based&lt;/td&gt;
&lt;td&gt;$20/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quality varies dramatically by task type. These numbers come from my &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;38-task benchmark across 15 models&lt;/a&gt;, grouped by category to show where cheap models hold up and where they fall apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task Category&lt;/th&gt;
&lt;th&gt;Free Models&lt;/th&gt;
&lt;th&gt;Cheap Paid&lt;/th&gt;
&lt;th&gt;Premium&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extraction&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Use cheapest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple code&lt;/td&gt;
&lt;td&gt;97-100%&lt;/td&gt;
&lt;td&gt;97-100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0-3%&lt;/td&gt;
&lt;td&gt;Use cheapest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex code + reasoning&lt;/td&gt;
&lt;td&gt;60-100%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;13-40%&lt;/td&gt;
&lt;td&gt;Pay for frontier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing&lt;/td&gt;
&lt;td&gt;77-96%&lt;/td&gt;
&lt;td&gt;89-100%&lt;/td&gt;
&lt;td&gt;97-100%&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;td&gt;Context-dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planning + system health&lt;/td&gt;
&lt;td&gt;50-94%&lt;/td&gt;
&lt;td&gt;94-100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;25-44%&lt;/td&gt;
&lt;td&gt;Pay for frontier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data analysis&lt;/td&gt;
&lt;td&gt;75-80%&lt;/td&gt;
&lt;td&gt;75-95%&lt;/td&gt;
&lt;td&gt;95-100%&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;Pay for frontier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investments&lt;/td&gt;
&lt;td&gt;83-87%&lt;/td&gt;
&lt;td&gt;87-100%&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;Use cheapest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the gap between free and paid exceeds 10 percentage points, pay for frontier. Below 10, free or cheap is fine, and the savings compound across hundreds of calls per week. Paying Opus rates for extraction is a 17x premium for zero quality improvement. Routing reasoning tasks to Qwen means getting the wrong answer 40% of the time.&lt;/p&gt;

&lt;p&gt;The full quality and cost breakdown across all 15 models is in &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;my 38-task LLM benchmark&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing in Practice: Three Real Examples
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Web research batch (100 companies, pulling founding year, HQ, employee count, latest funding).&lt;/strong&gt; Gemini Flash handles this in 5 minutes at $0 because it's the only programmable option with built-in web search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Categorize 1,000 local files.&lt;/strong&gt; Qwen local runs overnight at $0 but takes 107 minutes; Gemini Flash finishes in 17 minutes via &lt;code&gt;@file&lt;/code&gt; syntax. Claude Code Max could do it, but burning a $100/mo subscription on classification wastes its real value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean up a 100-file codebase.&lt;/strong&gt; Claude Code Max is the only option that autonomously navigates a repo, edits files, runs tests, and recovers from errors, so there's no real alternative for this class of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are You Actually Paying For?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Free Only&lt;/th&gt;
&lt;th&gt;Split Stack&lt;/th&gt;
&lt;th&gt;Max 5x + Supplements (my setup)&lt;/th&gt;
&lt;th&gt;Max 20x + Supplements&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$10 (electricity for the Mac Studio)&lt;/td&gt;
&lt;td&gt;~$40&lt;/td&gt;
&lt;td&gt;~$120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What's included&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Qwen local + Gemini free + gpt-oss-20b via OpenRouter&lt;/td&gt;
&lt;td&gt;Claude Pro ($20) + ChatGPT Plus ($20) + Gemini free + Qwen local&lt;/td&gt;
&lt;td&gt;Claude Max 5x ($100) + ChatGPT Plus ($20) + Gemini free + Qwen local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Message limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None (local), 1,000/day (Gemini)&lt;/td&gt;
&lt;td&gt;Rolling caps on Claude Pro and ChatGPT&lt;/td&gt;
&lt;td&gt;225/5hr window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Privacy-sensitive work, budget-zero&lt;/td&gt;
&lt;td&gt;Individual devs, &amp;lt;4 hrs AI coding/day&lt;/td&gt;
&lt;td&gt;Most professional developers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free tier can't do agentic coding without building the plumbing yourself, and reasoning accuracy drops to 60%. The split stack at $40/mo gets to 95-97% quality, but Claude Pro's rolling usage cap will hit at the worst moment, deep into a complex session. Max 5x at $100/mo is the practical sweet spot for most work: Sonnet and Opus on demand, native file access, and 225 messages per 5-hour window that most sessions don't exhaust.&lt;/p&gt;

&lt;p&gt;I use Max 5x ($100/mo), and most weeks the 225-message/5hr ceiling is enough. Some weeks I barely touch it. Other weeks, batch jobs and sustained research sessions push past it by Tuesday, and I'm rationing or deferring work to the next reset window. Max 20x ($200/mo) would eliminate that ceiling anxiety, but I haven't found the extra $100/mo justified yet. For competitive context, ChatGPT Plus runs $20/mo, Google AI Ultra hits $250/mo, and SuperGrok is $30/mo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provider Trust and Jurisdiction Risk
&lt;/h2&gt;

&lt;p&gt;DeepSeek R1 scores 96.8% and MiniMax M2.5 hits 98.6% at $0.07/run, so the quality is genuinely competitive. The question is whether you trust the provider's data handling. The Canadian federal government restricted DeepSeek from government devices in February 2025, BC banned it from provincial devices, and in February 2026 Anthropic alleged that DeepSeek, Moonshot AI, and MiniMax ran &lt;a href="https://www.anthropic.com/news/detecting-and-preventing-distillation-attacks" rel="noopener noreferrer"&gt;coordinated distillation attacks targeting Claude&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My position: less-trusted models for personal experimentation on non-sensitive data, kept away from client work or regulated industries. Running them via OpenRouter routes calls through US infrastructure, which reduces but doesn't eliminate the risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Benchmark Meets Practice
&lt;/h2&gt;

&lt;p&gt;The benchmark suggests Haiku (95.9%, $0.04/run) is the optimal cost-quality model, and Claude Code already routes Haiku-appropriate calls (short responses, file reads, simple bash) automatically. In my session data, roughly 70% of calls fit that profile.&lt;/p&gt;

&lt;p&gt;But I also offload work to Gemini Flash that would otherwise burn Haiku calls against my Claude quota. Gemini is faster (1.1s vs 2s), has built-in web search, and doesn't count against my Max 5x message ceiling at all. Every file summary or web lookup I route to Gemini is one fewer call ticking down my 225-message window.&lt;/p&gt;

&lt;p&gt;Claude Code doesn't expose per-turn routing decisions, so the gap between the optimal routing table and what the tooling actually supports is where real savings sit.&lt;/p&gt;

&lt;p&gt;For the practitioner companion piece - what happens when a routed session fails to catch a subtle bug because the deterministic tool isn't in the pipeline - see &lt;a href="https://ianlpaterson.com/blog/llm-kept-saying-fixed-three-months/" rel="noopener noreferrer"&gt;The LLM Kept Saying ‘Fixed.’&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does extended thinking burn more Claude Max quota?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Thinking tokens count against your quota, and a thinking-heavy model generates roughly 5x more tokens than standard. On Max 5x (225 messages/5hr), heavy thinking hits the ceiling 3-4x faster than standard Sonnet calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I spawn a Haiku subagent from Claude Code, does that count as a Haiku call or Sonnet call?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code's subagent routing is opaque but observable. In my session data, 12.2% of calls were routed to Haiku automatically, and subagent-heavy workflows are more quota-efficient than single-thread sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not use an ML-based router like RouteLLM?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For my workflow (80-120 calls/day, 5 models), the routing logic fits in my head: extraction goes cheap, reasoning goes frontier, everything else defaults to Sonnet. The router overhead only makes sense at enterprise scale where per-call savings outweigh the routing infrastructure cost across millions of calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I use a thinking model for agentic coding loops?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generally no. Thinking models add 10-25 seconds of latency above baseline per call for the reasoning phase, and in agentic loops with 50+ sequential calls that compounds to roughly 24 minutes of wall clock time versus about 1.7 minutes with Haiku at 2s per call. Use thinking models for single high-stakes decisions and fast models for the iterative loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it worth running a local model daily?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, but depends on workload mix. Qwen 3.5 locally scores 100% on extraction and 97% on code, covering overnight batch jobs at zero marginal cost. The tradeoff: ~29s median response and 60% reasoning accuracy versus 100% for frontier. If you have batch work that can run overnight and care about data sovereignty, a local model pays for itself in the first month.&lt;/p&gt;

&lt;p&gt;If you want the full build recipe for a CUDA-side local setup, I documented the &lt;a href="https://ianlpaterson.com/blog/llama-cpp-3090-ti-dell-t5820/" rel="noopener noreferrer"&gt;from-source llama.cpp build on a Dell T5820 and RTX 3090 Ti&lt;/a&gt; separately, including the boot-loop fix and the production server flags. For the three months of speed tuning that followed (autoregressive baseline, DFlash, MTP, ending at 39 to 49 tok/s on a single 3090 Ti), see &lt;a href="https://ianlpaterson.com/blog/3090-ti-qwen-speedup-dflash-mtp/" rel="noopener noreferrer"&gt;Three Months of Speed-Up Experiments on a 3090 Ti&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use DeepSeek or other less-trusted providers for production work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The quality is real (R1 scored 96.8%), but several governments have restricted specific providers, and Anthropic has documented &lt;a href="https://www.anthropic.com/news/detecting-and-preventing-distillation-attacks" rel="noopener noreferrer"&gt;distillation attacks&lt;/a&gt; by some. For anything touching client data, trusted providers or local only.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Companion to&lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;LLM Benchmark 2026: 38 Actual Tasks, 15 Models for $2.29&lt;/a&gt;, which has the full quality, cost, and speed data across all 15 models. The benchmark test suite and scoring harness are on GitHub.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>Stop Claude Code from Lobotomizing Itself Mid-Task</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 19:58:28 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/stop-claude-code-from-lobotomizing-itself-mid-task-15fl</link>
      <guid>https://forem.com/ianlpaterson/stop-claude-code-from-lobotomizing-itself-mid-task-15fl</guid>
      <description>&lt;p&gt;Claude Code has a feature called auto-compact that quietly destroys your session quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I was three hours into a multi-file refactoring session, had just finished explaining which modules needed interface changes and which were already done. Auto-compact fired at 80% context. When Claude came back, it had no idea which files had been edited and which still needed changes. It suggested modifying a file I'd finished an hour earlier and forgot a constraint I'd repeated twice. I had to re-explain the entire task from scratch.&lt;/p&gt;

&lt;p&gt;The community calls this "lobotomization" and it's accurate. Post-compaction Claude loses track of what repo you're in, forgets constraints you set, drops skills you invoked. The quality drop is immediate and obvious.&lt;/p&gt;

&lt;p&gt;A note on terminology: throughout this post I reference /flush, /project, and other slash commands. These are custom Claude Code skills I built (stored as markdown files in ~/.claude/commands/), not built-in features. /project loads a project's saved state (CLAUDE.md with Working/Blocked/Next, domain topic files, recent daily logs) so Claude knows what happened last session. /flush captures the current session state to multiple places (daily log, project state, MEMORY.md, topic files) so the next session can pick up where this one left off. Think of /project as "load game" and /flush as "save game." The skill files are simple markdown prompts that Claude follows as instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Reality
&lt;/h2&gt;

&lt;p&gt;Community analysis of Claude Code's minified source found a hardcoded buffer that triggers compaction. The exact value has changed across versions (it was ~13k in early 2026, reportedly ~33k in later builds), but the mechanism is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;jbA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;13000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;lwB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UhT&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// Available input (context - max_output_tokens)&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;jbA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Subtract 13k buffer&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// This is where auto-compact triggers&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buffer Reserved = max output tokens + safety buffer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output Token Setting&lt;/th&gt;
&lt;th&gt;Buffer Reserved&lt;/th&gt;
&lt;th&gt;Usable Context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;64k (max)&lt;/td&gt;
&lt;td&gt;77k (38.5%)&lt;/td&gt;
&lt;td&gt;123k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32k (default)&lt;/td&gt;
&lt;td&gt;45k (22.5%)&lt;/td&gt;
&lt;td&gt;155k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-compact off&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;200k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With default settings, you're losing 22.5% of your context window to a safety buffer you may never need.&lt;/p&gt;

&lt;p&gt;These numbers reflect the ~13k buffer from early 2026 builds. If the buffer has increased in your version, the usable context will be lower. The principle holds regardless: auto-compact reserves a meaningful chunk of your context window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Research on iterative context rewriting (arXiv 2510.04618) calls it "context collapse": when LLMs rewrite their own context iteratively, accuracy drops. Claude Code's auto-compact does exactly this.&lt;/p&gt;

&lt;p&gt;Compaction is lossy compression. You're asking an LLM to decide what's "important" and discard the rest. For the kind of work I do (multi-file refactors, constraint-heavy debugging sessions, anything where I've spent twenty minutes explaining what not to touch), that's a bad tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Add one line to &lt;code&gt;~/.claude.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"autoCompactEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or run &lt;code&gt;/config&lt;/code&gt; in an active session and toggle "Auto-compact enabled" off.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Workflow
&lt;/h2&gt;

&lt;p&gt;With auto-compact disabled, you get the full 200k context window with no buffer held in reserve. No surprise compaction mid-task. The session stays coherent until you decide otherwise.&lt;/p&gt;

&lt;p&gt;When I do need to reclaim context, I use &lt;code&gt;/compact&lt;/code&gt; with explicit instructions: &lt;code&gt;/compact "focus on the authentication refactor, discard the earlier debugging"&lt;/code&gt;. This way I control what survives instead of letting Claude guess. More often, though, the better move is &lt;code&gt;/export&lt;/code&gt; to save the conversation, then start a fresh session and have Claude read it back in. Fresh context, curated history, no lossy summarization.&lt;/p&gt;

&lt;p&gt;I also dropped my flush threshold from 75% to 60%. When context hits 60%, Claude prompts me to run &lt;code&gt;/flush&lt;/code&gt; before continuing. This captures the session state while there's still room to maneuver.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tradeoff
&lt;/h2&gt;

&lt;p&gt;Disabling auto-compact means sessions will hit the context limit. You'll need to manually compact, export and restart, or finish the task before running out. I'd rather use that 45k toward skills, rules, and context I deliberately set up than reserve it for an automatic summarization that makes Claude worse.&lt;/p&gt;

&lt;p&gt;Anthropic's own guidance has moved toward recommending auto-compact stay enabled, and for simple single-task sessions that's reasonable. For constraint-heavy work where I've built up significant context (multi-file refactors, debugging with specific rules about what not to touch), the automatic summarization loses too much.&lt;/p&gt;

&lt;p&gt;Claude Code now supports 1M token context windows on some plans, which reduces the urgency of this problem. But context hygiene still matters at any window size. A 1M window fills up the same way a 200k window does, just slower. Knowing when to flush deliberately versus letting auto-compaction guess is a workflow discipline, not a context size question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session Management (Beyond Compaction)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Context % Statusline
&lt;/h3&gt;

&lt;p&gt;I have a custom statusline script that shows &lt;code&gt;[XX%]&lt;/code&gt; at every prompt so I always know how much context is left. The script reads &lt;code&gt;context_window.used_percentage&lt;/code&gt; from Claude Code's JSON input and renders it inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.claude/statusline-command.sh&lt;/span&gt;
&lt;span class="nv"&gt;PERCENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.context_window.used_percentage // 0'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[01;32m%s@%s&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[00m:&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[01;34m%s&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[00m [%s%%]"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cwd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PERCENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Emergency Rescue
&lt;/h3&gt;

&lt;p&gt;When you hit &amp;gt;90% and can't even run /flush (the output won't fit), the workaround is a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# In the dying session:
"Write a summary of everything we've done to ~/session-dump.txt"

# In the fresh session:
"Read ~/session-dump.txt and run /flush for those projects"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dying session has enough context left to write a file. The fresh session has enough context to process it. One session as the brain, the other as the hands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thresholds
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context %&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;60%&lt;/td&gt;
&lt;td&gt;Work normally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;td&gt;Claude suggests /flush&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;td&gt;Claude auto-runs /flush (non-interactive)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt;90%&lt;/td&gt;
&lt;td&gt;Emergency: dump to file, rescue from new session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;Session dead. Hope you flushed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Session Lifecycle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Session Start: /project
&lt;/h3&gt;

&lt;p&gt;Every session starts with &lt;code&gt;/project &amp;lt;name&amp;gt;&lt;/code&gt;, which fuzzy-matches against a project index, reads the project's CLAUDE.md (including the State section with Working/Blocked/Next), pulls in domain-specific topic files, and checks recent daily logs for open threads. After that, Claude knows what I was doing last session, what's blocked, and what's next. I've wasted enough time re-explaining context to treat this as non-optional.&lt;/p&gt;

&lt;h3&gt;
  
  
  During the Session
&lt;/h3&gt;

&lt;p&gt;MEMORY.md is always available (auto-loaded). Topic files get pulled when the task crosses domains. Context files from &lt;code&gt;llm-context/&lt;/code&gt; are loaded as needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session End: /flush
&lt;/h3&gt;

&lt;p&gt;Think of &lt;code&gt;/flush&lt;/code&gt; as a save game hotkey. You just scraped through a gnarly debugging session, you're at 65% context, and the next task is going to be heavy. &lt;code&gt;/flush&lt;/code&gt; captures session state to three or four places: daily log, project state (Working/Blocked/Next), MEMORY.md for new lessons, and optionally a domain topic file if something specialized came up. All append-only, new entries prepended, old entries never dropped.&lt;/p&gt;

&lt;p&gt;I learned the hard way that skipping /flush before switching tasks means the next session starts cold. Claude picks up CLAUDE.md and has no idea what happened in the gap.&lt;/p&gt;

&lt;p&gt;I watched this exact failure mode play out over three months on a cron health system, where each cold-start session ratified a broken fix (see: &lt;a href="https://ianlpaterson.com/blog/llm-kept-saying-fixed-three-months/" rel="noopener noreferrer"&gt;The LLM Kept Saying ‘Fixed.’&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  After /flush: Why /clear Beats /compact
&lt;/h3&gt;

&lt;p&gt;After flushing, I use &lt;code&gt;/clear&lt;/code&gt; instead of &lt;code&gt;/compact&lt;/code&gt;. Compaction spends tokens on a lossy summary. &lt;code&gt;/clear&lt;/code&gt; costs zero tokens and gives a clean 200k window. The memory system means nothing is lost: /flush already persisted everything worth keeping, and &lt;code&gt;/project&lt;/code&gt; reloads it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/compact&lt;/code&gt; still has a use case mid-session (at ~70% context, same task, don't want to reload everything). For session boundaries, &lt;code&gt;/clear&lt;/code&gt; is strictly better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Daily Cron
&lt;/h3&gt;

&lt;p&gt;Drift check confirms indexes match filesystem reality. Heartbeat alerts for overdue P1 tasks get written to the daily log automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Flow Summary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/project (read) → work → /flush (write) → /clear → /project (read) → ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each session starts where the last one left off, without carrying the previous session's token baggage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooks as Workflow Enforcement
&lt;/h2&gt;

&lt;p&gt;Claude Code's &lt;code&gt;settings.json&lt;/code&gt; supports lifecycle hooks: &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, and &lt;code&gt;PermissionRequest&lt;/code&gt;. These fire on every tool call, and the output gets injected into the conversation as a system reminder. Most people use them for security (blocking dangerous commands). But they're also the best way to enforce workflow patterns that instructions alone can't guarantee.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Forcing a Plan Template
&lt;/h3&gt;

&lt;p&gt;I have a plan template at &lt;code&gt;~/llm-context/plan-template.md&lt;/code&gt; with pre-flight checklists, agent routing guidance, TDD decisions per step, and a verification matrix. CLAUDE.md says "use this template for any plan with 3+ steps." Claude follows this instruction maybe 70% of the time. When it doesn't, the plan is worse: missing verification steps, no fail-fast conditions, no agent routing.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;code&gt;PostToolUse&lt;/code&gt; hook on &lt;code&gt;EnterPlanMode&lt;/code&gt;. When Claude enters plan mode, a 5-line shell script fires and injects the entire plan template into the conversation as a system reminder. Claude can't miss it because the template is literally in the conversation at the moment planning starts.&lt;/p&gt;

&lt;p&gt;The hook script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;TEMPLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/llm-context/plan-template.md"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPLATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"MANDATORY: Use the following plan template. Follow its structure exactly."&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPLATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settings.json entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EnterPlanMode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/plan-mode-template.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Works Better Than Instructions
&lt;/h3&gt;

&lt;p&gt;A PostToolUse hook injects the template into the conversation at the moment it's needed. Claude can't miss what's already on screen. CLAUDE.md instructions compete for attention with everything else in the system prompt, and compliance hovers around 70%. The hook makes it 100%.&lt;/p&gt;

&lt;p&gt;Any time you catch yourself thinking "Claude keeps forgetting to do X before Y," that's a hook. I have a PostToolUse on WebFetch that injects prompt injection warnings after fetching external content (caught a real issue once when a scraped page had instructions embedded in it). I have a PreToolUse on Bash that adds safety checks before shell commands in my infrastructure directories. A PostToolUse on Write/Edit that reminds Claude to lint after file modifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Belongs Here vs Memory Architecture
&lt;/h3&gt;

&lt;p&gt;The session management side is the easy part. How the memory files are structured, what goes in MEMORY.md vs topic files vs project state, is the part most people get wrong. That's a separate post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;LLM Benchmark Rankings 2026&lt;/a&gt; | &lt;a href="https://ianlpaterson.com/blog/inference-arbitrage-llm-routing-playbook/" rel="noopener noreferrer"&gt;LLM Routing Playbook&lt;/a&gt; | &lt;a href="https://ianlpaterson.com/blog/tracking-claude-codex-gemini-quotas-from-one-script/" rel="noopener noreferrer"&gt;API Quota Dashboard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>The LLM Kept Saying “Fixed.” For Three Months, It Wasn’t.</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 19:56:26 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/the-llm-kept-saying-fixed-for-three-months-it-wasnt-20nn</link>
      <guid>https://forem.com/ianlpaterson/the-llm-kept-saying-fixed-for-three-months-it-wasnt-20nn</guid>
      <description>&lt;p&gt;That afternoon a Slack bot told me a script had NEVER RUN. That was a lie. The script had pulled 81 weather observations two minutes earlier. Unwinding the lie took three hours.&lt;/p&gt;

&lt;p&gt;The bigger lie had been running for three months underneath it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three months of "got it"
&lt;/h2&gt;

&lt;p&gt;Before the session in this post, the cron health alert had been firing two or three times a week for three months. Each time, I'd paste the alert into a Claude Code session and ask the LLM to figure out why a script was reporting NEVER RUN. Each time the LLM would root around, land on something plausible, propose a fix, and confirm it with some variation of "yep, that's it, we got it." I'd apply the fix and move on.&lt;/p&gt;

&lt;p&gt;The fix was never the fix. Some of the time the LLM had just pushed &lt;code&gt;alerted_until&lt;/code&gt; a few months forward, quieting the alert without touching the structural bug. Some of the time it edited the wrong file. Either way the alert came back within a week or two on a different script, and the loop rolled forward.&lt;/p&gt;

&lt;p&gt;Each session was a cold start. The model had no memory of the previous session, of the pattern, of anything. The workflow failure was mine. I was treating fifteen independent debugging sessions as if they were one ongoing conversation, and the model was only seeing the one in front of it.&lt;/p&gt;

&lt;p&gt;I had an inbox and a model saying "got it," and that was enough.&lt;/p&gt;

&lt;p&gt;I run about sixty-six scheduled scripts on a personal VPS. This is one story from that pile. I'd &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;benchmarked the frontier models a few weeks earlier&lt;/a&gt;. The tier mapping that came out of it was fine for planning work. It was not sufficient for catching hallucinated fixes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What was actually broken
&lt;/h2&gt;

&lt;p&gt;The cron health monitor is a dead man's switch. Every scheduled script is supposed to send a heartbeat ping on each run. If the monitor doesn't see a ping within the expected window, it fires a Slack alert.&lt;/p&gt;

&lt;p&gt;Healthchecks.io, Cronitor, and Dead Man's Snitch all solve this as a service: you get an HTTP endpoint per check, you hit it from your cron script, and they alert you if a ping goes missing. My system was a homegrown version of the same pattern, which is why the bugs described here were possible. A SaaS monitor would have refused to let me register a slug that had never sent a ping.&lt;/p&gt;

&lt;p&gt;The architecture had three components that had to agree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; crontab.txt
  (slug tag)
      |
      v
checks.json
(registry) ------&amp;gt; source code
      |           (health_run())
      |                  |
      +------&amp;gt; pings.json &amp;lt;------+
              (runtime record)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing enforced the edges. You could register a script in &lt;code&gt;checks.json&lt;/code&gt; without adding a &lt;code&gt;health_run()&lt;/code&gt; call. You could tag a cron line with a slug that didn't exist in the registry. You could mute an alert indefinitely without touching source.&lt;/p&gt;

&lt;p&gt;Every new cron script I'd shipped had reproduced the same bug. Registered in the registry, no ping call in source, alert fires, alert gets muted. The monitor was doing its job. I was systematically ignoring it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Didn't we just fix this?" - Me, that afternoon, wrong.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The bug that would have wiped everything
&lt;/h2&gt;

&lt;p&gt;I opened a session and put Opus 4.6 on architecture review while Codex CLI (GPT-5) rewrote the validator and tagged 66 cron lines with their slugs. About thirty-five minutes, start to finish.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"[K-2SO] The chaos has been slightly inconvenienced." - Opus 4.6, after round 2, before we discovered the &lt;code&gt;crontab-apply.sh&lt;/code&gt; bug that would have silently deleted every scheduled job on the VPS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(K-2SO is the sardonic persona I prompt Claude Code with.)&lt;/p&gt;

&lt;p&gt;Four audit passes in, Sonnet 4.5 with &lt;code&gt;shellcheck&lt;/code&gt; wired in flagged a bug in &lt;code&gt;crontab-apply.sh&lt;/code&gt; that neither Opus nor Codex had caught during implementation or the cold audit round. The script was supposed to install a new crontab safely. The actual sequence was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# BEFORE: install first, verify after&lt;/span&gt;
crontab new.txt                             &lt;span class="c"&gt;# already live&lt;/span&gt;
crontab &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; verify.txt
diff crontab.txt verify.txt &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1       &lt;span class="c"&gt;# too late&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the diff failed, the script exited 1. The new crontab was already live. A malformed &lt;code&gt;crontab.txt&lt;/code&gt; would have wiped every scheduled job on the VPS with no restore path.&lt;/p&gt;

&lt;p&gt;The fix is obvious once you see it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AFTER: verify first, install only on pass&lt;/span&gt;
crontab &lt;span class="nt"&gt;-n&lt;/span&gt; new.txt &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; restore_backup&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
crontab new.txt
crontab &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; verify.txt
diff crontab.txt verify.txt &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; restore_backup&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This bug had been in the script since the script was written. Opus and Codex both looked at the file and missed it. I never looked at it at all. I was trusting the two frontier models to flag anything off.&lt;/p&gt;

&lt;p&gt;Before this session, shellcheck wasn't in my review pipeline at all. When Sonnet 4.5 caught the bug on Round 4, it wasn't because the model out-reasoned Opus and Codex. The &lt;code&gt;qa-bash&lt;/code&gt; skill wires shellcheck into the review. Once shellcheck scanned the file, it flagged the order-of-operations pattern on its own. Sonnet read the output and passed it upstream. A validator that always passes isn't a validator. It's a confidence injection machine. I had been using the whole session pipeline as one for three months.&lt;/p&gt;




&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session length:            3 hours
Distinct bugs found:       18 (9 during implementation wave, 9 across 4 audit passes)
Audit passes:              4 (Codex cold audit + qa-bash + qa-python + Opus final)
Pass-by-pass bug count:    5, 1, 3, 0
Cron lines tagged:         66
Coverage:                  0% -&amp;gt; 94% (86 tests)
Scripts never pinging:     handful -&amp;gt; 1 (legitimate edge case)
Alerts muted to 2099:      15 -&amp;gt; 1 (legitimate intentional mute)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key lessons when working with LLMs
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Use deterministic tools wherever possible
&lt;/h2&gt;

&lt;p&gt;Shellcheck, &lt;code&gt;pytest-cov&lt;/code&gt;, &lt;code&gt;mypy&lt;/code&gt;, a type checker, a linter, a schema validator, any tool that either finds a bug or doesn't find a bug with no probabilistic layer in between is the first thing you should reach for. LLMs are useful for everything that can't be checked deterministically, but stacking more LLM passes is not a substitute for a single deterministic tool with domain-specific rules.&lt;/p&gt;

&lt;p&gt;The LLM on its own had confidently endorsed broken fixes for three months. A shell linter caught a crontab-wipe bug on its first scan.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Picking which LLM for each pass
&lt;/h2&gt;

&lt;p&gt;The tier mapping I use with Claude Code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opus 4.6&lt;/strong&gt; : architecture review, session planning, final-pass oversight. Best at noticing what's missing from a diff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex CLI (GPT-5)&lt;/strong&gt; : implementation. Writes the code fastest and sticks closest to the plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sonnet 4.5&lt;/strong&gt; : skill-driven QA passes where a deterministic tool is wired in. I use two custom Claude Code skills, &lt;code&gt;qa-bash&lt;/code&gt; (which runs shellcheck) and &lt;code&gt;qa-python&lt;/code&gt; (which runs pytest, pytest-cov, and mypy). The model drives the skill, the tool finds the bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Haiku 4.5&lt;/strong&gt; : structured extraction, tight tasks with known output shape, anything that would be overkill for a bigger model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your stack will be different. The piece worth copying is pairing each LLM review pass with a deterministic tool, not stacking prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Loop until zero, in contained systems
&lt;/h2&gt;

&lt;p&gt;On a personal cron supervisor, roughly two hundred lines of logic with deterministic inputs, the bug count trends toward zero across passes. Pass one found five bugs. Pass two found one. Pass three found three (the implementation wave had created new surface to audit). Pass four found zero. That's where I stopped.&lt;/p&gt;

&lt;p&gt;Past that size, or once database side effects and real concurrency enter, the pass count stops converging and "loop until zero" becomes a paralysis spiral. This is a rule for small, fully-owned systems, not for production services with moving dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a dead man's switch in cron monitoring?
&lt;/h3&gt;

&lt;p&gt;A monitoring pattern where the absence of a signal triggers the alert, not the presence of one. Every scheduled script sends a heartbeat ping on each run. If the monitor doesn't see a ping within the expected window, the script is assumed dead. Healthchecks.io, Cronitor, and Dead Man's Snitch are commercial implementations.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does this compare to healthchecks.io or Cronitor?
&lt;/h3&gt;

&lt;p&gt;Same pattern, rolled by hand. A SaaS monitor wouldn't have let me register a slug and never ping it, because the slug doesn't exist until the first ping lands. Most of the referential integrity gaps that caused the bugs in this post are enforced by those services at signup.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the "install-before-verify" anti-pattern?
&lt;/h3&gt;

&lt;p&gt;Installing a change to a live system before validating it, with no rollback path if validation fails. In the crontab case, &lt;code&gt;crontab new.txt&lt;/code&gt; made the new config live immediately, and the &lt;code&gt;diff&lt;/code&gt; check that followed couldn't undo it. The fix is to validate syntax in a staging slot first with &lt;code&gt;crontab -n&lt;/code&gt;, then install on pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the "muting is not fixing" anti-pattern?
&lt;/h3&gt;

&lt;p&gt;Responding to a noisy monitor by silencing it (pushing &lt;code&gt;alerted_until&lt;/code&gt; forward, adding a filter, raising a threshold) without addressing why the alert fired. Debt accumulates invisibly. Three months of mutes looks fine on the dashboard. One bad state escaping to production recovers the debt with interest.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is referential integrity in a cron monitoring system?
&lt;/h3&gt;

&lt;p&gt;The property that the three components describing a scheduled job (the crontab line, the registry entry in &lt;code&gt;checks.json&lt;/code&gt;, and the health-ping calls in source code) must all agree. Without enforcement, you can register a job that has no ping call, tag a cron line with a slug that doesn't exist in the registry, or mute an alert indefinitely without touching source. SaaS monitors enforce this at signup. A homegrown system has to add the gate deliberately.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;I had been fixing this bug, one alert at a time, for three months. Every fix was a mute. Every mute was a debt I told myself I'd deal with later. I didn't.&lt;/p&gt;

&lt;p&gt;Three hours with a shell linter. I had spent more than that, cumulatively, letting a confident LLM talk me out of reading my own code.&lt;/p&gt;

&lt;p&gt;Fix once is a lie. Loop until zero.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>How I Track Claude, Codex, and Gemini Quotas from One Script</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Mon, 18 May 2026 19:56:25 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/how-i-track-claude-codex-and-gemini-quotas-from-one-script-36n5</link>
      <guid>https://forem.com/ianlpaterson/how-i-track-claude-codex-and-gemini-quotas-from-one-script-36n5</guid>
      <description>&lt;p&gt;(If you're trying to decide &lt;a href="https://ianlpaterson.com/blog/inference-arbitrage-llm-routing-playbook/" rel="noopener noreferrer"&gt;which model to switch to&lt;/a&gt; when one runs dry, I &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;benchmarked 15 models on 38 real coding tasks&lt;/a&gt; with full cost-per-task breakdowns.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz0b46u8vus1et5zikwq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz0b46u8vus1et5zikwq.png" alt="Claude Code status line showing token consumption percentages for Claude, Codex, and Gemini" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I run three AI coding CLIs daily. None of them tell me whether I'm about to hit a rate limit. I periodically get locked out mid-task and spend ten minutes figuring out which tool ran out, when it resets, and whether I should switch models or wait.&lt;/p&gt;

&lt;p&gt;I built a script that collects quota data from all three, writes it to a single JSON file, and runs on an hourly cron. The whole thing feeds a status line in Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session: ███░░░⏐░░░░░░░░░░░░░ 10% (3h12m left)
Weekly:  ████████░░⏐░░░░░░░░░ 44% (Thu Mar 05 8pm PT)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filled blocks (&lt;code&gt;█&lt;/code&gt;) show usage consumed. The marker (&lt;code&gt;⏐&lt;/code&gt;) shows where you are in the time window. If the blocks outpace the marker, you're burning budget faster than time is passing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you query Claude Code's rate limit programmatically?
&lt;/h2&gt;

&lt;p&gt;Claude Code authenticates via OAuth, with credentials stored at &lt;code&gt;~/.claude/.credentials.json&lt;/code&gt;. What I couldn't find in any official documentation is that &lt;code&gt;api.anthropic.com/api/oauth/usage&lt;/code&gt; returns the data you need: utilization percentages and reset timestamps for both the 5-hour rolling window and the 7-day weekly allocation. It's used internally by Claude Code's HUD, but it doesn't appear in Anthropic's public API reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.claudeAiOauth.accessToken // empty'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.claude/.credentials.json"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 10 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"anthropic-beta: oauth-2025-04-20"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.anthropic.com/api/oauth/usage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"five_hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resets_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-28T17:00:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seven_day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resets_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-07T08:00:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seven_day_sonnet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resets_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-07T08:00:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I found this by searching Anthropic's GitHub issues for "usage" and "quota," then trying endpoints until one worked. The catch is that &lt;code&gt;anthropic-beta: oauth-2025-04-20&lt;/code&gt; header. Without it, you get a 401. The date in the version string suggests this header has been updated before, so when it changes again, the collector breaks silently.&lt;/p&gt;

&lt;p&gt;Claude Code uses a 5-hour rolling session window, not a 24-hour one, and the weekly limit resets Thursday at 8pm PT, not midnight UTC. I only discovered both by watching the numbers change over a week of collection. The docs don't mention either detail.&lt;/p&gt;

&lt;p&gt;I've been hitting this endpoint hourly since February with zero failures. But "undocumented beta endpoint" is not a phrase that inspires long-term confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you track Gemini CLI usage without an API?
&lt;/h2&gt;

&lt;p&gt;Gemini CLI has a &lt;code&gt;/stats&lt;/code&gt; command that works interactively, but non-interactively it completes silently with no output (the stats render through &lt;a href="https://github.com/vadimdemedes/ink" rel="noopener noreferrer"&gt;Ink&lt;/a&gt;, a React-based terminal renderer, which doesn't survive piping or capture). &lt;a href="https://github.com/google-gemini/gemini-cli/issues/19067" rel="noopener noreferrer"&gt;GitHub issue #19067&lt;/a&gt; has a user asking the same question I had. The maintainer response: "there's no way within Gemini CLI to see your daily quota."&lt;/p&gt;

&lt;p&gt;The workaround: Gemini stores session files at &lt;code&gt;~/.gemini/tmp/_/chats/session-YYYY-MM-DD_.json&lt;/code&gt;. Each file is one session. The free tier allows 1,000 requests per day, resetting daily. There's no official way to query how many you've used. You count your own files. I verified the session counts against Google's AI Studio usage dashboard to make sure the file-based approach was tracking correctly.&lt;/p&gt;

&lt;p&gt;A note on what this actually measures: the free tier limit is requests, but what we're counting is session files and token consumption. Session files don't map 1:1 to API requests (a single session can contain multiple turns), so the session count is a lower bound, not an exact quota meter. For my usage patterns, the counts track closely enough to be useful as a warning signal, but they won't catch you at exactly request 999.&lt;/p&gt;

&lt;p&gt;Counting files only tells you session counts. For actual consumption data, you parse the JSON. Each session file has a &lt;code&gt;messages&lt;/code&gt; array where every message includes a &lt;code&gt;tokens.total&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/chats/session-*.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# YYYY-MM-DD from session-YYYY-MM-DDTHH-MM-*.json
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;file_date&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;file_date&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;file_date&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;file_tokens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output includes today's session and token counts, lifetime totals, and per-day breakdowns for the past week. All from flat files that were never intended to be an API. If Google changes the session file schema or directory structure, there's no migration path. You find out when the numbers stop updating.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Codex CLI expose rate limits?
&lt;/h2&gt;

&lt;p&gt;Codex has an interactive &lt;code&gt;/status&lt;/code&gt; command that shows rate limits in a TUI modal. The same problem as Gemini: it only works inside the REPL (the interactive read-eval-print loop). &lt;code&gt;codex /status&lt;/code&gt; exits immediately with nothing.&lt;/p&gt;

&lt;p&gt;My first approach felt like performing surgery with oven mitts: launch Codex inside a tmux session, send &lt;code&gt;/status&lt;/code&gt; as keystrokes, wait for the modal to render, press Escape to dismiss it, send &lt;code&gt;/help&lt;/code&gt; to push the status into the scroll buffer, capture 300 lines of scroll history, grep for "% left." It required &lt;code&gt;sleep&lt;/code&gt; statements between every step and never worked reliably.&lt;/p&gt;

&lt;p&gt;Then I found &lt;code&gt;codex app-server&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Codex ships with an &lt;code&gt;app-server&lt;/code&gt; subcommand that speaks JSON-RPC over stdin/stdout. OpenAI has &lt;a href="https://developers.openai.com/codex/app-server/" rel="noopener noreferrer"&gt;documentation for it&lt;/a&gt;, though I only found it after discovering the feature in the source code. The &lt;code&gt;account/rateLimits/read&lt;/code&gt; method isn't prominently featured, but it works. You spawn the process, send an &lt;code&gt;initialize&lt;/code&gt; handshake, then call it. The 500ms delay between handshake and request is necessary because shorter values produce empty responses before the connection is ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;codex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ignore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInterface&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Handshake&lt;/span&gt;
&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;initialize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;clientInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quota-collector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Quota Collector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Request rate limits after handshake completes&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account/rateLimits/read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response comes back with &lt;code&gt;rateLimits.primary&lt;/code&gt; (five-hour window) and &lt;code&gt;rateLimits.secondary&lt;/code&gt; (weekly), each with &lt;code&gt;usedPercent&lt;/code&gt; and &lt;code&gt;resetsAt&lt;/code&gt;. No scroll buffer archaeology required.&lt;/p&gt;

&lt;p&gt;Codex also maintains a SQLite database at &lt;code&gt;~/.codex/state_5.sqlite&lt;/code&gt; (the &lt;code&gt;_5&lt;/code&gt; is a schema version, so this path may change in future releases) with a &lt;code&gt;threads&lt;/code&gt; table that tracks every session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
          &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokens_used&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'unixepoch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The collector: one cron, one JSON
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;claude_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;collect_claude&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;codex_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;collect_codex&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;gemini_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;collect_gemini&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;

jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--argjson&lt;/span&gt; claude &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$claude_json&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--argjson&lt;/span&gt; codex &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$codex_json&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--argjson&lt;/span&gt; gemini &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$gemini_json&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--arg&lt;/span&gt; ts &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ts&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'{collected_at: $ts, claude: $claude, codex: $codex, gemini: $gemini}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTFILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After writing &lt;code&gt;latest.json&lt;/code&gt;, the script appends a compacted copy to a daily JSONL file for historical tracking. One line per collection, roughly 15 lines per day, about 2KB. Want to know how fast you burned through your five-hour window last Tuesday? &lt;code&gt;jq -r '.claude.five_hour.utilization' quota/history/2026-03-11.jsonl&lt;/code&gt; and watch the numbers climb line by line.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I display it
&lt;/h2&gt;

&lt;p&gt;A separate status line script reads &lt;code&gt;latest.json&lt;/code&gt; and renders progress bars on every Claude Code interaction. Green below 70%, amber at 70-90%, red above 90%. The time marker (&lt;code&gt;⏐&lt;/code&gt;) gives you an instant burn-rate signal without reading numbers.&lt;/p&gt;

&lt;p&gt;The same JSON feeds a dashboard with budget bars and threshold alerts. Both are reading a flat file, no live API calls during rendering. The hourly cron does the expensive work once, everything downstream reads the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use ccusage?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ryoppippi/ccusage" rel="noopener noreferrer"&gt;ccusage&lt;/a&gt; reads Claude Code and Codex JSONL logs, giving you per-model cost breakdowns with date filtering. It's better than the collector at USD cost tracking and historical queries. If that's what you need, use it.&lt;/p&gt;

&lt;p&gt;I built my own because the collector does three things ccusage doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It tracks Gemini (1,000 req/day free tier, no JSONL logs to read, only session file parsing).&lt;/li&gt;
&lt;li&gt;It acts on the data (configurable spend alerts, pipeline kill switches when costs spike).&lt;/li&gt;
&lt;li&gt;It burns unused Codex budget on maintenance tasks when weekly utilization drops below 60%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also doesn't require an additional MCP server. It reads a flat JSON file.&lt;/p&gt;

&lt;p&gt;The usage data exists somewhere in every AI CLI. It might be an undocumented endpoint, a pile of session files, or a JSON-RPC server. An hourly cron that normalizes it all into one JSON file is about 250 lines of bash, Python, and JavaScript. An afternoon of reverse engineering, once you know where to look.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
      <category>bash</category>
    </item>
    <item>
      <title>OpenClaw: 13 Errors, $1.50/Month, and an AI Team That Doesn’t Need the Cloud</title>
      <dc:creator>Ian L. Paterson</dc:creator>
      <pubDate>Sat, 16 May 2026 23:10:31 +0000</pubDate>
      <link>https://forem.com/ianlpaterson/openclaw-13-errors-150month-and-an-ai-team-that-doesnt-need-the-cloud-4256</link>
      <guid>https://forem.com/ianlpaterson/openclaw-13-errors-150month-and-an-ai-team-that-doesnt-need-the-cloud-4256</guid>
      <description>&lt;p&gt;I run a team of AI agents on a Mac I bought in 2022. They handle my Slack, run research, draft content, monitor infrastructure, and spawn sub-agents for compound tasks. The whole operation costs me $1.50 a month in electricity. Zero API fees, zero cloud provider. Just OpenClaw in a Docker container and a 30-billion parameter model running locally.&lt;/p&gt;

&lt;p&gt;This is the full setup, including every config that matters, every error I hit, and the performance tuning that took me from 12 tokens per second to 49. (Full cost breakdown: $1.50 electricity vs. the $330/month I was paying before. Details at the end.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What OpenClaw Actually Is
&lt;/h2&gt;

&lt;p&gt;I first installed OpenClaw the week it launched in January 2026. By that point it had already crossed 100,000 GitHub stars. Peter Steinberger's interview with Lex Fridman is worth the watch if you want the full backstory.&lt;/p&gt;

&lt;p&gt;OpenClaw is an open-source AI agent framework. You give it access to tools (shell, browser, file system, messaging) and a model, and it acts autonomously. You can talk to it through Slack, a web UI, or the CLI. It supports sub-agents for compound tasks, custom skills, and model routing across multiple providers.&lt;/p&gt;

&lt;p&gt;The key difference from Claude Code: OpenClaw runs in a loop. It has a heartbeat system that keeps agents alive and working even when you're not at the keyboard. Claude Code (in its default interactive mode) waits for you to type something. OpenClaw doesn't. That's what drew me in. I wanted agents running overnight, picking up tasks from a queue, monitoring infrastructure, drafting reports while I slept. Right now my agents handle Slack triage (summarizing threads, flagging action items), run multi-source research with parallel sub-agents, draft first passes of content like this article, and monitor my infrastructure for drift. The compound task pattern is the most useful: I describe what I want, and OpenClaw spawns three sub-agents to tackle different angles simultaneously.&lt;/p&gt;

&lt;p&gt;You can run it in Docker, on a VPS, on a Mac Mini under your desk, or an old PC with a GPU. Pair it with a local model and your cloud costs go to zero.&lt;/p&gt;

&lt;p&gt;The 30,000+ exposed instances found by security researchers tell you two things: a lot of people are running this, and most of them didn't read the security docs. I nearly joined that list myself (see Error #9 and the network isolation section above).&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenClaw Setup: Installation and First Config
&lt;/h2&gt;

&lt;p&gt;I started with a fresh Ubuntu system and followed the install docs on GitHub. The first thing you hit is missing build tools. apt-get install python3, gcc, make, and the usual suspects before the install script will complete. Node.js 18+ is required.&lt;/p&gt;

&lt;p&gt;The first run drops you into a config wizard that asks about gateway mode, model selection, and channel setup. I skipped it and wrote the config files directly. The wizard is interactive, which doesn't work if you're setting up over SSH or scripting the deployment.&lt;/p&gt;

&lt;p&gt;You need three things configured:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A model provider. I started with OpenRouter for testing before moving everything to my on-prem server. Initially I pointed it at Opus, which worked perfectly and consumed credits at an alarming rate. Scaled down to Sonnet, then found MiniMax M2.1 and Kimi2.5 as workable cheap alternatives. I avoided the free models entirely because free endpoints on OpenRouter don't reliably support tools, system prompts, and structured output, which OpenClaw requires. They also route through providers whose data retention policies vary. Defeats the purpose if you're trying to own your stack.&lt;/li&gt;
&lt;li&gt;A messaging channel. I use Slack with Socket Mode. No public webhook endpoint needed, just a bot token and an app token. The non-obvious gotcha: under App Home, "Allow users to send messages from the messages tab" must be checked. It's separate from OAuth scopes and nothing in the docs tells you this. Socket Mode connects fine without it. Messages just silently never arrive.&lt;/li&gt;
&lt;li&gt;Network isolation. I didn't bother with the application-level security settings initially. Instead I firewalled everything off, set up Tailscale, restricted SSH to private keys only, and bound all services to localhost. If nothing is listening on a public port, most of the hardening guides are solving a problem you don't have.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The config lives in three places:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Environment file&lt;/td&gt;
&lt;td&gt;API keys, tokens, gateway token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clawdbot.json&lt;/td&gt;
&lt;td&gt;Model, plugins, channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auth-profiles.json&lt;/td&gt;
&lt;td&gt;Model provider authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One warning: clawdbot config set says "Updated" but doesn't always write to the file the gateway actually reads. Edit the JSON directly. I stopped using the CLI for config changes after the third time it silently did nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting LM Studio as a Local LLM Provider
&lt;/h2&gt;

&lt;p&gt;I run LM Studio on a 2022 Mac Studio (M1 Max, 32GB). The model is Qwen3-Coder-30B-A3B, a mixture-of-experts architecture where 3 billion parameters are active per token but all 30 billion live in memory. In GGUF Q4_K_S quantization it's about 17.5GB on disk.&lt;/p&gt;

&lt;p&gt;LM Studio exposes an OpenAI-compatible API on localhost. OpenClaw connects to it like any other model provider. If the machine running OpenClaw and the machine running LM Studio are the same box, you just point at localhost and you're done.&lt;/p&gt;

&lt;p&gt;My setup is split across two machines. OpenClaw runs on a server, the Mac Studio runs the model. An SSH reverse tunnel connects them over Tailscale, which means you've got an encrypted tunnel inside an encrypted VPN. Belt and suspenders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server (OpenClaw, localhost:port)
        ↓
   Tailscale mesh (encrypted)
        ↓
   SSH tunnel (encrypted)
        ↓
Mac Studio (LM Studio :port)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tunnel adds less than one millisecond of latency. The bottleneck is always model inference, never the network.&lt;/p&gt;

&lt;p&gt;I use a macOS LaunchAgent to keep the tunnel alive. It reconnects automatically after network drops, sleep/wake, router reboots. No third-party tools needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Residential IP
&lt;/h3&gt;

&lt;p&gt;There's an added bonus to running on-prem hardware at your house that I didn't anticipate. Your home internet connection has a residential IP address. When your agents browse the web, fetch pages, or interact with APIs, they're coming from an IP that looks like a normal person, not a datacenter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Residential IPs don't get automatically blocked or hit with CAPTCHAs&lt;/strong&gt; the way cheap hosting providers do. I've had agents get blocked on a VPS and work fine through the Mac Studio on the same site, same request, same minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Config Gotchas for LM Studio + OpenClaw
&lt;/h3&gt;

&lt;p&gt;This is where I lost the most time.&lt;/p&gt;

&lt;p&gt;The two settings that burned me longest were both silent failures. The provider name in your config must be "openai," not "lmstudio" or "local" or anything creative. OpenClaw's auth resolution silently fails on unrecognized provider names. Nothing in the logs, nothing on screen. It just doesn't connect. Same story with the API mode: it must be "openai-completions" (which calls /v1/chat/completions). The other option, "openai-responses," calls /v1/responses, which hangs indefinitely on LM Studio. The naming suggests they're interchangeable. They're not. I read the OpenClaw source code to figure both of these out.&lt;/p&gt;

&lt;p&gt;The auth profile format also isn't documented. After guessing for two hours, I found it requires a v1 store format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"openai:default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lm-studio"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key value doesn't matter for LM Studio (it doesn't authenticate) but it can't be empty or OpenClaw skips the provider.&lt;/p&gt;

&lt;p&gt;Two more that bit me later: the context window defaults to 4,096 tokens, but OpenClaw's system prompt alone is 17,000 tokens. You'll get "Cannot truncate prompt" errors until you bump the context in LM Studio's UI to at least 32,768. Set it in the UI, not the CLI. The CLI setting doesn't survive a crash.&lt;/p&gt;

&lt;p&gt;And the Jinja template bug: Qwen3-Coder's GGUF template includes a &lt;code&gt;| tojson | safe&lt;/code&gt; filter. LM Studio's Jinja engine doesn't support &lt;code&gt;| safe&lt;/code&gt;. It only triggers with complex tool schemas (nested JSON in parameters), so you might run fine for days before hitting it. Fix: edit the template in LM Studio's UI (My Models &amp;gt; Prompt Template), find the two occurrences of &lt;code&gt;| tojson | safe&lt;/code&gt;, and change them to &lt;code&gt;| tojson&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  13 OpenClaw Errors and How I Fixed Them
&lt;/h2&gt;

&lt;p&gt;Thirteen errors across the full setup. None of them had useful documentation when I searched. That's why they're all here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Root Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;"Unknown model" on OpenRouter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Free models fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;"Cannot truncate prompt"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Provider auth silent fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;"openai-responses" hangs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;"Unknown filter: safe"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;MLX crashes under load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Session history bloat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Sub-agent token mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;"openclaw: command not found"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;GATEWAY_BIND=lan breaks auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;bootstrapMaxChars crash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Speculative decoding freeze&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. "Unknown model" on OpenRouter
&lt;/h3&gt;

&lt;p&gt;OpenClaw has an internal model registry. Models not in it get rejected before the request leaves the box. MiniMax M2.1 wasn't listed. Fix: add it to agents.defaults.models (plural, not model singular) as an allowlist entry. The naming matters too. OpenRouter uses lowercase IDs (minimax/minimax-m2.1, not MiniMax-M2.1). Check openrouter.ai/models for exact strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Free models can't handle the agent protocol
&lt;/h3&gt;

&lt;p&gt;Llama 3.3 70B returned 404. Gemma 3 27B threw "Upstream error from OpenInference." Free endpoints on OpenRouter don't reliably support tools, system prompts, and structured output, which is everything OpenClaw needs. Save yourself the debugging: use paid models or go local.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Cannot truncate prompt with n_keep &amp;gt;= n_ctx"
&lt;/h3&gt;

&lt;p&gt;LM Studio defaults to 4,096 tokens of context. OpenClaw's system prompt is 17,000 tokens. The math doesn't work. Set context to at least 32,768 in LM Studio's UI settings, not the CLI. The CLI setting doesn't survive a crash.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Provider auth silently fails
&lt;/h3&gt;

&lt;p&gt;If you name your provider "lmstudio" instead of "openai" in the config, OpenClaw's auth resolution doesn't error. It just doesn't connect. Nothing in the logs, nothing on screen. I read the source code to find this.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. "openai-responses" hangs indefinitely
&lt;/h3&gt;

&lt;p&gt;openai-responses calls /v1/responses, which LM Studio doesn't serve. openai-completions calls /v1/chat/completions, which it does. The naming suggests they're interchangeable. They're not.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Jinja template: "Unknown StringValue filter: safe"
&lt;/h3&gt;

&lt;p&gt;Qwen3-Coder's GGUF template uses &lt;code&gt;| tojson | safe&lt;/code&gt;. LM Studio doesn't support the safe filter. Only triggers with complex nested tool schemas, so you might run fine for days before hitting it. Fix: edit the template in LM Studio UI, remove &lt;code&gt;| safe&lt;/code&gt; from both occurrences.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. 30B MLX model crashes under sustained load
&lt;/h3&gt;

&lt;p&gt;I tried the MLX 4-bit version of Qwen3-Coder first. It loaded fine, ran fine for short conversations, then started producing hallucinated gibberish followed by "Exit code: null." No useful error in logs. Switched to GGUF Q4_K_S with sysctl tuning for the GPU memory cap and it's been stable since.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Session history bloat
&lt;/h3&gt;

&lt;p&gt;OpenClaw persists conversation history per session. After extended debugging, one session had 1,836 lines. New requests were failing because the history plus the system prompt exceeded the context window. Fix: delete stale session files from ~/.openclaw/agents/main/sessions/. Not obvious that this is a thing you need to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Sub-agent auth: the three-token problem
&lt;/h3&gt;

&lt;p&gt;When sub-agents spawn, they connect back to the gateway via WebSocket. Three auth layers must agree:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Device identity token&lt;/li&gt;
&lt;li&gt;Gateway paired device record&lt;/li&gt;
&lt;li&gt;Gateway auth token (stored in the config AND the env file AND gateway-token.txt)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After upgrading OpenClaw, the config auto-migrated with a new gateway token, but the env file kept the old one. Sub-agents read from env, the gateway validates against config. Every spawn failed with "device_token_mismatch." Zero useful error messages.&lt;/p&gt;

&lt;p&gt;Post-upgrade checklist I wish I'd had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chown -R clawdbot:clawdbot .git/ dist/ if git ops ran as root&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. "openclaw: command not found" from sub-agents
&lt;/h3&gt;

&lt;p&gt;Source installs don't add the binary to PATH. Sub-agents need it to announce back to the gateway. Fix: &lt;code&gt;ln -sf /opt/clawdbot/openclaw.mjs /usr/local/bin/openclaw&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  11. GATEWAY_BIND=lan breaks sub-agents
&lt;/h3&gt;

&lt;p&gt;Known issue (#916 on GitHub). Internal gateway calls don't pass auth correctly when bound to the LAN interface. Change to loopback and sub-agents start working.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. bootstrapMaxChars as an object crashes the gateway
&lt;/h3&gt;

&lt;p&gt;The config expects a plain number (e.g., 8000). I passed it as an object with per-file settings. The gateway crashed on startup with no indication of which config key was wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  13. Speculative decoding froze the Mac twice
&lt;/h3&gt;

&lt;p&gt;I tried enabling speculative decoding with a 0.75B draft model to speed up generation. At 120k context, the Mac Studio hard-froze. Power cycled, tried again at 140k. Froze again. On 32GB Apple Silicon with a 30B model near the context ceiling, there's no memory headroom for a draft model. The display server gets killed first, which means no graceful shutdown. Just a black screen and a hard reboot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apple Silicon Performance Tuning: 12 to 49 Tokens Per Second
&lt;/h2&gt;

&lt;p&gt;Out of the box, I was getting 12 tokens per second with frequent crashes. After tuning, 49 tokens per second at 140,000 tokens of context, stable for days.&lt;/p&gt;

&lt;h3&gt;
  
  
  KV Cache Quantization: The Single Biggest Win
&lt;/h3&gt;

&lt;p&gt;The key-value cache stores attention state for every token in your context window. By default, LM Studio keeps it in F16 (16-bit floating point). Switching both K and V to Q8_0 (8-bit) nearly doubles your usable context and increases generation speed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Max Context&lt;/th&gt;
&lt;th&gt;Gen Speed&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;F16 KV&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;td&gt;12-35 t/s&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q8_0 KV&lt;/td&gt;
&lt;td&gt;140,000&lt;/td&gt;
&lt;td&gt;49 t/s&lt;/td&gt;
&lt;td&gt;Production config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I tested every increment:&lt;/p&gt;

&lt;p&gt;32k stable → 49k tight → 65k comfortable → 75k ceiling (F16) → 80k fails to load → 120k works (Q8_0) → 140k production ceiling (Q8_0) → 150k fails → 200k OOM kills the display server.&lt;/p&gt;

&lt;p&gt;Set Flash Attention to explicit "On" in LM Studio, not "Auto." Auto doesn't always activate, and it's required for KV cache quantization to work.&lt;/p&gt;

&lt;h3&gt;
  
  
  sysctl: Raise the GPU Memory Cap
&lt;/h3&gt;

&lt;p&gt;macOS caps GPU memory at about 66% of unified RAM by default. On a 32GB machine, that's roughly 21GB. A 17.5GB model plus KV cache at any reasonable context length blows right past that.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo sysctl iogpu.wired_limit_mb=24576&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This raises the cap to 24GB. Persist it in /etc/sysctl.conf so it survives reboots. This was the difference between "crashes under load" and "stable at 140k context."&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU Threads: Less is More
&lt;/h3&gt;

&lt;p&gt;Apple Silicon has performance cores and efficiency cores. The M1 Max has 8 P-cores and 2 E-cores. I assumed 10 threads would be faster than 8. It's not. The E-cores are slower and create a bottleneck. Use P-cores only.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Didn't Help
&lt;/h3&gt;

&lt;p&gt;I also tried batch size 1536 (vs 768), speculative decoding with a 0.75B draft model, and sub-4-bit quantization (Q3_K_M, IQ3_XS). Batch size made zero practical difference (48.5 vs 49.2 t/s). Speculative decoding froze the Mac twice at 120K+ context because there's no memory headroom for a draft model on 32GB. And sub-4-bit quants are actually slower on Apple Silicon because of dequantization overhead. Q4_K_S is the sweet spot.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenClaw Config Optimization
&lt;/h3&gt;

&lt;p&gt;The model is only half the story. OpenClaw's defaults are designed for 200k-context cloud models. On local hardware, you need to trim.&lt;/p&gt;

&lt;p&gt;The first thing I changed was &lt;code&gt;bootstrapMaxChars&lt;/code&gt;, from 20,000 down to 8,000. OpenClaw loads project files into context at the start of every request. At 20k per file, a single bootstrap was consuming half my context window before the conversation even started.&lt;/p&gt;

&lt;p&gt;Next was &lt;code&gt;contextPruning&lt;/code&gt; (cache-ttl, 5 minutes). Old context that hasn't been referenced gets dropped automatically. Before this, I was running out of context mid-conversation because stale tool outputs were taking up space.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;historyLimit: 3&lt;/code&gt;. This caps how much Slack conversation history gets loaded. I had a busy channel filling the context with old messages, crowding out the actual work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Config Summary
&lt;/h3&gt;

&lt;p&gt;For anyone running a 30B MoE model on 32GB Apple Silicon:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Qwen3-Coder-30B-A3B Q4_K_S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;140,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV Cache (K and V)&lt;/td&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flash Attention&lt;/td&gt;
&lt;td&gt;On (explicit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU Threads&lt;/td&gt;
&lt;td&gt;8 (P-cores only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU Layers&lt;/td&gt;
&lt;td&gt;All (49/49)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch Size&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bootstrapMaxChars&lt;/td&gt;
&lt;td&gt;8000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;historyLimit&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;contextPruning&lt;/td&gt;
&lt;td&gt;cache-ttl, 5m&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generation speed: 49 tokens/second. Prompt eval: ~470 tokens/second. Three sub-agents spawning concurrently at 120k context, all completing successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  10 Things I'd Change on a Fresh Install
&lt;/h2&gt;

&lt;p&gt;If I were starting over tomorrow, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;sysctl iogpu.wired_limit_mb=24576&lt;/code&gt; before loading any model. I was convinced the model was unstable when the real problem was macOS starving the GPU of memory. Persist it in /etc/sysctl.conf immediately.&lt;/li&gt;
&lt;li&gt;Start with GGUF, not MLX. MLX has better integration with some tools but its KV cache quantization is buggy (fails above 1k tokens on 8-bit). GGUF's Q8_0 KV cache just works and it's the single biggest performance unlock.&lt;/li&gt;
&lt;li&gt;Set LM Studio's default context in the UI on first launch. When the model crashes (and it will, while you're testing limits), LM Studio reloads it with the default from settings.json. If that default is 4,096 and your system prompt is 17,000 tokens, you're in a crash loop that looks like the model is broken when it's actually a config problem.&lt;/li&gt;
&lt;li&gt;Read the OpenClaw source for auth-profiles.json. The format isn't documented anywhere. I burned two hours guessing before reading the source.&lt;/li&gt;
&lt;li&gt;Symlink the openclaw binary to /usr/local/bin immediately after a source install. You won't know sub-agents need it in PATH until they silently fail, and "silently" means a 60-second timeout with no error message.&lt;/li&gt;
&lt;li&gt;After every OpenClaw upgrade, verify the env file tokens match the config tokens. The config auto-migrates. The env file doesn't.&lt;/li&gt;
&lt;li&gt;Set both K and V cache to Q8_0 from day one. I ran F16 initially because I didn't know better. The switch doubled my context and increased speed. There's no reason not to do it.&lt;/li&gt;
&lt;li&gt;Don't try 200k context on 32GB. I know the model card says it supports it. It will OOM kill your display server and you'll be reaching for the power button. 140k is the ceiling on 32GB with this model. Respect it.&lt;/li&gt;
&lt;li&gt;Clear session history periodically. OpenClaw doesn't do this for you. Sessions accumulate conversation history that counts against your context window. I didn't notice until a session had 1,836 lines and new requests were failing for no apparent reason.&lt;/li&gt;
&lt;li&gt;Test with complex tool schemas early. The Jinja template bug only triggers with nested JSON parameters. Simple requests work fine. You'll think everything is stable, deploy to production, and then it breaks on the first real compound task.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cost Breakdown
&lt;/h2&gt;

&lt;h3&gt;
  
  
  My Setup
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Electricity (BC Hydro)&lt;/td&gt;
&lt;td&gt;~$1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPS (optional)&lt;/td&gt;
&lt;td&gt;$0 - $24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud API&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.50 - $25.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Mac Studio draws about 11W idle, 60W under load. In practice it averages around 20W because agents run in bursts, not continuously. BC Hydro's residential rate ($0.0996/kWh) on 20W average works out to about $1.50 a month. Your mileage varies by jurisdiction, but even in expensive markets you're looking at $3-5.&lt;/p&gt;

&lt;p&gt;The VPS is optional. If you run OpenClaw directly on the same machine as your model, it's electricity only. I use a VPS for remote access and 24/7 uptime independent of my home network, but it's not required.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Was Paying Before
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code (Max)&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT Pro&lt;/td&gt;
&lt;td&gt;$200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter credits&lt;/td&gt;
&lt;td&gt;$20-50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Misc API calls&lt;/td&gt;
&lt;td&gt;$10-30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$330-380&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not all of that is replaced. I still use Claude Code for interactive work and ChatGPT for specific tasks. But the 24/7 autonomous agents, the batch jobs, the research tasks, the monitoring, the content drafts, all of that moved to OpenClaw on local hardware. The subscriptions that were funding autonomous work went to zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud API Cost Comparison
&lt;/h3&gt;

&lt;p&gt;These costs assume heavy autonomous agent usage (millions of tokens per month). Light usage would be significantly cheaper on cloud APIs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPS + Anthropic Sonnet 4&lt;/td&gt;
&lt;td&gt;$124+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPS + Anthropic Sonnet 4.5&lt;/td&gt;
&lt;td&gt;$44-74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPS + OpenRouter MiniMax M2.1&lt;/td&gt;
&lt;td&gt;$29-39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPS + local model&lt;/td&gt;
&lt;td&gt;$25.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local only + local model&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bottom line is the one nobody writes about because it requires owning hardware. But if you already have a Mac with 32GB sitting on a desk, you already own the most expensive part.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ianlpaterson.com/about/" rel="noopener noreferrer"&gt;Ian L. Paterson&lt;/a&gt; is CEO of Plurilock, a publicly traded cybersecurity company. This is part of a series documenting what it looks like to build AI-powered infrastructure for real work. Other posts cover &lt;a href="https://ianlpaterson.com/blog/claude-code-memory-architecture/" rel="noopener noreferrer"&gt;persistent memory for Claude Code&lt;/a&gt;, session lifecycle management, and the daily automation layer that ties it all together.&lt;/p&gt;

&lt;p&gt;Related: LM Studio Troubleshooting&lt;/p&gt;

&lt;p&gt;Hit an error running this setup? Fixes for the most common LM Studio issues on Apple Silicon, including prompt truncation, jinja template failures, and performance tuning (12 to 49 tok/s): &lt;a href="https://ianlpaterson.com/blog/lm-studio-fix-cannot-truncate-prompt-n-keep-n-ctx/" rel="noopener noreferrer"&gt;LM Studio Errors on Apple Silicon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Related: Building a Memory System for Claude Code&lt;/p&gt;

&lt;p&gt;This setup powers the persistent memory behind my Claude Code workflows. See how MEMORY.md, topic files, and automated maintenance keep context alive across sessions: &lt;a href="https://ianlpaterson.com/blog/claude-code-memory-architecture/" rel="noopener noreferrer"&gt;Claude Code Memory System&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a deeper look at how I decide which models handle which tasks, see &lt;a href="https://ianlpaterson.com/blog/inference-arbitrage-llm-routing-playbook/" rel="noopener noreferrer"&gt;how I route 200+ daily LLM calls across five models&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which cloud models give the best results per dollar? I &lt;a href="https://ianlpaterson.com/blog/llm-benchmark-2026-38-actual-tasks-15-models-for-2-29/" rel="noopener noreferrer"&gt;tested 15 models on 38 real coding tasks&lt;/a&gt; and ranked them by accuracy and cost. Sonnet 4.6 scored 100% at $0.20 per task. Flash 2.5 hit 97% for $0.003.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ian Paterson is CEO of Plurilock (TSXV: PLUR) and writes about AI engineering, cybersecurity, and building in public at &lt;a href="https://ianlpaterson.com" rel="noopener noreferrer"&gt;ianlpaterson.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>applesilicon</category>
      <category>lmstudio</category>
      <category>localllm</category>
      <category>openclaw</category>
    </item>
  </channel>
</rss>
