Releases: fkiene/llmtrim
Release list
llmtrim 0.11.4
Added
- Status line shows the active
/subplan's rate limits (Codex first). Under a Codex
reroute the◔segment now reads the ChatGPT plan's windows (weekly always; 5h when the plan
reports one) fromGET /backend-api/wham/usage, cached at~/.llmtrim/sub-rate-limits.jsonby
the proxy on each throttled turn. A matching snapshot replaces Claude Code's Anthropic blob
only while the proxy is healthy; until the first poll lands (or if the proxy is down) the
Claude numbers stay so the segment isn't blank. Kimi/Grok share the cache schema; their usage
endpoints are not wired yet.
Fixed
- Codex traffic is compressed and tracked again. Codex CLI 0.144+ sends its request bodies
withContent-Encoding: zstd, which the interceptor could not parse, so every Codex turn was
forwarded verbatim and never reached the ledger —llmtrim statusshowed no Codex activity at
all (#193). The interceptor now decodes zstd- and gzip-encoded request bodies (capped at
256 MB) before compression and attribution, and forwards the body as plain JSON. A body it
cannot decode still passes through verbatim, so a decode problem can never break the call. - Codex sessions are labeled
codexin the cost breakdown again. Two attribution gaps hid
them: the fingerprint markers predated Codex 0.144's system prompt (which opens with "You are
Codex" and no longer says "Codex CLI"), and identity extraction read only the first
system/developer item of a Responses body — Codex now leads with a non-message
additional_toolsitem, so the identity message behind it was skipped. The fingerprint gained
the current marker and identity extraction concatenates every system/developer message,
skipping tool registrations (whose schemas would also destabilize the session hash).
llmtrim 0.11.3
Fixed
/compactredirects to a cheaper model only when the prompt cache is cold. The redirect
used to run on every/compact. But/compactre-sends the conversation Claude Code has been
caching against your selected model, so while that cache is warm a cache-read there is cheaper
than a cold read on a smaller model, so redirecting a warm compact cost more, not less. The
redirect now stays on your selected model when the session's last turn is within the cache TTL.
Unknown freshness (no session row yet, or a build without thebreakdownfeature) still
redirects, so the feature is never silently disabled.
llmtrim 0.11.2
Fixed
- Window
/subsurvives Claude Code/clear(and/compact) without the env token.
Retention used to depend only onLLMTRIM_CLAUDE_WINDOW_TOKENsurviving into the next
SessionStarthook. Claude Code often drops that env on clear, so/sub on grokwas lost and
the next turn fell back to Anthropic. SessionEnd now keeps the live session map and a
short-livedclearedbackup; SessionStart reattaches via session map, that backup, or the
env token last (so a stale token from another window cannot override this session). /subskill bash no longer trips Claude Code permission checks. Session id is read from
process env (not shell-expanded on the skill line), andBash(<llmtrim> window-sub slash *)is
pre-approved via skillallowed-tools+settings.permissions.allow.ensuretreats a
missing/stale allow rule as stale so upgrades self-heal.
llmtrim 0.11.1
Changed
- Overview and non-TTY status dollars match Sessions (frozen proxy bills). KPI "You paid" /
"Total saved" / today / week / trend / top models, and the pipedstatushero, read
breakdown_turns.bill_microsplus a per-turn frozen-rate input counterfactual
(llmtrim-ledger::money) — not live re-pricing ofcompressions. Lifetime $ may drop vs
older builds when breakdown retention is shorter than compressions, or for CLI/MCP-only
traffic (no session bill). Token % gauge is unchanged. When there is no proxy bill yet, the
UI shows a billing notice (not$0.00). - Sessions columns:
saved→saved$(money) +trim(token size %). Detail bill
footer usesSUM(bill_micros)(blocks allocate;*when they residual). status --json/ MCPllmtrim_stats: additivemoneyobject withunavailable+
null dollars when there is no proxy attribution. Legacycostremains compressions +
live list prices with"source": "compressions_live_prices"— prefermoneywhen
unavailableis false.
Added
- Money coverage UX on Overview (and doctor empty-case only) when compressions exist but no
breakdown bills — banner instead of fake$0.00. llmtrim-ledger::money: sharedMoneyTotals/ coverage / per-day / per-model queries;
sharedsaved_microsSQL for Overview and Sessions.- Grok remote auth: paste the callback URL or xAI one-time code into
llmtrim sub auth grok login, or use RFC 8628llmtrim sub auth grok devicefor SSH/headless
hosts (no localhost callback).
llmtrim 0.11.0
Breaking
SubProvider/StreamReducergain aGrokvariant and are marked#[non_exhaustive]so
further backends can land without a major bump. External exhaustivematches need a_arm.
Workspace version is 0.11.0 (0.x minor = semver-breaking).
Added
llmtrim ensure: bring this machine to the recommended state. Installs or refreshes owned
Claude Code integrations (statusline, cold-cache guard, window-local/sub, default compact
model chain), tray login when the GUI binary is present, and restarts a version-skewed daemon.
Opt-outs live in~/.llmtrim/integrations.jsonso ensure does not re-enable after an explicit no.llmtrim doctor --fixand statusfrun the same ensure path.- Quiet auto-heal on
startand status open when the binary version advanced or owned pieces
look stale (no prompts; no network tray download; no first-time installs). - Linux desktop tray one-shot download during interactive ensure when the tray binary is
missing (optional; default off when non-interactive). - One-time subscription onboarding in the status TUI (
sfor login steps,nto dismiss)
when Claude Code is present andsubis not configured. - Subscription reroute: Grok (SuperGrok / Grok Build).
llmtrim sub auth grok loginsigns in
via OAuth againstauth.x.ai(browser PKCE);llmtrim sub use grokmaps Claude tiers to
grok-4.5(opus/sonnet/fable) andgrok-composer-2.5-fast(haiku) and routes Claude Code's
Anthropic/v1/messagestraffic tocli-chat-proxy.grok.com/v1/responses. Tokens live at
~/.llmtrim/grok/auth.json. Works withsub mode fallbackchains (codex,kimi,grok) and the
window/subcommand. Device-code login is not available yet (browser only). - Window
/sub on <provider>. The Claude Code slash command accepts an explicit backend:
/sub on codex,/sub on kimi, or/sub on grok. Bare/sub onstill re-enables the last
window provider or the globalsubsetting. - Status line shows window
/suboverrides immediately. Mid-session/sub on grok(or any
provider) now paints→grok-4.5even when earlier turns were Anthropic and globalsubis
off. Window overrides are treated as always-mode for the arrow (matching the proxy), and a
policy switch overrides a stale ledger backend until the new one serves a turn. - Window
/subno longer inherits another provider's tier map. With globalllmtrim sub on codexand a Claude Code/sub on grok, the proxy was already routing to Grok but still applied
[sub.codex.tiers](e.g.opus → gpt-5.6-terra) on the Grok request. Tiers are now loaded per
serving provider, and foreign model ids in overrides are ignored. After upgrading, run
llmtrim ensure(orfinstatus) so owned/subhooks refresh and a version-skewed
daemon restarts onto the new binary.
Changed
setupand binary-channelupdatecall ensure. First install wires
statusline/guard//sub/compact. Binaryupdaterestarts the daemon and runsensure -qon the
new binary (never in-process after a self-replace). Package-manager channels print the package
command then a singlellmtrim ensurefollow-up.- Owned Claude Code hooks and statusline rewrite in place when the binary path or payload
(e.g.refreshInterval) changes. Quiet auto-heal only refreshes stale owned pieces and daemon
skew; it never first-installs integrations or enables tray login. - Top-level help leads with
setup/update/ensure/doctor. Power-user
statusline/guard/compactstay available but are hidden from the main command list. - Opt-outs stick:
statusline/guard/window-sub/compact offwrite
integrations.jsonopt-outs; corrupt state refuses auto-apply instead of resetting them. - Safer hooks and tray install: shell-quoted hook paths, atomic
settings.jsonwrites,
sharedCLAUDE_CONFIG_DIR, arch-aware tray download via path-filtered tar into a temp dir,
then promote.
llmtrim 0.10.2
Added
- Ordered model fallback for Claude Code
/compact. Configure[compact] models = ["haiku", "sonnet"]or runllmtrim compact models haiku sonnetto try cheaper models for compaction.
Each candidate is admitted against its known context window after candidate-specific compression;
failures before any output advance to the next candidate, and Claude Code's selected model remains
the implicit final fallback. The same chain works through subscription tier mappings, while
preserving the original model identity in the response.llmtrim setupproposes the default
chain on Claude Code installations that have not made a choice. llmtrim guard— stop paying for a cold cache without being told. Resume a session that has
been idle past the prompt-cache TTL and the next turn silently re-writes the entire context,
billed at the cache-write rate (2× base input for the 1h TTL Claude Code asks for). On a 225k
context — the median across my own idle gaps — that is a few dollars before any work happens, and
nothing at the prompt says so.guardis a Claude CodeUserPromptSubmithook: on the first
submit after a long gap on a big context it prints the idle time, the context size and what the
cold write costs, then exits 2, which blocks the prompt with no API call. A resend goes straight
through, and the blocked message is saved to disk since Claude Code does not restore it.llmtrim setupoffers to wire it in;guard install/guard uninstalldo it directly, merging into
~/.claude/settings.jsonrather than clobbering hooks you already have. It reads Claude Code's
transcript, not llmtrim's ledger — the ledger keys sessions by a hash of the system prompt, so a
subagent turn would mask a stale main conversation — and every failure path exits 0: a bug in the
guard must never block a prompt.
Changed
- The status line now refreshes while a session sits idle. It already reddened the cache segment
once the prompt cache expired, but Claude Code only re-renders on conversation events, so an
abandoned session kept the line drawn at its last turn — green and warm — straight through the TTL
expiring. The warning only appeared after the turn that paid for it. It now sets
refreshInterval, so the state is on screen before you type. Re-runllmtrim statusline install
to pick it up. - The cold-cache segment no longer suggests
/compact. It read♻ cold · /compact, which sold
compaction as the thrifty move. It isn't:/compactre-reads the same cold context to summarise
it — paying the charge it appears to avoid — and the next turn then writes a fresh cache for the
summary. Resending pays once. The segment now just reports the state:♻ cache cold.
llmtrim 0.10.1
Fixed
-
Codex reroute failed every turn after the first. The ChatGPT backend accepts
previous_response_idonly over its WebSocket transport; on the HTTP/responsespath llmtrim
uses, it answers400 Unsupported parameter: previous_response_id. Server-side continuation
defaulted to on, so from the second turn onward every rerouted request died — and it was
incoherent with thestore: falsellmtrim sends anyway, since there is no stored response to
continue from. Continuation now defaults to off (still available via
LLMTRIM_CODEX_PREVIOUS_RESPONSE_IDor[sub.codex] previous_response_id, for a transport that
accepts it), and prefix caching viaprompt_cache_keycarries the cache instead. A rejected
continuation now also heals itself: the request is re-issued in full rather than surfacing the
400, so an enabled-by-config continuation degrades instead of breaking the session. -
Compression could rewrite the system prompt. The compressible set came solely from the cache
zone, so a request with nocache_controlmarkers had nothing frozen and the text-mutating
stages were free to fold n-grams through the instructions. On Claude Code's title-generation
call — which carries no markers — this rewrote the few-shot examples, deleting the conditional
that gave them meaning. Instructions are what the model conditions on rather than reads as data,
so a fold that is harmless in a tool result can invert a directive.system,instructionsand
system-role messages are now excluded from compression unconditionally, cached or not. This
costs nothing on real traffic (593 of 594 captured requests already hadcache_controlon
system; replaying 58 of them produces byte-identical output) and closes the gap for the small
utility calls that carry no markers. -
Status line blanked out mid-turn. The cache percentage and context gauge were read only from
Claude Code's blob, which fillscurrent_usageandtotal_input_tokensfrom the last
response. Claude Code re-runs the status line on every render, so while a turn was in flight both
were absent: the cache segment vanished and the gauge emptied, then both snapped back when the
turn landed — which reads like the prompt cache collapsing on every message. The proxy measures
the same quantities on the wire, so the last completed turn now stands in across the gap. The
blob still wins whenever it carries a figure, and a fresh session with no recorded turn renders
empty rather than inventing a value.
Added
- Reroute request capture. With
capture_dirset, the body actually sent to a reroute backend
is recorded alongside the existing before/after pair. The capture previously stopped at llmtrim's
own pipeline output, leaving the translated payload — the thing the upstream prompt cache keys
on — unobservable, which made cache behaviour undersubimpossible to diagnose from evidence.
llmtrim 0.10.0
Fixed
- Status line claimed a reroute that never happened in
subfallback mode. The reroute arrow
and the context gauge were derived from config: ifsubwas set, the line rendered
◆ Opus→gpt-5.6-terraon every turn. But infallbackmode Anthropic serves the turn and the
chain fires only when Anthropic fails — so the arrow named a backend that hadn't answered, and
the gauge rescaled to that backend's context window, under-reporting how full the real (Claude)
window was. The serving backend is now recorded per turn in the ledger (newsub_provider
column, added to existing ledgers on open) and the status line reads that: the arrow appears
only on a turn a backend genuinely served, and shows the model it ran. Inalwaysmode, where
every turn reroutes, config still seeds the arrow before the first turn lands, so nothing
changes there.
Added
- Ordered subscription fallback chains.
llmtrim sub mode fallbacknow tries a list of
providers in order instead of a single one:llmtrim sub chain codex,kimi(or
LLMTRIM_SUB_CHAIN=codex,kimi, or[sub] chain = ["codex", "kimi"]). When Anthropic cannot
serve a turn — quota, payment, throttling, overload, or a transient server failure — Anthropic is
retried once, and if it still fails each provider in the chain is tried in turn (with bounded
backoff, honoringRetry-After) until one answers. A failure reported inside an HTTP 200
stream counts as a failure too, and is caught before any of it reaches the client. With no chain
configured, the activesubprovider is the chain, so existing setups behave as before.
Changed
- Breaking (config):
sub mode on-erroris nowsub mode fallback. The old name still works —
llmtrim sub mode on-error,LLMTRIM_SUB_MODE=on_error, and[sub] mode = "on_error"all keep
meaning fallback, and print a one-line notice — but the new spelling is what gets persisted. - Breaking (library):
llmtrim-core'sRuntimeConfig::sub_on_erroris nowsub_fallback, and
RuntimeConfiggained asub_chainfield. Hence the 0.10 minor bump.
Fixed
-
Upstream connection failures are reported instead of swallowed (#157).
A request that failed before the upstream answered (a TLS error, a reset, a dropped
keep-alive) fell through to the proxy library's default handler: an empty-bodied502, with
the cause logged only through atracingsubscriber llmtrim never installs. Claude Code could
not parse the reply, so the turn died — and nothing anywhere recorded why. A transport failure
is now reported in the shape the client is reading (an SSEerrorframe on a streaming call, a
retryableoverloaded_errorotherwise), and the underlying cause is named on stderr, so
llmtrim servein the foreground shows what actually broke. -
Subscription reroute: the Codex prompt cache no longer goes cold every turn. Claude Code
prepends a billing-header system block whose hash changes on every turn, and the Codex translator
forwarded it at the head of the Responsesinstructions— the very front of the cached prefix —
so each turn re-sent a different prefix and paid a full cold cache. The block is now dropped for
both Codex and Kimi. On a 4-turn session, cached input read back rose from 4.6k to 34.3k tokens. -
Subscription reroute: the status line shows trim again. Rerouted turns never recorded their
Claude Code session, so the trim segment sat at✂ –even though compression was running
normally. It now reports the session's real savings (✂ 39.8%on the run above). -
GPT-5.6 Codex reroutes. Claude model requests keep the standard Responses shape and use the
official Codex originator and user-agent identity required by the backend. -
Subscription reroute: Claude Code thinking no longer 502s on Codex. The Codex
reducer streamedthinking_deltasummaries but never forwarded the upstream
encrypted_contentas Anthropicsignature_delta, so Claude Code rejected the SSE
stream (502 / dropped connection) and follow-up turns could not replay thinking blocks.
Signatures now map both ways (encrypted_content↔signature), text waits for a late
signature when needed, and adaptivethinkingwithout an explicitoutput_config.effort
still enables Codex reasoning. -
Claude Code statuslines survive package upgrades.
llmtrim statusline installnow keeps
a stable executable path when the active binary is also reachable through a symlink onPATH
(as with Homebrew's versionedCellarlayout), andllmtrim updatere-points an existing
llmtrim-owned entry before the upgrade runs, so the statusline no longer ends up pointing at
a removed binary.
llmtrim 0.9.4
Fixed
-
Low cache reuse on
sub codex/sub kimi. Follow-up turns now reuse backend state viaprevious_response_id+ output transcript, producing the expected high cache hit rates instead of full resends. -
Status line stayed red after
/compact. The cold-cache segment (♻ cold · /compact) and
context gauge keyed only on the interceptor ledger'slast_ts./compactruns inside Claude
Code and does not update that timestamp, so the line kept warning after a successful compact.
The renderer now clears the cold state when Claude Code reports a post-compact reset (empty
context, nocurrent_usageyet).
Changed
- Status line shows the resolved upstream model on
subreroutes. Codex reroutes now render
the concrete backend model (e.g.→gpt-5.6-terra) instead of the provider shortname (→codex);
Kimi still shows→kimi.
llmtrim 0.9.3
Fixed
- The desktop tray popover rendered blank. Every installed tray (macOS, Linux, Windows) showed
an empty white box under the tray icon instead of the stats UI. The shipped binaries were built
with plaincargorather thancargo tauri build, which left Tauri'scustom-protocolfeature
off, so the webview ran in dev mode and tried to load a Vite dev server that does not exist on a
user's machine.custom-protocolis now a default feature, so release builds embed and serve the
real UI. (#149)