fix(content-router): protect_tool_results must not be weakened by profile-derived read_protection_window#2105
Merged
chopratejas merged 2 commits intoJul 13, 2026
Conversation
…file-derived read_protection_window ContentRouter.apply() computed read_protection_window from protect_recent_reads_fraction (0.0 = "protect all excluded-tool output regardless of conversation depth", the sentinel --protect-tool-results sets per headroomlabs-ai#1374's documented contract), but then unconditionally overwrote that window with a per-request read_protection_window kwarg whenever one was present. proxy_pipeline_kwargs() supplies that kwarg on every request from the active AgentSavingsProfile.protect_recent (the default "coding" profile sets protect_recent=2), so in practice only the last 2 messages ever kept read-protection regardless of --protect-tool-results. The runtime kwarg may now only narrow the window when protect_recent_reads_fraction > 0; it can no longer shrink the "protect everything" guarantee set by --protect-tool-results. Adds a regression test to each of the two existing exclude-tools test files, both verified to fail without the fix and pass with it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
PR governanceThis PR does not yet satisfy the required template fields:
Please update the PR body, or move the PR back to draft while it is still in progress. |
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
ContentRouter.apply()computesread_protection_windowfromprotect_recent_reads_fraction, where0.0(the sentinel--protect-tool-resultssets, per #1374's documented contract) means "protect all excluded-tool output regardless of conversation depth." The method then unconditionally overwrote that window with a per-requestread_protection_windowkwarg whenever one was present.proxy_pipeline_kwargs()supplies that kwarg on every request from the activeAgentSavingsProfile.protect_recent(the defaultcodingprofile setsprotect_recent=2), so in practice only the last 2 messages ever kept read-protection regardless of--protect-tool-results— older excluded-tool output (Read,Glob,Grep,Write,Editresults) silently fell through to lossy Kompress compression.Closes #
Type of Change
Changes Made
headroom/transforms/content_router.py: the runtimeread_protection_windowkwarg may now only narrow the window whenself.config.protect_recent_reads_fraction > 0. It can no longer override the0.0("protect everything") sentinel that--protect-tool-resultssets.tests/test_content_router_exclude_tools.py: regression coverage that--protect-tool-results-equivalent config (protect_recent_reads_fraction=0.0) stays fully protected even when a savings-profile kwarg would otherwise shrink the window.tests/test_transforms/test_content_router.py: unit coverage of the precedence logic itself (kwarg narrows when fraction > 0, kwarg is ignored when fraction == 0.0).CHANGELOG.md: added an### Bug Fixesentry underUnreleased.Testing
pytest)ruff check .)mypy headroom)Test Output
Real Behavior Proof
headroom install apply) with--backend bedrock --mode token --code-aware --protect-tool-results Bash,HEADROOM_SAVINGS_PROFILE=coding(library defaultprotect_recent=2), fronting a live Claude Code session.Reada source file, continue the conversation past 2 more assistant turns (so the file'sReadresult ages past the profile'sprotect_recent=2window), then have the agent re-read or reference the same file.Readoutput for a plain (non-code) file came back as[N items compressed to M. Retrieve more: hash=...]despite--protect-tool-resultsbeing set andReadsitting inDEFAULT_EXCLUDE_TOOLS— confirmed by direct proxy log inspection (content_router.py's override silently winning over the0.0sentinel) and by byte-diffing the installed pipx package against this same fork's git source to rule out a stale build. After applying the fix, the same sequence leaves the agedReadoutput intact (no compression marker) — verified viapytestregression tests plus a fresh live-session check post-deploy.--mode cache(upstream's tested/benchmarked default for thecodingprofile as of68676daa), where the wholeread_protection_windowmechanism this bug lives in is structurally unreachable for anything inside the frozen prefix — so the precedence fix in this PR is primarily relevant totoken-mode deployments (or any deployment where cache mode's frozen-prefix boundary hasn't yet advanced past the affected message). It has not been independently re-verified live under--mode tokenafter the most recent rebase ontomain(only the automated test suite was rerun post-rebase).Review Readiness
Checklist
Screenshots (if applicable)
N/A — backend logic change, no UI surface.
Additional Notes
docs/,README.md, orCONTRIBUTING.mddocumentsread_protection_window,protect_recent_reads_fraction, or--protect-tool-resultsprecedence at all, so there was no existing section to update, and no new section was added either. This is arguably a pre-existing documentation gap this PR doesn't close.headroomlabs-ai/headroomissue first.