status
What runs, and what has not been answered
The pruner exists. Whether it is safe to use is not yet known.
winnow publishes to no package channel, so installing means a checkout. Nothing on this page is a roadmap: it is the state of the tree, and the rows below that carry no measurement are the ones the kill criteria are written against.
built
In the tree today
winnow context- What is in one session's context window, by provenance, with the total taken exactly out of the last priced request and the parts apportioned inside it. About 500 lines with 30 tests.
winnow inspect- Section 4 of
docs/SPEC.md, its six rules, the five guards, the cache readout andT*. About 600 lines with 54 tests, and milestone 1's number. Deprecated since 2026-09-03 in favour ofcontext, and kept rather than repaired for the filter arithmetic below the part that misleads. winnow plan- The dry run: which results a fork would replace, the pointer that would replace each, what the pointers cost, the net, and
T*for the cut. About 450 lines with 53 tests. winnow forkandwinnow recover- The writer and the round trip.
forkconsumesplan's list rather than reclassifying, so the dry run is the fork you get;recoverprints the original bytes back out of the untouched source. About 700 lines with 66 tests. winnow filter- The intake filter and the local proxy that carries it. Stdlib only, about 1,350 lines with 804 tests, of which a property test over generated conversations asserts the cache-write invariant and a golden pins the emitted bytes.
winnow savings- Prices the filter's own ledger against the transcripts, de-duped on
tool_use_idso a stateless filter's repeats are not counted as removals. Stdlib only, about 575 lines with 34 tests. winnow trial- Compares two configurations on what they were actually billed rather than on the cost model: it reads
message.usageoff the transcripts, attributes each session to whichever arm was switched on at the time, and divides. It reports no saving, deliberately — a saving needs a counterfactual and there is none here. - the validation harnesses
src/winnow/validate/— the 100-fork resume test, the stratified blind label with its sampler and scorer, and the disk-cost series. About 1,900 lines with 76 tests. Nowinnowcommand imports them, and none of it runs in the suite except through its own fixtures, so a green suite cannot be mistaken for a passed milestone.- orchestrator-safe mode
- The command groups and the gate around the vendored tool, about 1,450 lines with its tests. Built and tested; never run inside a real orchestrated cycle.
- the inherited tree
src/winnow/legacy/,plugin/andtests/— Cozempic 1.8.39 by Ruya AI, about 21,700 lines, renamed into winnow. Still not installed and not started.
02 · context
The instrument, and where it misses
winnow context writes nothing. It takes the window total exactly out of the last priced request and apportions the transcript inside it, so the shares sum to a number that is right by construction and the error lives in where the tokens were attributed rather than in how many there are.
Milestone 1's number is winnow inspect's, and it has been produced: tier CB strips 10.2% of message content pooled and 8.8% at the median, against the 22.6% / 21.6% section 6 of docs/SPEC.md recorded and the 3 points either way section 9 asked it to reproduce within.
It misses by 12.4 points, and 8.7 of those are one rule whose measured number was taken with a looser definition than the same document specifies. The population lands where the method says it should — 174 sessions over 400 KB of message content, 129.6 MB pooled, against a recorded 161 and 120.1 MB one day earlier — so the denominator is not the disagreement.
docs/SPEC.md and the reproduced share is milestone 1's own, both pooled over sessions carrying more than 400 KB of message content. A share is a ceiling on the mechanism rather than a saving: the removed bytes were being billed at 0.1×.python -m winnow context <session-id>python -m winnow context <session-id> --window 200000python -m winnow context <session-id> --auditpython -m winnow context <session-id> --color never
There is no default for --window and there will not be one: nothing in a transcript states the window size, and a session on a 1M-context model reports 512,133 tokens, which a hardcoded 200,000 would render as 256% full. --audit prints the whole reconciliation and the chars-per-token constant that would zero the residual, beside the fact that it was not applied. --color takes auto, always or never, and auto colours only when the output is a terminal.
Netted against the cache — 0.1·D earned on each turn that followed the cut, 1.9·S − 2·D paid once — a tier-CB cut pays off in 58% of sessions and is worth +3.27% of the bill, on an optimistic bound, against the 15% the success criteria set as the target.
Milestone 1 was built to be allowed to say that.
winnow inspect now opens by deprecating itself, and names the figure that forced the label. Every token number it prints is a lifetime sum over every assistant record in the file rather than a window: its cache_read_input_tokens reads 18,378,780 on a session whose window was 219,485 at the last request. Read as a window, that is the misreading winnow context was built to prevent.
03 · filter
The filter, and what its table is
On a replay over 175 historical sessions the filter reaches 8.21% and is worth +3.76% of the bill, against the tier-CB pruner's 10.17% and +3.27%. The ratio is 1.1×. What separates them is variance rather than size: the filter cannot be negative, and it pays in 175 sessions out of 175 against the pruner's 97 of 168.
That table was computed at a 2,048-byte floor which the filter no longer uses. Re-measured over one install's 869 transcripts, moving the floor to the 256 it now ships with takes the reach from 8.03% to 10.82% of message content and the net up 31%. The dollar column has not been recomputed and still describes the old floor.
Either way the table is a simulation — what the filter would have done. winnow savings is the instrument, and it prices one install's own ledger rather than a corpus average.
05 · safe
Six guarantees, held from outside the tree
The vendored tool assumes an interactive user: it can defer a prune until you quit, ask you to run init, and start a daemon that will SIGKILL a session it judges too large. Under an unattended harness there is nobody to defer to, and the session that daemon would kill is the one the tool is running inside.
Nothing in src/winnow/legacy/ was modified to do any of it — which is the more honest test: a wrapper that has to patch the thing it wraps has not shown the thing is safe to run.
export WINNOW_ORCHESTRATOR=1python -m winnow safe checkpython -m winnow safe plugin-dir --out ./outpython -m winnow safe run -- list
One switch. safe check prints what would be refused and why; safe plugin-dir writes a --plugin-dir with SessionStart removed; safe run puts a vendored command through the gate.
- no termination
- It never terminates the session it runs inside. The guard daemon cannot be started and
guard-watchdog --fix, which signals, is refused. Not deferred: the harness spawns headless, so there is no interactive quit to defer to. - no resume
--resumeand session identity belong to the harness, soreload, which spawns aclaude --resumewatcher, is refused.- no updater
- No auto-update, no PyPI check, no version drift — and not switched off but removed. There is no updater module, no
self-updatesubcommand and no upgrade step in the SessionStart hook, so no code path installs a package. - no writes to
~/.claude - That directory is a bind mount shared with the host. No global hook installation and no
settings.jsonthe mode does not own; loading happens through--plugin-dir, and the checkpoint the vendored tool would write inside the mount goes to winnow's own data directory instead. - no competing controls
- The harness owns
--autocompactand the per-cycle budget, so a mutating prune is refused while a Claude process is live and belongs between cycles. - nothing written to memory
digest injectwrites to~/.claude/projects/*/memory/; it is refused, and the plugin directory drops the skills and the MCP server.
The mode has never run inside a real orchestrated cycle — everything was exercised by hand in a container. No network call was proved absent, only switched off. The guard was never enabled, deliberately.
06 · unbuilt
What has not been answered
- the 100-fork resume test
- Fork 100 real sessions,
claude --resumeeach, 0 failures. Attempted 2026-09-07 and blocked by its environment before a single fork was written, so nothing was resumed and no model was called. What the run did settle is the population: it walked all 1,089 sessions of one corpus, 212 were forkable against a target of 100, and the cold-age guard refused 7 of them. - the 200-sample blind label
- At least 90% of stripped results confirmed once-only, with per-rule precision reported separately. The sampler, the sheet, the scorer and the scoring rule are committed before any labelling, so the bar cannot be settled after the numbers are in. No result has been labelled.
- a week of accumulated disk cost
- Measured rather than estimated. The script is committed and its series has no observations, and no disk-cost figure appears anywhere in the repository — one observation is not a week.
winnow bench- Milestone 3, and the quality arm with it. Not started — which is why no tool here can be called superior to another.
- a saving
- Any claim that pruning a Claude Code session saves money is unmade, by anyone.
Milestone 2's kill criteria are written against the first three rows above: aggregate rule precision under 80% on the blind label stops it outright, one fork that produces an unresumable session stops it unless a same-day change to the pairing guard fixes it, and loosening --min-cold-age to find a population is itself a kill condition. Stopping on any of them is the intended outcome rather than a failure of it.
install
There is no package to install
winnow publishes to no channel. The six package channels, the npm shim and the PyPI release workflow the inherited tree arrived with were deleted, and packaging/README.md is the record of them. Installing means a checkout.
winnow's own tests are unittest.TestCase classes rather than pytest functions, for one reason: they have to run where the mode runs, and the harness container has no pip, no venv and no pytest. Stdlib only, no network. pytest collects the same file unchanged.
git clone https://github.com/Xapicc/winnowcd winnowPYTHONPATH=src python3 -m unittest tests.test_orchestrator_safe
The inherited suite is a different matter: running it writes into your home directory. It added seven hooks to ~/.claude/settings.json, wrote ~/.winnow_global_initialized, and left fixture content in ~/.winnow/behavioral-digest.md. It leaves a settings.<timestamp>.bak beside the file it edited, which is how it was caught.
WINNOW_ORCHESTRATOR=1 python -m winnow safe check before and after will tell you.