Skip to content

RTK Proxy

rtk gain and the agent hooks work on bash output — what a command prints before your agent reads it. The RTK Proxy works one layer up, on the request your agent sends to the model: conversation history, tool results, file contents. It is a local HTTP daemon your agent talks to instead of talking to the model provider directly. The daemon binary is rtk-proxy; you drive it with rtk llm-proxy.

Claude Code ──▶ rtk-proxy ──▶ api.anthropic.com
│
├─ normalize: strip ANSI / redraws / dupes
├─ your proxy.request.tool_result rules
├─ (opt-in) cache-align
└─ record usage ──▶ rtk gain

It is local: one background process on your machine. It does four things — intercept, compress, forward, record — and does not store prompt or completion text (the opt-in CCR keeps the originals it compresses). What it compresses is in What the Proxy Compresses; its settings and endpoints are in Proxy Configuration.

Both layers are optional and independent. You can run the hooks without the proxy, the proxy without the hooks, or both — rtk gain reports them side by side.

Download the rtk-proxy-<target> archive for your platform from the releases page and put the binary next to rtk (anywhere on your PATH also works).

From a source checkout:

Terminal window
cargo build --release --manifest-path proxy/Cargo.toml

rtk looks for the daemon next to its own binary first, then on PATH. If it cannot find it, rtk llm-proxy tells you exactly where it looked.

Terminal window
rtk llm-proxy setup # check the daemon is present, list agents it can serve
rtk llm-proxy start # launch it in the background (no-op if already up)
rtk llm-proxy status # is it running and reachable?
rtk llm-proxy stats # usage and savings from the running daemon
rtk llm-proxy stop # ask it to shut down

setup is the one to run first: it reports which AI agents on this machine can be pointed at the proxy, and how.

You rarely need start by hand — rtk claude starts the daemon for you. See Getting your agent onto the proxy.

Two independent ways in. rtk init configures your agent once and persists it; rtk <harness> needs no configuration at all.

On a Claude Code init, RTK asks:

Set up the RTK proxy (routes Claude Code's API traffic through a local daemon)? [y/N]

Say yes and it writes env.ANTHROPIC_BASE_URL into settings.json and adds extends = "rtk:proxy" to your pipeline. Then it asks how the daemon should come back after a reboot:

ChoiceWhat it does
1. SessionStart hook (default)Registers a hook that starts the daemon when a Claude session begins
2. Background servicertk llm-proxy service install — a per-user service, independent of any agent
3. NeitherPrints the export line and leaves settings.json untouched

Declining leaves everything alone: “Skipped — enable later with rtk init -g or rtk llm-proxy start --env.”

If you already have the base URL set but nothing restarting the daemon, re-running rtk init -g asks only the persistence question.

It does not do the redirection — that is the settings.json entry above. The hook is a keep-alive: when a session whose ANTHROPIC_BASE_URL points at a loopback address finds nothing listening there, it runs rtk llm-proxy start.

It reports through systemMessage, which is user-visible and never enters model context — RTK spends no tokens configuring itself. Every failure path stays silent rather than disrupting the session.

This route only exists on harnesses that have a SessionStart hook, which is why the next one matters.

Terminal window
rtk claude

One command, no configuration, nothing persisted: it starts the daemon if it is not already up, points the harness at it, and execs the real binary. On Unix this is a genuine exec, so signals, stdio and the exit code behave exactly as if you had launched the harness directly.

This route needs no hook system. Not every harness has a SessionStart hook, but most offer a base-URL override — so this is how an agent without hooks still gets onto the proxy.

The daemon is agent-agnostic by design: it speaks the provider’s dialect, not the agent’s. A subcommand exists only where that redirection has been verified end to end, so rtk <harness> resolving is itself the signal that traffic really goes through the daemon. Today the list is:

SubcommandBase-URL variable
rtk claudeANTHROPIC_BASE_URL

A harness absent from the list is never silently unproxied — it falls through to plain passthrough. rtk llm-proxy setup reports what can be launched on this machine.

The launcher never blocks you: if the daemon cannot start, the harness launches unproxied with a warning rather than failing.

Terminal window
eval "$(rtk llm-proxy start --env)"

Any agent launched from that shell routes through the proxy. This is choice 3 above, done manually.

Start it on a different address with --bind:

Terminal window
rtk llm-proxy start --bind 127.0.0.1:9090

Every subcommand that talks to a running daemon (stop, status, stats) takes the same --bind flag, so you can manage a proxy that is not on the default 127.0.0.1:8080.

Two subcommands forward straight through to the daemon’s own CLI:

Terminal window
rtk llm-proxy config <args...> # get/set runtime settings on a running proxy
rtk llm-proxy service <args...> # manage a per-user background service

Run either with --help to see the daemon’s own options; the settings themselves are listed in Proxy Configuration. service is choice 2 of the guided setup — a per-user background service that survives logins independently of any agent.

One fixed location, so the daemon’s state does not depend on the shell’s working directory when it was started:

PathContents
~/.config/rtk/llm-proxy/daemon log and databases — on macOS ~/Library/Application Support/rtk/llm-proxy/, on Windows %APPDATA%\rtk\llm-proxy\
rtk-proxy.logdaemon log, started fresh on every start
rtk-proxy.dbusage records — the file rtk gain reads
rtk-proxy-memory.dboriginals kept by CCR (opt-in), 7 days

Because the usage database lives at a known path, rtk gain can report proxy savings whether or not the daemon is currently running.

With the proxy running, a bare rtk gain switches to a live dashboard covering both layers. See Live dashboard and Attribution.

Terminal window
rtk gain # live dashboard (proxy running, interactive terminal)
rtk gain --text # force the static view
rtk gain --text --by-method # which compression method saved what