rig-move-llm
Move the heavy lifting off your paid LLM. A local proxy that lets Claude Code keep planning on your paid subscription while every code change, file edit, test run, and knowledge lookup is delegated to a worker model of your choice — your own local model (llama.cpp / Ollama / ExLlama) or any API endpoint (OpenRouter, …). The worker's output is verified by a deterministic gate before it ever reaches the paid agent, so you save tokens without trading away correctness.
Status: early / pre-release. Proxy, translation, hooks, installer, and the reboot-safe daemon are working and validated end-to-end. The savings number below is a single measured instance (n=1) — treat it as evidence the mechanism produces a real saving on a real fix, then measure your own workload with
rig-move-llm stats.
Why
On a subscription, the only lever is paid-agent output tokens. rig-move-llm:
- Offloads every code change to a worker tool (
mcp__worker__implement) that runs on your endpoint, out-of-process; the main agent stays on Anthropic direct and only plans/reviews. - Forces delegation structurally — the paid agent plans/reviews only; a hook denies it the mutating/heavy tools so the work goes to the free/cheap worker.
- Gates the result deterministically (frozen fail-before repro + compile/lint floor + scoped regression) so a "cheaper" answer can't be a wrong answer.
- Brings your own model — local (free compute) or API (cheap, not free). Nothing points at anyone else's compute.
Headless / AFK oriented (worker models are slower than the frontier); not aimed at interactive latency.
What you save
Measured on one hard SWE-bench-style Python instance (flask-4045), main agent = Claude Sonnet, worker = a local 27B model, delegation enforced 100% by the hook:
- Paid-agent output tokens: −37% — 2,579 billed output tokens vs 4,069 solving the same fix solo. Every edit and test run — the heavy work — executed on the local worker, off your Anthropic quota. (A terser configuration reached −52% but skipped the review round that catches worker fallout; the shipping default keeps a mandatory regression check, so −37% is the number you actually get.)
- Correctness held. The target test passed and no pre-existing test regressed — and a solo baseline left this same instance unresolved, so here the hybrid solved what solo could not. This is not the general rule (see variance disclosure below); it is one honest data point.
- Wall time: slower — ~20 min here vs a few minutes solo (worker throughput). This tool is for headless / AFK / overnight work, not interactive latency.
This is n=1. It shows the mechanism produces a real output-token saving on a real
fix; it is not a benchmark. An earlier 7-task pilot of the predecessor (base-URL)
design showed the same shape — quality roughly at parity and output savings that are
bimodal (heavy tasks save the most; trivial tasks can cost more than they save,
because the fixed plan/review overhead dominates the little work delegated). That is why
this tool targets heavy, headless work. Measure your own with rig-move-llm stats.
What "savings" means, precisely
- Quota is counted server-side. Worker requests go to your endpoint and never reach api.anthropic.com — worker generation is 100% outside your Anthropic quota.
- It is not "100% free." Worker output that the main agent reads back counts as main-agent input tokens. Input is ~5× cheaper than output, and the deterministic gate shrinks what the main agent reads back to a short verdict — but it is not zero.
- Client-side displays lie a little. Claude Code's
/cost(and tools like ccusage) count tokens the client saw, including rerouted worker traffic — that display is cosmetic, not your bill.rig-move-llm statsseparates the legs honestly; its main-leg counts are validated to match Anthropic's own reportedinput_tokens/output_tokensexactly on a live instance. (Prompt-cache reads/writes are billed by Anthropic but not yet tracked in the ledger — the savings claims above are output-token-centric, which caching doesn't touch.) - Two worker tiers, two claims. A local worker (llama.cpp / Ollama / vLLM) is free compute — heavy work leaves your quota entirely. An API worker (OpenRouter etc.) is cheaper, not free: you pay per token, typically well below Claude output rates, and tool-call/streaming support varies by upstream model (which can affect how much actually gets delegated).
- Variance disclosure. Worker patches vary run-to-run (we observed two equivalent
patches differing by one line with different hidden-test outcomes). With arbitrary
user endpoints the variance is larger. We therefore claim the cost floor and
quality parity mechanism, not absolute resolve rates — measure on your own
workload (
rig-move-llm stats --history).
Architecture (one binary)
Claude Code ──> rig-move-llm proxy ─> api.anthropic.com (main leg: raw passthrough, OAuth untouched, usage metered)
│ (ANTHROPIC_BASE_URL)
└─ mcp__worker__implement ─────> your endpoint (worker leg: out-of-process MCP tool, Anthropic<->OpenAI, off the paid ledger)
The offload runs through a worker MCP tool, not the proxy: on Claude Code 2.1.x native
subagents run in-process and never egress to a base-URL proxy, so the main agent delegates
code work to mcp__worker__implement (out-of-process, guaranteed to reach your worker
endpoint). The proxy is the main-leg observability layer — it forwards the paid traffic
verbatim and meters what it spends. One static Go binary, stdlib-only, replaces what
previously needed a Node shim and Python LiteLLM; cross-compiles to macOS / Linux /
Windows (amd64 + arm64) with zero toolchain.
Configuration (bring-your-own endpoint)
WORKER_API_BASE=http://localhost:11434/v1 # Ollama / llama.cpp / ExLlama(TabbyAPI) — local or over Tailscale
WORKER_MODEL=qwen2.5-coder:32b
WORKER_API_KEY=... # or an OpenRouter key: https://openrouter.ai/api/v1
MAIN_UPSTREAM_URL=https://api.anthropic.com
PORT=4000
The setup wizard collects these for you — this is what it writes to config.env. Scope is
global (all projects, follows you) or project (this dir only); ENABLED gates the whole
thing on/off.
Install & use
npx rig-move-llm # one command → the setup wizard (installs itself on confirm)
claude # plain Claude Code — auto-delegates to the worker, no flags
npx rig-move-llm (or rig-move-llm setup if already installed) runs an interactive wizard: it
asks the scope (global = every project, follows you; or project = this dir), then the
worker endpoint — which you can skip by pressing Enter (rig installs but stays off, so
Claude Code runs exactly as normal; turn it on later). It auto-detects a local Ollama/llama.cpp and
offers it as the default. No config file to hand-edit. Because the hooks call rig-move-llm, the
wizard offers to npm install -g itself so it stays on your PATH — so the single npx command sets
up everything.
The wizard wires Claude Code so a plain claude (no flags, no wrapper) offloads to the worker:
the mcp__worker__implement tool, the force-delegate + gate hooks, and a terse
plan→delegate→review output style (.claude/output-styles/rig-delegate.md) that keeps the paid
agent's output small, plus .rig-move-llm/config.env.
- Global (follows you): registers the worker at user scope in
~/.claude.jsonand installs global hooks + output style + aSessionStarthook — so every project offloads with no per-project setup. On first session in a project theSessionStarthook lazily creates a.rig-move-llm/there — the way Serena creates.serena/. It inherits every setting from the global config (nothing is copied), so a later change to~/.rig-move-llm/config.env(endpoint, model,ENABLEDon/off) propagates to all projects. Add aKEY=valueline in a project's.rig-move-llm/config.envonly to override one setting there (e.g.ENABLED=falseto turn the hybrid off in that project alone). - Project: wires only this directory (a project-root
.mcp.json, pre-approved byenableAllProjectMcpServersso headless-pnever hangs on the trust prompt).
On / off switch. ENABLED in config.env is the master toggle: false (the default when you
skip the worker) means the hook passes every tool through and Claude Code behaves normally; set a
worker endpoint and ENABLED=true to activate the offload — no re-install needed. Flip it from the
CLI without touching the hidden dir: rig-move-llm enable / rig-move-llm disable (add --local to
scope the flip to this project only). rig-move-llm config prints the effective configuration — which
scope wins, the resolved worker endpoint, and the on/off state — and rig-move-llm config --open
opens the target scope's config.env in your $EDITOR. rig-move-llm run -- claude remains available when you also want the proxy's observability on the main leg. Reverse
everything with rig-move-llm uninstall (restores your settings.json verbatim; strips the
user-scope worker registration). rig-move-llm init [--global] [--npx] [flags] is the
non-interactive form for scripts (--npx spawns the worker via npx -y rig-move-llm worker, no
global binary needed for that leg).
The npm package ships a single prebuilt static binary per platform via
optionalDependencies (the esbuild/biome pattern — no postinstall download).
Permissions posture (headless)
Claude Code's auto-mode runs a model-based safety classifier before auto-approving Bash commands. In a headless hybrid run the main agent is already structurally denied mutating tools by the rig hook, and the worker's own tools run out-of-process on your endpoint — so the classifier is a redundant layer on the main leg. If you turn it off (headless allowlist / bypass permissions), understand what that means: you are trusting the hook + your own sandboxing instead of a second model opinion. Do this only for unattended runs in an environment you'd let a CI job loose in.
Subcommands:
rig-move-llm (no args) | setup guided setup wizard (scope + worker + wiring)
(arrow/space-select TUI on a terminal;
numbered line prompts when piped/headless)
rig-move-llm enable [--local] turn offload ON (flip ENABLED in config.env)
rig-move-llm disable [--local] turn offload OFF (Claude Code runs normally)
rig-move-llm config [--local] [--open] show the effective config / open it in $EDITOR
rig-move-llm serve [--port N] [--status] run the routing proxy / report state
rig-move-llm hook pre-tool|post-tool|session-start Claude Code hooks (force-delegate + gate + auto-materialize)
rig-move-llm init [--global] [--npx] [--service] [flags] non-interactive bootstrap
(--service: OS-supervised, survives reboots)
rig-move-llm uninstall [--global] [--purge] reverse init for a scope (incl. OS service)
rig-move-llm run [--] <command...> launch a command with the proxy wired in
rig-move-llm stats [--reset|--history] token accounting / savings
Agent teams (experimental)
Claude Code's experimental agent teams work under rig with no extra setup. In the
default in-process backend each teammate shares the lead's process and its tool
calls already carry an agent_id, so the force-delegate hook treats teammates as the
workers (allows their tools) while the lead stays plan/delegate/review-only.
The terminal backends (--teammate-mode tmux|iterm2) spawn each teammate as a
separate claude process whose hook payloads have no agent_id — they would
otherwise be mistaken for the paid lead and denied every tool. rig-move-llm run
points CLAUDE_CODE_TEAMMATE_COMMAND at itself so it can stamp the teammate's
identity (RIG_AGENT_ID, which the hook honors like agent_id) and, by default,
pin the teammate to a cheaper model tier (--model haiku). Set
RIG_TEAMMATE_MODEL=inherit to keep the model the lead requested, or
RIG_TEAMMATE_MODEL=<name> to force a specific one. A launcher you set yourself in
CLAUDE_CODE_TEAMMATE_COMMAND is never overwritten.
Note the scope honestly: model-pinning selects a cheaper Anthropic tier, it does
not route teammate inference to your worker endpoint — off-quota offload goes through
mcp__worker__implement, not the team path. Teams are interactive-only (headless -p
has no teams), so this path is not exercised by rig's CI smokes; treat it as
experimental.
Layout
cmd/rig-move-llm/ entrypoint
internal/cli/ subcommand dispatch (serve/hook/init/run/stats)
internal/service/ OS supervision (launchd / systemd --user / Task Scheduler), stdlib-only
internal/proxy/ main-leg observability (raw Anthropic passthrough + usage metering)
internal/worker/ the worker MCP tool (mcp__worker__implement): agentic loop on your endpoint
internal/hook/ force-delegate + deterministic-gate hooks (Go, no shell)
internal/config/ layered .env config + backend registry (Ollama first-class)
pkg/translate/ Anthropic <-> OpenAI translation library (importable, 27 conformance tests)
Build
go build -o rig-move-llm ./cmd/rig-move-llm # Go 1.22+: works as-is on all platforms
# Go 1.21 on macOS only (LC_UUID bug): add -ldflags=-linkmode=external && codesign -s - -f rig-move-llm
Cross-compile is pure-Go (CGO_ENABLED=0); CI builds all six targets
(darwin/linux/windows × amd64/arm64) — see .github/workflows/build.yml.
License
MIT — see LICENSE.