From af778cea7bd9c6672e05fd21993c424d689090ca Mon Sep 17 00:00:00 2001 From: anshal21 Date: Fri, 10 Jul 2026 09:56:09 +0530 Subject: [PATCH 1/2] readme: quick-links row, sharper hero, comparison table, dedupe (marketing review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Head-of-marketing critique of the README, all actionable items applied: - Quick-links row under the banner (evo/drizzle/posthog pattern), including "Why whittle?" as a nav link to docs/why-write-time.md โ€” the posthog form of a Why section, surfacing the argument without top-of-page marketing prose. - Hero leads with the fidelity promise; routing demoted to the optional clause (one crisp promise for the 3-second read; routing is the second surface). - Highlights 7 -> 5 (fail-open+local merged, executable-claims folded into the motif line); fail-open deduped from 5 mentions to 2 load-bearing ones. - Comparison table in "Why write-time?" โ€” the differentiating axes vs read-time proxies, evidence-shaped instead of adjective-shaped. - Who-this-is-for line, honest star ask, explicit near-term roadmap, real alt text on the banner. - Dev version baseline bumped to 0.3.0 ahead of the tagged release that ships the router (the release itself resolves the README-vs-@latest gap the review flagged as P0). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 43 +++++++++++++++++++++++++++++++++---------- cmd/whittle/main.go | 2 +- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 32084cc..152d817 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,36 @@ # ๐Ÿช“ whittle -**Carve your agent's context down to what matters, and route every request to the cheapest model that can handle it.** +**Carve your agent's context down to what matters. Lossless or clearly marked, code never touched.** -*Local. Fail-open. Every claim in this README regenerates from a clone.* +*Optionally, route each request to the cheapest model that can handle it. Local, fail-open, and every claim in this README regenerates from a clone.* [![CI](https://github.com/firstops-dev/whittle/actions/workflows/ci.yml/badge.svg)](https://github.com/firstops-dev/whittle/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/firstops-dev/whittle)](https://github.com/firstops-dev/whittle/releases) [![Go Reference](https://pkg.go.dev/badge/github.com/firstops-dev/whittle.svg)](https://pkg.go.dev/github.com/firstops-dev/whittle) [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE) -![whittle: carve your agent's context down to what matters, and route every request to the right model](assets/banner.svg) +![banner: the whittle wordmark carving into green shavings, dense context flowing in on the left, three model tiers flowing out on the right](assets/banner.svg) + +

+ Install ยท + How it works ยท + Compression ยท + Model routing ยท + Benchmarks ยท + Why whittle? +

Long agent sessions drown in tokens, and most compressors buy their ratio by silently destroying what agents need: array rows vanish, file reads get gutted, identifiers come back mangled. Whittle holds one line: **lossless or clearly marked, code never touched, every anomaly fails open to the original bytes.** +Built for engineers running long Claude Code sessions who refuse to trade fidelity for token savings. + ## Highlights - ๐Ÿช“ **Write-time compression**: a Claude Code PostToolUse hook whittles each tool output *before* it enters context, so the savings repeat on every later turn. History is never mutated, so prompt caches stay intact. - ๐Ÿ”’ **Lossless or marked**: JSON reshapes byte-exact (rows are never dropped), logs keep every error plus an honest `[N lines omitted]`, source code passes through untouched. - ๐ŸŽฏ **Token-honest numbers**: savings measured against calibrated tokenizer counts, not byte counts that overstate by up to 4ร—. - ๐Ÿงญ **Opt-in model router**: hard reasoning stays on your strongest model, trivia drops to the cheapest, per one auditable policy file driven by trained-classifier signals ([how it works](docs/ROUTER.md)). -- ๐Ÿ›Ÿ **Fail-open everywhere**: whittle down means your agent runs on originals; a rejected rewrite means your original request, retried. Never blocked, never corrupted. -- ๐Ÿ  **Runs entirely on your machine**: zero credentials leave your box; the Go binary has zero external dependencies. -- ๐Ÿงพ **Executable claims**: `make test` and `go run ./bench` regenerate every number in this README from a clone. +- ๐Ÿ›Ÿ **Fail-open, local, yours**: if whittle is down your agent runs on originals, a rejected rewrite retries your original request, zero credentials leave your box, and the Go binary has zero external dependencies. ## Install @@ -60,7 +69,7 @@ Both errors, their context, and the summary survive; 138 lines of noise collapse ## Compression: what happens to each content type -JSON is reshaped **losslessly** (byte-exact reconstruction). Logs and terminal streams are cut lossily but **marked and exactly accounted**. Markdown file reads keep code fences, tables, and headings byte-exact while prose is compressed by an optional local model with fidelity guards. Source code is **never touched**. Every path is wrapped in fail-open guardrails: the worst case is always *not compressed*, never *corrupted*. +JSON is reshaped **losslessly** (byte-exact reconstruction). Logs and terminal streams are cut lossily but **marked and exactly accounted**. Markdown file reads keep code fences, tables, and headings byte-exact while prose is compressed by an optional local model with fidelity guards. Source code is **never touched**. Every path is guarded: the worst case is always *not compressed*, never *corrupted*. The full per-type contract, ML prose path, architecture, and performance tables: [docs/compression.md](docs/compression.md) ยท what each guarantee is pinned by: [GUARANTEES.md](GUARANTEES.md). @@ -71,14 +80,24 @@ Whittle's second surface: a local proxy on `ANTHROPIC_BASE_URL` that sends each - **Calibrated out of the box**: `whittle policy init` writes a conservative default (hard reasoning โ†’ strongest tier, confident chit-chat โ†’ cheapest, *everything else untouched*) with your account's real model ids auto-detected. [What it does & how to customize](router/policies/default.md). - **Multi-signal, not keyword-matching**: a trained 14-subject classifier (probability-mass thresholded, so an *uncertain* classification never escalates), a contrastive difficulty score, and your own keywords. Every log line shows each signal's value against its gate. - **Rewrites the model, never your history**: prompt-cache prefixes survive; capabilities the cheaper model rejects are stripped automatically; credentials pass through untouched. -- **Fail-open by construction**: bad policy, dead classifier, or a rejected rewrite all fall back to your original request. Unset the env var and you're direct again. +- **Never blocks you**: bad policy, dead classifier, or a rejected rewrite all fall back to your original request. Unset the env var and you're direct again. - **Savings you can measure**: every request logs requested model, served model, and real token usage. The router itself (engine, policy design, signal composition) is whittle's own; two pretrained models power its ML signals (the 14-subject `domain` classifier and the text embedder, both from [vLLM Semantic Router](https://github.com/vllm-project/semantic-router)). Architecture, signal math, and precise credits: [docs/ROUTER.md](docs/ROUTER.md). ## Why write-time? -Most context compressors are read-time proxies: they rewrite your conversation history on every LLM call, which breaks prompt caches, terminates your API traffic, and makes lossy compression the default. Whittle compresses each output **once, at the moment it's born**, before it enters history: savings compound across every later turn, nothing sits in your request path, and failure costs nothing. The full argument: [docs/why-write-time.md](docs/why-write-time.md). +Most context compressors are read-time proxies: they rewrite your conversation history on every LLM call, which breaks prompt caches, terminates your API traffic, and makes lossy compression the default. Whittle compresses each output **once, at the moment it's born**, before it enters history: savings compound across every later turn, and nothing sits in your request path. + +| | whittle | read-time compressors | +|---|---|---| +| compresses | once, at write time | re-runs on every LLM call | +| prompt cache | intact, history never rewritten | invalidated by history rewrites | +| your request path | nothing resident in it | a proxy that must stay up | +| loss model | lossless or marked | lossy by default, recover on demand | +| cost of failure | original bytes | a broken or blocked call | + +The full argument: [docs/why-write-time.md](docs/why-write-time.md). ## FAQ @@ -106,12 +125,16 @@ python bench/calibrate_tokens.py # reproduces the token-estimator MAE ## Contributing -The bar: guarantees are executable, see [CONTRIBUTING.md](CONTRIBUTING.md). Fidelity bugs (whittle changing an output's meaning) are treated as urgent. Good first issues: agent adapters, Linux packaging, detection corpus cases. +The bar: guarantees are executable, see [CONTRIBUTING.md](CONTRIBUTING.md). Fidelity bugs (whittle changing an output's meaning) are treated as urgent. + +Near-term roadmap: a tagged release that ships the router, agent adapters (Cursor, Codex, OpenCode), Linux packaging. Good first issues: adapters, packaging, detection corpus cases. ## Acknowledgments Whittle's log-selection strategy, several detection heuristics, and the tabular parser were adapted from [Headroom](https://github.com/headroomlabs-ai/headroom) (Apache-2.0); their compaction work is excellent; we wanted the write-time position and the stricter fidelity contract it demands. The router's two pretrained models (the `domain` classifier and the text embedder behind the similarity signals) come from [vLLM Semantic Router](https://github.com/vllm-project/semantic-router) ([whitepaper](https://vllm-semantic-router.com/white-paper)); the routing engine and policy design are whittle's own. See [NOTICE](NOTICE). +If whittle's fidelity contract is the trade you want, a โญ helps other agent users find it. + ## License Apache-2.0. diff --git a/cmd/whittle/main.go b/cmd/whittle/main.go index 2e8caea..fb1726a 100644 --- a/cmd/whittle/main.go +++ b/cmd/whittle/main.go @@ -23,7 +23,7 @@ import ( // version is injected by goreleaser (-X main.version=...); dev builds show the // last released baseline. -var version = "0.2.1" +var version = "0.3.0" func main() { if len(os.Args) < 2 { From 9614007da8e8d474c8cbfdcefe4e75df1aa49ba5 Mon Sep 17 00:00:00 2001 From: anshal21 Date: Fri, 10 Jul 2026 09:59:19 +0530 Subject: [PATCH 2/2] cli: report the real module version on plain go-install builds Verified post-release: `go install ...@latest` correctly fetched v0.3.0, but the binary printed the baseline var (plain go install runs no ldflags). Prefer the module version from runtime/debug.ReadBuildInfo when goreleaser didn't inject one, so @latest users see the tag they actually installed. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/whittle/main.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cmd/whittle/main.go b/cmd/whittle/main.go index fb1726a..2698356 100644 --- a/cmd/whittle/main.go +++ b/cmd/whittle/main.go @@ -14,6 +14,8 @@ import ( "io" "net/http" "os" + "runtime/debug" + "strings" "time" "github.com/firstops-dev/whittle" @@ -21,10 +23,23 @@ import ( "github.com/firstops-dev/whittle/server" ) -// version is injected by goreleaser (-X main.version=...); dev builds show the -// last released baseline. +// version is injected by goreleaser (-X main.version=...). For plain +// `go install module@vX.Y.Z` builds (no ldflags), the module version from build +// info is used instead โ€” so @latest users see the real tag, not the baseline. var version = "0.3.0" +func resolvedVersion() string { + if version != baselineVersion { + return version // goreleaser-injected + } + if bi, ok := debug.ReadBuildInfo(); ok && bi.Main.Version != "" && bi.Main.Version != "(devel)" { + return strings.TrimPrefix(bi.Main.Version, "v") + } + return version +} + +const baselineVersion = "0.3.0" + func main() { if len(os.Args) < 2 { usage() @@ -56,7 +71,7 @@ func main() { case "hook": cmdHook(os.Args[2:]) case "version": - fmt.Println("whittle", version) + fmt.Println("whittle", resolvedVersion()) default: usage() os.Exit(2)