Same model, 15x fewer tokens | Chirag Patil
← All writing

Chirag Patil · Sep 29, 2026 · 3 min read

Same model, 15x fewer tokens

Same agent, same prompt, same model. Swapping eight typed browser tools for one CLI tool cut a run from 351 seconds to 102, and 1.5M input tokens to 97.5k.

Eight typed tools351 s · 26 screenshots

Eight typed tools: every tool call over time57 tool calls over 351 seconds, 26 of them screenshots, and 58 model calls.browser_open · 29.9 sbrowser_screenshot · 10.3 sbrowser_read · 4.0 sbrowser_click · 2.4 sbrowser_screenshot · 3.7 sshell · 0.9 sshell · 1.4 sbrowser_snapshot · 1.8 sbrowser_scroll · 1.7 sbrowser_screenshot · 3.4 sbrowser_press · 1.6 sbrowser_screenshot · 3.5 sbrowser_press · 1.7 sbrowser_screenshot · 3.2 sbrowser_press · 1.6 sbrowser_screenshot · 3.3 sbrowser_press · 1.6 sbrowser_screenshot · 3.6 sbrowser_press · 1.6 sbrowser_screenshot · 3.4 sbrowser_press · 1.7 sbrowser_screenshot · 4.8 sbrowser_press · 1.7 sbrowser_screenshot · 3.7 sbrowser_press · 1.8 sbrowser_screenshot · 3.2 sbrowser_click · 2.3 sbrowser_screenshot · 3.3 sbrowser_press · 1.7 sbrowser_screenshot · 3.8 sbrowser_press · 1.6 sbrowser_screenshot · 3.2 sbrowser_press · 1.6 sbrowser_screenshot · 4.3 sbrowser_press · 1.8 sbrowser_screenshot · 3.6 sbrowser_press · 1.6 sbrowser_screenshot · 3.6 sbrowser_press · 1.6 sbrowser_screenshot · 2.9 sbrowser_press · 1.7 sbrowser_screenshot · 4.7 sbrowser_click · 2.1 sbrowser_screenshot · 3.5 sbrowser_click · 1.6 sbrowser_screenshot · 4.5 sbrowser_click · 2.1 sbrowser_screenshot · 3.5 sbrowser_press · 1.6 sbrowser_screenshot · 3.2 sbrowser_press · 1.6 sbrowser_screenshot · 5.0 sbrowser_click · 2.0 sbrowser_screenshot · 3.8 sbrowser_click · 1.9 sbrowser_screenshot · 4.3 sview_image · 1.4 s

One CLI tool102 s · 0 screenshots

One CLI tool: every tool call over time13 tool calls over 102 seconds, 0 of them screenshots, and 14 model calls.agent_browser · 2.6 sagent_browser · 1.8 sagent_browser · 1.5 sread_file · 0.6 sagent_browser · 20.9 sagent_browser · 3.5 sagent_browser · 2.3 sagent_browser · 7.7 sagent_browser · 2.2 sagent_browser · 8.6 sagent_browser · 1.6 sagent_browser · 4.6 sagent_browser · 3.0 s
  • screenshot
  • scroll, press, click
  • everything else
  • model call
Every tool call in both runs, drawn to the same time scale. Hover a block for the tool and its duration.

TL;DR: I only changed the tools. One CLI tool was about 3x faster than eight typed ones and used 15x fewer input tokens, in one run each.

What happened

I asked Athena, my Rust agent on Telegram, to read a Kaggle competition page. It took 26 screenshots, 17 PageDown presses and almost six minutes.

Athena drove the browser through eight typed tools I had wrapped around agent-browser. Each one checked its inputs and did one small thing. It looked tidy. Then I ran the same commands myself and saw what the model had actually been given:

  • The snapshot ran before Kaggle finished rendering, so it came back empty.
  • browser_read always passed a URL, so it fetched raw HTML instead of the rendered tab.
  • The interactive snapshot lists buttons, not page text.

So the model did what I would have done. It took screenshots and scrolled. Two of those are plain bugs, and I did not fix them and keep the typed tools, so I cannot say how much of the gap is design and how much is bugs.

The cache surprise

Every model call resends the whole conversation, and providers cache the start of it. 23 of the 58 calls threw that cache away and wrote everything again. Each one came right after an image.

Input tokens per model call, read from cache versus written againTyped tools: 58 model calls climbing to about 48.4k tokens, 23 of them rewriting the cache. One CLI tool: 14 calls, all under 12.6k, no rewrites.025k50kTyped toolsOne CLI tool1.2k in · 0.0k read from cache · 1.2k written again1.3k in · 1.2k read from cache · 0.1k written again3.8k in · 1.3k read from cache · 2.4k written again3.9k in · 3.8k read from cache · 0.1k written again4.0k in · 3.9k read from cache · 0.1k written again6.4k in · 4.0k read from cache · 2.4k written again6.7k in · 6.4k read from cache · 0.2k written again6.8k in · 6.7k read from cache · 0.1k written again8.8k in · 6.8k read from cache · 2.0k written again8.9k in · 8.8k read from cache · 0.1k written again11.3k in · 8.9k read from cache · 2.4k written again11.4k in · 11.3k read from cache · 0.1k written again13.8k in · 11.4k read from cache · 2.4k written again13.9k in · 13.8k read from cache · 0.1k written again15.4k in · 1.3k read from cache · 14.0k written again15.4k in · 15.4k read from cache · 0.1k written again16.9k in · 1.2k read from cache · 15.7k written again17.1k in · 16.9k read from cache · 0.2k written again18.6k in · 1.2k read from cache · 17.4k written again18.6k in · 18.6k read from cache · 0.1k written again20.1k in · 1.2k read from cache · 18.9k written again20.2k in · 20.1k read from cache · 0.1k written again21.7k in · 1.2k read from cache · 20.5k written again21.8k in · 21.7k read from cache · 0.1k written again23.2k in · 1.2k read from cache · 22.0k written again23.3k in · 23.2k read from cache · 0.1k written again24.8k in · 1.2k read from cache · 23.6k written again24.9k in · 24.8k read from cache · 0.1k written again26.4k in · 1.2k read from cache · 25.2k written again26.4k in · 26.4k read from cache · 0.0k written again27.9k in · 1.2k read from cache · 26.7k written again28.0k in · 27.9k read from cache · 0.0k written again29.5k in · 1.2k read from cache · 28.3k written again29.5k in · 29.4k read from cache · 0.1k written again31.0k in · 1.2k read from cache · 29.8k written again31.1k in · 31.0k read from cache · 0.1k written again32.5k in · 1.2k read from cache · 31.3k written again32.6k in · 32.5k read from cache · 0.1k written again34.1k in · 1.2k read from cache · 32.9k written again34.1k in · 34.1k read from cache · 0.0k written again35.6k in · 1.2k read from cache · 34.4k written again35.7k in · 35.6k read from cache · 0.1k written again37.2k in · 1.2k read from cache · 36.0k written again37.3k in · 37.2k read from cache · 0.2k written again38.8k in · 1.2k read from cache · 37.6k written again38.8k in · 38.8k read from cache · 0.1k written again40.3k in · 1.2k read from cache · 39.1k written again40.4k in · 40.3k read from cache · 0.1k written again41.9k in · 1.2k read from cache · 40.7k written again41.9k in · 41.9k read from cache · 0.1k written again43.4k in · 1.2k read from cache · 42.2k written again43.4k in · 43.4k read from cache · 0.1k written again44.9k in · 1.2k read from cache · 43.7k written again45.1k in · 44.9k read from cache · 0.1k written again46.5k in · 1.2k read from cache · 45.3k written again46.6k in · 46.5k read from cache · 0.1k written again48.1k in · 1.2k read from cache · 46.9k written again48.4k in · 1.2k read from cache · 47.2k written again1.0k in · 0.0k read from cache · 1.0k written again1.1k in · 1.0k read from cache · 0.1k written again4.8k in · 1.1k read from cache · 3.7k written again4.9k in · 4.8k read from cache · 0.1k written again5.1k in · 4.9k read from cache · 0.1k written again5.2k in · 5.0k read from cache · 0.1k written again5.5k in · 5.1k read from cache · 0.4k written again8.0k in · 5.5k read from cache · 2.5k written again8.1k in · 8.0k read from cache · 0.1k written again8.4k in · 8.1k read from cache · 0.3k written again8.5k in · 8.4k read from cache · 0.1k written again11.9k in · 8.5k read from cache · 3.4k written again12.3k in · 11.9k read from cache · 0.4k written again12.6k in · 12.3k read from cache · 0.3k written againfirst reset
  • read from cache
  • written again
Input tokens for each model call, in order. Every mostly-yellow bar is a call that threw the cache away. Hover a bar for its numbers.

My guess: Athena keeps only the newest four images. From the fifth one on, every new screenshot swaps an old image for a placeholder, which edits an early message and breaks the cached start. I have not tested this by changing the cap, but the pattern is gone in the run with no screenshots.

The change

I deleted the eight tools and added one, agent_browser, which passes its arguments straight to the CLI. Same prompt, same model (openai/gpt-6-luna), same page.

MeasureEight typed toolsOne CLI tool
Wall time351 s102 s
Model calls5814
Screenshots260
Input tokens1.50M97.5k
Cache resets230
One run of each.

This time the model read pages as text, with read on the open tab and eval document.body.innerText, and moved between pages by URL.

Caveats

One run of each, and I did not grade the answers, only time and tokens. The traces record cost as 0, and cache reads and writes bill at different rates, so 15x fewer tokens is not 15x cheaper.

The new tool is also unrestricted. No URL checks, and eval and downloads are allowed. The sandbox is now the only guardrail.

What I would check next time

  • Count tool calls per task. 26 screenshots for one page was sitting right there in the trace.
  • Look at cache reads per call, not just totals. A reset shows up as a sawtooth.
  • Run the tool yourself with the same flags before blaming the model.

The change is pull request 27 in Athena.

← All writing