My AI-agent token bill kept climbing, and the work wasn’t getting harder. I run Claude Code and Codex against four openmedical.cz codebases — the backend, the admin portal, the mobile app, and the website — all large TypeScript projects. Same prompts, same kinds of tasks, week after week. The meter kept going up anyway.
Then it clicked: I wasn’t paying for the model to reason. I was paying for scrollback. Every git status, every eslint run, every jest log, every ssh into the server dumped its raw output straight into the agent’s context — and I paid for all of it, in tokens, on repeat, across four repos. Nothing had broken. The agent worked fine. Only the bill told me anything was wrong.
TLDR
- My AI-agent token bill kept climbing on work that wasn’t getting harder. I was paying for scrollback — raw
git/eslint/jest/sshoutput streamed into the model — not for reasoning. - RTK (Rust Token Killer) is a background CLI proxy. After a one-time
rtk init -g, a hook rewritesgit status→rtk git statusbefore it runs, so command output reaches the model already compacted. No prompt change, nothing agent-visible. - One month across four openmedical.cz apps: 5,497 commands, 24M input tokens, 15.6M saved — 65.2%. RTK advertises 60–90%; my real number is the low end, and that’s the point.
- The savings were wildly concentrated: single
sshlog captures each stripped millions of tokens;eslintsat at 99.0%,jestat 93.4%. - On failure RTK tees the full unfiltered output, so the agent never loses anything it needs. In a month, over-compaction never bit me.
- squeez is the one genuine direct-ish alternative. Headroom and mcp-compressor are adjacent; LLMLingua is a different category.
What was actually expensive
The failure mode was the quiet kind. Nothing crashed; the agent did its job. The cost lived in the noisy commands, not the hard ones — lint logs, test logs, and especially SSH captures, where 95% of the lines carry zero decision-relevant signal for the model and I paid for every one of them.
The worst offenders were the commands I’d never have thought to optimize. A single ssh into the server to read logs could dump a multi-million-token wall of output into the context window. The model needed four lines of it. I was billed for all of it.
What changed in my workflow
Nothing about how I drive the agent. That’s the whole point.
| Before | After | |
|---|---|---|
| Agent runs git / eslint / jest / ssh | Raw output streamed into context | Hook rewrites to rtk …; model sees a compacted version |
| My prompts / how I drive the agent | — | Unchanged |
| Cost of a noisy command | Paid in full, every run | 65.2% less on aggregate; ~99% on lint/ssh, ~93% on jest |
| Seeing the cost | Only the bill, after the fact | rtk gain on demand |
| When a command fails | Full log in context | Full log teed, so the agent can still read it |
The one new habit is occasionally running rtk gain to see what I saved, and rtk discover to find commands still running raw.
RTK in one sentence
RTK is a single-binary Rust CLI that intercepts common dev commands (git, eslint, jest, ssh, build/test) and returns a compacted version of their output to the agent instead of the raw dump.
The hook I forgot was running
You install it once:
rtk init -g
That drops a global PreToolUse hook into Claude Code. From then on, when the agent types git status, the hook rewrites it to rtk git status before it runs. The agent never knows. The output it gets back is smaller.
The compaction shapes are blunt and obvious once you see them:
rtk git push → ok main (~10 tokens vs ~200 raw)
rtk jest run → FAILED: 2/15 tests (a couple of lines vs 200+)
The fix that worked was the one I stopped noticing. Same prompts, same commands. I forgot it was even there.
The month made visible
I ran rtk gain for the first time after about a month of work. This is what a month of invisible compaction looks like:
Total commands: 5,497
Input tokens: 24.0M
Output tokens: 8.4M
Tokens saved: 15.6M (65.2%)
Total exec time: 12,195m31s (avg 2m13s per command)
By Command (top savers):
rtk:toml ssh root@178… single calls saving 3.0M / 1.8M / 1.8M / 981.2K / 966.4K tokens each (100% / ~99.9%)
rtk lint eslint 40 calls 1.3M saved 99.0% avg
rtk jest run 50 calls 717.9K saved 93.4% avg
rtk:toml ssh root@… more entries at ~99.7%
Two things stand out. First, my real aggregate is 65.2% — the low end of RTK’s advertised “60–90%.” I’d rather report the number I can reproduce than the one I can quote. That it lands at the low end is what makes it credible.
Second, the savings are wildly concentrated. I expected the daily savers — eslint at 99.0% over 40 runs, jest at 93.4% over 50 — to be the story. They’re steady, but the real money was in a handful of one-shot ssh log captures, each of which stripped 3.0M, 1.8M, 1.8M raw-output tokens on a single call. SSH was the silent budget-killer I’d never have found reading my bill line by line.
One honest note: rtk:toml ssh … is my own dashboard label. My best read is that it’s a custom command wrapper defined in config.toml applying RTK’s compaction to an ssh call — but the exact label semantics aren’t spelled out in the docs, so treat that as inferred.
Setup, so you can repeat it
# install (pick one)
brew install rtk
curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
cargo install --git https://github.com/rtk-ai/rtk
# turn on the hook (one time)
rtk init -g
# work normally, then check what you saved
rtk gain
One gotcha trips you first: the name collision. There’s a completely different tool also called rtk — reachingforthejack/rtk, “Rust Type Kit,” which has nothing to do with tokens. The tell: on the token killer, rtk gain works. If it errors, run which rtk and check you installed the right one.
What actually runs under the hood
It’s less clever than it sounds, which is a compliment:
- The agent issues a Bash tool call. Claude Code fires the hook.
- If the command matches one RTK knows (100+ of them), it rewrites
git status→rtk git status. - RTK runs the real command and captures its full output.
- It applies four strategies: smart filtering (strip comments, whitespace, progress lines), grouping (files by directory, errors by type), truncation (keep context, cut redundancy), and deduplication (collapse repeated lines with a count).
- The agent receives only the compacted result. That’s where the tokens go.
- On failure, RTK tees the full unfiltered output, so the agent can read the real thing without re-running the command.
Step 6 is the load-bearing one. It’s why aggressive compaction is safe to forget about.
One scope limit worth knowing: the hook only fires on Bash tool calls. Claude Code’s built-in Read, Grep, and Glob bypass it, so those outputs aren’t compacted. Commands RTK doesn’t recognize run raw — rtk discover scans your history to find them.
What it costs
The honest tradeoffs:
- You’re adding another background binary and hook to your toolchain, plus that name-collision footgun.
- The “zero workflow change” has a coverage gap:
Read/Grep/Globand unknown commands bypass the hook. You have to runrtk discoverto find what’s still raw. - You’re trusting a compactor to decide what’s noise. The model won’t see the stripped lines unless the command fails.
The failure I expected but didn’t get was over-compaction. A competing tool, squeez, argues in its own README that compaction can backfire — that the agent emits more output to compensate for missing context, so total cost can rise. I’m attributing that, not endorsing it: in a month across four apps it never happened to me, and the tee-on-failure net is why. Nothing important was ever actually lost, because the agent could always fall back to the full output.
The genuine alternatives
RTK is fairly unique in the specific niche of “silently rewrite dev commands and compact their shell output for a coding agent.” The honest survey, kept short:
- squeez — the one direct-ish alternative. Same four strategies, hooks five AI CLIs, claims up to 95% bash compression. The architectural difference: squeez injects a hook that leaves the command intact, whereas RTK rewrites the command itself.
- Headroom — adjacent and broader. A whole-context compression layer (tool outputs, logs, files, RAG chunks, conversation history) with library, proxy, agent-wrap, and MCP modes. More power, more surface area, more setup. RTK is the smaller, sharper tool if you only want cheaper
git/eslint/jest/ssh. - mcp-compressor — adjacent, different layer. It compresses at the MCP tool-call layer, not the bash layer. Complementary, not competing.
- LLMLingua — a different category: a prompt-compression library that drops low-information tokens, meant for building pipelines, not for cheapening
git status.
My rule
If your agent’s cost is climbing on work that isn’t getting harder, you’re paying for output, not intelligence. Compact the noisy command output before it reaches the model — and treat “60–90%” as a range someone else hit, not a promise you’re owed. Measure your own with rtk gain.
FAQ
Will this change how my agent behaves?
No. The hook rewrites the command before it runs — same prompts, same commands. Only the output the model sees is compacted.
What if it hides output the agent actually needs?
On failure RTK tees the full unfiltered output, so the agent reads the real thing without re-running. In a month across four apps, I never lost anything that mattered.
Is 60–90% realistic?
That’s the vendor range. My real aggregate was 65.2% — the low end. Measure yours with rtk gain instead of trusting the number.
Does it cover everything my agent runs?
No. Only Bash tool calls pass through the hook; Read, Grep, and Glob bypass it, and unknown commands run raw. rtk discover finds the ones you’re still running raw.
I installed rtk but rtk gain errors — what happened?
You probably have the wrong rtk. reachingforthejack/rtk is “Rust Type Kit,” a different tool. On the token killer, rtk gain works; check which rtk.
The best token optimization is the kind you forget is running. A tool that changes how you drive the agent is a tax you pay every session; a hook that rewrites the command and gets out of the way, you pay for once. RTK saved 15.6M tokens in a month by being something I stopped noticing.
