Last week, 76% of the AI tasks my systems ran never left my Mac. Drafts, classifications, code reviews, and structured extractions all went to local models on hardware I already own, and I kept the cloud APIs for the work that genuinely needs a frontier model. I only know the number because the router measures it, and that measurement layer was the piece I couldn't find anywhere. So I built it and put it on GitHub. It's called cheapskate.
It didn't start as an economics project. It started as a turf war over one computer. I'd bought a 128GB Mac to run local models and offload the bulk work my Claude agents were doing. Meanwhile my son Bryan, studying abroad in Sydney, was remoting into that same Mac over Tailscale to drive its big models from LM Studio on his laptop, half a world away. A 128GB box is generous until two people share it. He'd load a model, my jobs would try to load another, and the machine would thrash or run out of memory and kill whatever the other person was mid-way through. We needed a way for several people and several models to take turns on one Mac without stepping on each other. That coordinator is what cheapskate grew out of.
The hard part isn't the models, it's deciding
Local models are good enough now for a real share of everyday work. What nobody had built was the layer that decides where each task goes. Three questions kept nagging at me, and I was answering all of them by feel:
- Which tasks actually belong on a local model, and which need a frontier one?
- Is the local detour still saving anything once you count the retries and the tasks that escalate to the cloud anyway?
- A credible new local model drops about once a week. Do I switch, and on what evidence?
The tools that exist each solve a neighboring problem. LiteLLM is a gateway between cloud providers, GPUStack serves models across a cluster, RouteLLM picks a strong or weak model per query. What none of them do is sit above your local fleet and your cloud keys and route each task from measured cost and measured quality, which is the one thing I needed. The README has a head to head comparison, including where you should reach for one of those instead.
What it actually does
- A spend dial. One machine-readable setting, cloud-first at one end and local-only at the other, that every routing decision reads. It's how you tell the whole toolchain to be frugal this month without touching a line of config anywhere else.
- Safety classes that fail closed, both directions. Financial, legal, and medical tasks never run on a weak local model. Tasks you mark never-cloud never leave the box: if one can't run locally, it errors rather than quietly shipping your data off-machine. Both directions are pinned by tests.
- Verify and repair. The cheap model drafts against acceptance criteria you write, a verifier checks it, and after two failed repairs the task escalates with a structured signal. Your expensive tier becomes a reviewer instead of a first-drafter.
- Eval-gated model updates. New local models get discovered automatically but only promoted once they beat the incumbent on your eval suite on your hardware, with the incumbent and a rollback target protected. Switching models stops being a vibe.
- Receipts. Content-free telemetry (counts, durations, token counts, never your text) feeds a monthly report: percent routed local, retry and escalation rates, and true cost per task type. The cost math charges a task for its failed local attempts and its cloud escalation, which is the accounting most savings claims skip. Because it records tokens now, that cost is a real per-token number from your first task, not an estimate.
- Drop-in surfaces. An OpenAI-compatible endpoint (point any tool's
base_urlat it) and an MCP server. There are ready-made offload kits for Claude Code, Gemini CLI, and Codex, so those agents hand their bulk work to cheap models and keep their own tokens for the thinking.
It runs on top of what you already have. Serving stays on Ollama or MLX, the cloud adapters are thin and off by default, and it doesn't try to be a hundred-provider gateway.
The measured week
From my own telemetry, roughly eight days after the router went live (one bounded 23-minute outage excluded, details in BENCHMARKS):
| Measure | Value |
|---|---|
| Real delegations | 1,681 |
| Served locally | 76% |
| Adoption ramp | 41/day to 485/day within the week |
| Code-review role | 333 calls, 93% success, 2.5s median |
| A custom domain-expert role | 170 calls, 100% success |
I won't put a dollar figure on that week. My telemetry back then didn't record token counts, so any savings number would be a guess, and at single-user scale the honest wins were rate-limit headroom, latency, and privacy anyway. What changed since is that the shipped tool records tokens, so your receipts show a real per-token cost from the first task you run. The dollar story is now a report you run, not a number I ask you to trust. If you want to sketch your own numbers before installing anything, I put up a calculator that uses current list prices and shows its formula.
Why the whole thing is built around measurement
While I was extracting this for release, my local code reviewer started returning empty output on a 50KB diff after 276 seconds of apparent work. It was a thinking model that spent its entire output budget on internal reasoning and delivered nothing visible, and the OpenAI-compatible endpoint it ran behind silently ignored the parameters that would have fixed it. Rerouting through the native endpoint with thinking disabled for that role turned 276 seconds of nothing into a 48-second structured review that caught a legitimate critical bug in the diff.
Without telemetry and a fail-fast design, that bug is a silent 13-minute hang inside a release gate, which is exactly how it showed up the first two times. With them, it's an explicit escalate signal in seconds, a root cause you can point at, and a one-line config fix. That gap is the reason every failure path in cheapskate ends in a bounded, explicit signal instead of a wait, and it wasn't the only one I hit: shaking out the local task path for release turned up a string of these, down to the receipt itself briefly double-counting a run, each one caught because the numbers were visible.
Since this is also how I work with clients, I'll be transparent about the process: Claude agents did the bulk extraction and test-writing under my architecture and my review gates, and that pipeline routed its own cheap work through cheapskate. The system helped build itself, with the expensive model on design and verification. Bryan's sessions from Sydney and my agents' offload jobs still share the one Mac, which is the original problem, now handled by an admission queue that batches work by model and lets an interactive request cut the line instead of two runtimes fighting over memory. Cheapskate is co-created with him: the ideation, the testing, and the feature requests that shape the roadmap come from his side of the machine.
Honest limits
This is v0.1 from a single author with one week of production telemetry behind it. It's macOS-first: the power measurement uses Apple Silicon tooling, though the fleet serves anything Ollama runs, and the routing and receipts work the same on Linux minus the watt sampling. Streaming through the cloud path isn't built yet. The full multi-machine fleet agent is the next milestone, and today a remote backend is a config entry pointing at another box. Issues are welcome, an SLA is not.
If you run local models and can't answer "is this actually paying off," the repo has a two-minute path: pull one small model, point a role at it, and run a real task through the router. You get your first receipt the same day, not a week later.