fix(compression): reject lossy unmarked tool output in unit router path#1479
Merged
JerrettDavis merged 2 commits intoJun 30, 2026
Merged
Conversation
Contributor
PR governanceThis PR follows the template and is marked ready for human review. |
75c947f to
6790f03
Compare
JerrettDavis
approved these changes
Jun 27, 2026
JerrettDavis
left a comment
Collaborator
There was a problem hiding this comment.
The direction looks good and the second commit narrows the guard to the risky local shell-output path, which preserves recoverable summaries while preventing fabricated multi-line command output from replacing the original. The regression coverage exercises both the reject and recoverable cases. I do not see a blocker in this revision.
Merged
chopratejas
added a commit
that referenced
this pull request
Jun 30, 2026
…mpress-v2-base (#1613) ## Description Adds an opt-in flag that routes **all** compressible content through Kompress (`kompress-v2-base`), bypassing per-type compressor selection (SmartCrusher / CodeAware / log / diff / html / tabular / search). For deployments that prefer a single uniform compressor over the per-type set, at a deliberate cost of per-type structural fidelity. The mechanism already existed: `ContentRouter` reads a `force_kompress` runtime kwarg but nothing turned it on. This PR wires it to user-facing config (CLI + env), defaulting off. Closes # N/A ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Add `force_kompress_all` to `ProxyConfig` (`headroom/proxy/models.py`) and `ContentRouterConfig` (`headroom/transforms/content_router.py`). - Default the existing `force_kompress` runtime path from config: `kwargs.get("force_kompress", self.config.force_kompress_all)` — a per-request kwarg still overrides. - Expose `--force-kompress-all` CLI flag and `HEADROOM_FORCE_KOMPRESS_ALL=1` env, mirroring the existing `--disable-kompress` pattern (both the env factory and the `__main__` CLI path). - Add `tests/test_force_kompress_all.py`. **Safety preserved:** the flag changes *strategy selection only*. The Read/Glob/Grep exclusion (`excluded_tool_ids`) runs *before* any compressor, and the tool-output reversibility gate (`#1307`/`#1479`) runs *after* — neither is reachable from the strategy choice. So tool ground truth stays verbatim. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] Type checking passes (`mypy`) - [x] New tests added for new functionality - [ ] Manual testing performed (see Real Behavior Proof → Not tested) ### Test Output ```text $ ruff check headroom/proxy/models.py headroom/transforms/content_router.py headroom/proxy/server.py tests/test_force_kompress_all.py All checks passed! $ ruff format --check <same files> 4 files already formatted $ mypy headroom/proxy/models.py headroom/transforms/content_router.py headroom/proxy/server.py tests/test_force_kompress_all.py Success: no issues found in 4 source files $ pytest tests/test_force_kompress_all.py tests/test_content_router_exclude_tools.py -q tests/test_force_kompress_all.py .... [ 44%] tests/test_content_router_exclude_tools.py ..... [100%] ============================== 9 passed in 1.31s =============================== ``` ## Real Behavior Proof - **Environment:** macOS (Darwin 25.4.0), Python 3.12.6, project `.venv`. - **Exact command / steps:** Constructed `ContentRouter(ContentRouterConfig(force_kompress_all=True))` and drove the real `apply()` entry point (see `tests/test_force_kompress_all.py`) to verify: (1) the config resolves the runtime flag on; (2) an explicit `force_kompress=False` kwarg overrides it; (3) a `Read` tool_result is passed through **verbatim** with the flag on (`router:excluded:tool` marker present). Plus the full ruff/mypy/pytest suite above. - **Observed result:** 9 tests pass. Read tool output is unchanged (byte-for-byte) under `force_kompress_all=True`; the per-request kwarg override works; the existing exclude-tools suite still passes through `HeadroomProxy` (which now builds `ContentRouterConfig(force_kompress_all=...)`). - **Not tested:** Live proxy end-to-end against a real upstream with the `kompress-v2-base` ONNX model compressing real traffic; aggregate savings/accuracy deltas on a real workload. The unit tests assert the **routing decision and the Read/Glob carve-out**, not model output quality or ratio. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation — N/A (documented inline via config docstring + `--help`; see Additional Notes) - [x] My changes generate no new warnings - [x] I have added tests that prove my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable — N/A (Release Please generates it from the `feat(proxy):` commit) ## Additional Notes - **Accuracy tradeoff (intentional):** forcing Kompress on all types trades per-type structural fidelity (and possibly compression ratio, since SmartCrusher/CodeAware can beat a general model on their native type) for a single uniform compressor. Off by default; opt-in per deployment. Correctness is *not* affected — excluded tools and reversibility-gated tool ground truth are never touched. - **Docs:** behavior is documented inline (CLI `--help` text + `ProxyConfig` docstring). Happy to add a README/wiki note if maintainers want one.
19 tasks
chopratejas
pushed a commit
that referenced
this pull request
Jul 3, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>0.29.0</summary> ## [0.29.0](v0.28.0...v0.29.0) (2026-07-03) ### Features * **proxy:** add --lossless no-CCR mode with format-native compaction ([#1721](#1721)) ([c75ebde](c75ebde)) * **stats:** surface Codex WS compression counters in /stats summary ([#1680](#1680)) ([2fe19c3](2fe19c3)) * **transforms:** adaptive Otsu KEEP/DROP threshold (+ land relevance split on main) ([#1726](#1726)) ([eea667a](eea667a)) ### Bug Fixes * **bedrock:** fail fast when session-token auth lacks botocore ([#1553](#1553)) ([54cfa36](54cfa36)) * **bedrock:** route ARNs via converse, named AWS profiles, and au. re… ([#1456](#1456)) ([7d87aa2](7d87aa2)) * **ccr:** honor workspace dir for sqlite store ([#1564](#1564)) ([96e1dfe](96e1dfe)) * **claude:** surface Remote Control proxy incompatibility ([#1610](#1610)) ([4bf7f92](4bf7f92)) * **cli:** stop advertising unwired compression tuning env vars in banner ([#1634](#1634)) ([d5bf98d](d5bf98d)) * **codex:** avoid duplicate headroom provider config ([#1431](#1431)) ([ddd4adf](ddd4adf)) * **compression:** reject lossy unmarked tool output in unit router path ([#1479](#1479)) ([de24cd5](de24cd5)) * **cortex-code:** migrate to current Cortex REST API endpoints + add e2e benchmarks ([#1474](#1474)) ([f00ace6](f00ace6)) * **dashboard:** align token savings headline denominator ([#1653](#1653)) ([646e705](646e705)) * **dashboard:** derive per-project setup URL from live origin ([#1511](#1511)) ([e035aef](e035aef)) * **detection:** contain unidiff panic on orphaned +++ target line ([#1548](#1548)) ([e386c09](e386c09)) * **evals:** CJK-aware F1 tokenization + token estimation ([#1527](#1527)) ([99a8540](99a8540)) * **install:** close parent log fd in start_detached_agent ([#1576](#1576)) ([816cb85](816cb85)) * **install:** use Windows-safe PID liveness probe in runtime_status ([#1544](#1544)) ([#1560](#1560)) ([6b227b9](6b227b9)) * **learn:** aggregate verbosity baselines across projects instead of overwriting ([#1288](#1288)) ([27a5468](27a5468)) * **mcp:** show lifetime totals and label rolling session scope in headroom_stats ([#1428](#1428)) ([1c0e152](1c0e152)) * **memory:** cap local embedder CPU thread oversubscription ([#198](#198)) ([#1559](#1559)) ([b84afbf](b84afbf)) * **memory:** singleflight LocalBackend init to stop cold-start races ([#1691](#1691)) ([bec47a1](bec47a1)) * **openclaw:** detect uv-installed headroom binary in ~/.local/bin ([#1459](#1459)) ([adaeb88](adaeb88)) * **opencode:** preserve custom OpenAI gateway paths ([#1596](#1596)) ([c19347c](c19347c)) * **opencode:** route native providers + load transport plugin, fix Serena context ([#1573](#1573)) ([ad0034f](ad0034f)) * preserve anthropic passthrough tool order ([#1427](#1427)) ([a932247](a932247)) * **proxy/auth:** match real Anthropic OAuth token prefix (sk-ant-oat) ([#1672](#1672)) ([8cddf9b](8cddf9b)) * **proxy:** expose persistent savings metrics ([#1647](#1647)) ([5fe4e7b](5fe4e7b)) * **proxy:** fail open when kompress saturation would exhaust pre-upstream budget ([#1430](#1430)) ([15ac650](15ac650)) * **proxy:** handle streaming CCR retrieval ([#1451](#1451)) ([d337e3b](d337e3b)) * **proxy:** include system/tools/sampling in cache key ([#1473](#1473)) ([312129a](312129a)) * **proxy:** preserve Responses passthrough bytes ([#1598](#1598)) ([2a34a82](2a34a82)) * **proxy:** strip Codex lite header on the HTTP /responses path ([#1663](#1663)) ([9fbd47b](9fbd47b)) * **proxy:** wire --compression-max-workers / HEADROOM_COMPRESSION_MAX_WORKERS ([#1632](#1632)) ([814ffa3](814ffa3)) * **savings:** count cache-read tokens in input cost estimate ([#1429](#1429)) ([72ade37](72ade37)) * skip Magika backend on x86 CPUs without AVX2 ([#1162](#1162)) ([64783d8](64783d8)) * **transforms/content-router:** route grep/log output away from HTML extractor ([#1719](#1719)) ([0d18ef2](0d18ef2)) * **transforms:** bound native content detection with a Windows watchdog ([#575](#575)) ([#1563](#1563)) ([95abca3](95abca3)) * Vertex AI support for Claude Code with ANTHROPIC_VERTEX_BASE_URL ([#1393](#1393)) ([cff7247](cff7247)) * **wrap:** detach the shared proxy on Windows so it survives an ungraceful agent close ([#1464](#1464)) ([6cba441](6cba441)) * **wrap:** preserve custom Vertex base URL ([#1477](#1477)) ([75427bb](75427bb)) * **wrap:** remove rtk instructions from Codex AGENTS.md on unwrap ([#1604](#1604)) ([c9d717c](c9d717c)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
JerrettDavis
pushed a commit
to peterlodri-sec/headroom
that referenced
this pull request
Jul 14, 2026
…th (headroomlabs-ai#1479) ## Description Closes headroomlabs-ai#1342 Codex shell output currently goes through the unit-router compression path as a plain `local_shell_call_output` string. When that path picks a lossy strategy and the compressed text carries no CCR retrieval marker, the agent gets a summary that can't be reversed back to the original shell log. That breaks the point of showing command output at all. This change keeps structured shell output verbatim unless the replacement stays recoverable. Other tool-output paths stay unchanged. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `headroom/transforms/compression_units.py`: add a lossy-strategy set and a structured-shell heuristic, then reject lossy unmarked replacements for `role="tool"` plus `item_type="local_shell_call_output"` by returning the original text with `reason="lossy_unrecoverable_tool_output"`. - `tests/test_compression_units.py`: add regression coverage for the failing case, the recoverable-marker case, non-shell tool output, and assistant text so the guard stays scoped. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [ ] Manual testing performed ### Test Output ```text GitHub Actions on head 6790f03: - PR Governance: green - CI: green, including lint, build-wheel, docker-native-e2e, test shards 1-4, test-agno, test-dashboard-ui, and test-extras - Init E2E: green - Wrap E2E: green - Evaluation Suite smoke-test: green ``` ## Real Behavior Proof - Environment: current PR head `6790f034862cb7f6e698c745d729061b4d112aae` in GitHub Actions. - Exact command / steps: exercise `compress_unit_with_router` with structured multi-line `local_shell_call_output`, return a lossy unmarked replacement, and assert the original shell text is kept with `reason="lossy_unrecoverable_tool_output"`. Paired tests prove that CCR-marked replacements still compress, non-shell tool output still compresses, and assistant text still compresses when explicitly allowed. - Observed result: the new regression coverage passes on the PR head and the full PR check set is green. - Not tested: end-to-end live shell sessions through the Responses API; intentionally unstructured shell output below this heuristic remains compressible. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A, backend compression-path change. ## Additional Notes `mypy headroom` was not run for this PR body refresh, so the type-check box stays unchecked here. The changelog and docs boxes are N/A for this targeted bug fix.
JerrettDavis
pushed a commit
to peterlodri-sec/headroom
that referenced
this pull request
Jul 14, 2026
…mpress-v2-base (headroomlabs-ai#1613) ## Description Adds an opt-in flag that routes **all** compressible content through Kompress (`kompress-v2-base`), bypassing per-type compressor selection (SmartCrusher / CodeAware / log / diff / html / tabular / search). For deployments that prefer a single uniform compressor over the per-type set, at a deliberate cost of per-type structural fidelity. The mechanism already existed: `ContentRouter` reads a `force_kompress` runtime kwarg but nothing turned it on. This PR wires it to user-facing config (CLI + env), defaulting off. Closes # N/A ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Add `force_kompress_all` to `ProxyConfig` (`headroom/proxy/models.py`) and `ContentRouterConfig` (`headroom/transforms/content_router.py`). - Default the existing `force_kompress` runtime path from config: `kwargs.get("force_kompress", self.config.force_kompress_all)` — a per-request kwarg still overrides. - Expose `--force-kompress-all` CLI flag and `HEADROOM_FORCE_KOMPRESS_ALL=1` env, mirroring the existing `--disable-kompress` pattern (both the env factory and the `__main__` CLI path). - Add `tests/test_force_kompress_all.py`. **Safety preserved:** the flag changes *strategy selection only*. The Read/Glob/Grep exclusion (`excluded_tool_ids`) runs *before* any compressor, and the tool-output reversibility gate (`headroomlabs-ai#1307`/`headroomlabs-ai#1479`) runs *after* — neither is reachable from the strategy choice. So tool ground truth stays verbatim. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] Type checking passes (`mypy`) - [x] New tests added for new functionality - [ ] Manual testing performed (see Real Behavior Proof → Not tested) ### Test Output ```text $ ruff check headroom/proxy/models.py headroom/transforms/content_router.py headroom/proxy/server.py tests/test_force_kompress_all.py All checks passed! $ ruff format --check <same files> 4 files already formatted $ mypy headroom/proxy/models.py headroom/transforms/content_router.py headroom/proxy/server.py tests/test_force_kompress_all.py Success: no issues found in 4 source files $ pytest tests/test_force_kompress_all.py tests/test_content_router_exclude_tools.py -q tests/test_force_kompress_all.py .... [ 44%] tests/test_content_router_exclude_tools.py ..... [100%] ============================== 9 passed in 1.31s =============================== ``` ## Real Behavior Proof - **Environment:** macOS (Darwin 25.4.0), Python 3.12.6, project `.venv`. - **Exact command / steps:** Constructed `ContentRouter(ContentRouterConfig(force_kompress_all=True))` and drove the real `apply()` entry point (see `tests/test_force_kompress_all.py`) to verify: (1) the config resolves the runtime flag on; (2) an explicit `force_kompress=False` kwarg overrides it; (3) a `Read` tool_result is passed through **verbatim** with the flag on (`router:excluded:tool` marker present). Plus the full ruff/mypy/pytest suite above. - **Observed result:** 9 tests pass. Read tool output is unchanged (byte-for-byte) under `force_kompress_all=True`; the per-request kwarg override works; the existing exclude-tools suite still passes through `HeadroomProxy` (which now builds `ContentRouterConfig(force_kompress_all=...)`). - **Not tested:** Live proxy end-to-end against a real upstream with the `kompress-v2-base` ONNX model compressing real traffic; aggregate savings/accuracy deltas on a real workload. The unit tests assert the **routing decision and the Read/Glob carve-out**, not model output quality or ratio. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation — N/A (documented inline via config docstring + `--help`; see Additional Notes) - [x] My changes generate no new warnings - [x] I have added tests that prove my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable — N/A (Release Please generates it from the `feat(proxy):` commit) ## Additional Notes - **Accuracy tradeoff (intentional):** forcing Kompress on all types trades per-type structural fidelity (and possibly compression ratio, since SmartCrusher/CodeAware can beat a general model on their native type) for a single uniform compressor. Off by default; opt-in per deployment. Correctness is *not* affected — excluded tools and reversibility-gated tool ground truth are never touched. - **Docs:** behavior is documented inline (CLI `--help` text + `ProxyConfig` docstring). Happy to add a README/wiki note if maintainers want one.
JerrettDavis
pushed a commit
to peterlodri-sec/headroom
that referenced
this pull request
Jul 14, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>0.29.0</summary> ## [0.29.0](headroomlabs-ai/headroom@v0.28.0...v0.29.0) (2026-07-03) ### Features * **proxy:** add --lossless no-CCR mode with format-native compaction ([headroomlabs-ai#1721](headroomlabs-ai#1721)) ([c75ebde](headroomlabs-ai@c75ebde)) * **stats:** surface Codex WS compression counters in /stats summary ([headroomlabs-ai#1680](headroomlabs-ai#1680)) ([2fe19c3](headroomlabs-ai@2fe19c3)) * **transforms:** adaptive Otsu KEEP/DROP threshold (+ land relevance split on main) ([headroomlabs-ai#1726](headroomlabs-ai#1726)) ([eea667a](headroomlabs-ai@eea667a)) ### Bug Fixes * **bedrock:** fail fast when session-token auth lacks botocore ([headroomlabs-ai#1553](headroomlabs-ai#1553)) ([54cfa36](headroomlabs-ai@54cfa36)) * **bedrock:** route ARNs via converse, named AWS profiles, and au. re… ([headroomlabs-ai#1456](headroomlabs-ai#1456)) ([7d87aa2](headroomlabs-ai@7d87aa2)) * **ccr:** honor workspace dir for sqlite store ([headroomlabs-ai#1564](headroomlabs-ai#1564)) ([96e1dfe](headroomlabs-ai@96e1dfe)) * **claude:** surface Remote Control proxy incompatibility ([headroomlabs-ai#1610](headroomlabs-ai#1610)) ([4bf7f92](headroomlabs-ai@4bf7f92)) * **cli:** stop advertising unwired compression tuning env vars in banner ([headroomlabs-ai#1634](headroomlabs-ai#1634)) ([d5bf98d](headroomlabs-ai@d5bf98d)) * **codex:** avoid duplicate headroom provider config ([headroomlabs-ai#1431](headroomlabs-ai#1431)) ([ddd4adf](headroomlabs-ai@ddd4adf)) * **compression:** reject lossy unmarked tool output in unit router path ([headroomlabs-ai#1479](headroomlabs-ai#1479)) ([de24cd5](headroomlabs-ai@de24cd5)) * **cortex-code:** migrate to current Cortex REST API endpoints + add e2e benchmarks ([headroomlabs-ai#1474](headroomlabs-ai#1474)) ([f00ace6](headroomlabs-ai@f00ace6)) * **dashboard:** align token savings headline denominator ([headroomlabs-ai#1653](headroomlabs-ai#1653)) ([646e705](headroomlabs-ai@646e705)) * **dashboard:** derive per-project setup URL from live origin ([headroomlabs-ai#1511](headroomlabs-ai#1511)) ([e035aef](headroomlabs-ai@e035aef)) * **detection:** contain unidiff panic on orphaned +++ target line ([headroomlabs-ai#1548](headroomlabs-ai#1548)) ([e386c09](headroomlabs-ai@e386c09)) * **evals:** CJK-aware F1 tokenization + token estimation ([headroomlabs-ai#1527](headroomlabs-ai#1527)) ([99a8540](headroomlabs-ai@99a8540)) * **install:** close parent log fd in start_detached_agent ([headroomlabs-ai#1576](headroomlabs-ai#1576)) ([816cb85](headroomlabs-ai@816cb85)) * **install:** use Windows-safe PID liveness probe in runtime_status ([headroomlabs-ai#1544](headroomlabs-ai#1544)) ([headroomlabs-ai#1560](headroomlabs-ai#1560)) ([6b227b9](headroomlabs-ai@6b227b9)) * **learn:** aggregate verbosity baselines across projects instead of overwriting ([headroomlabs-ai#1288](headroomlabs-ai#1288)) ([27a5468](headroomlabs-ai@27a5468)) * **mcp:** show lifetime totals and label rolling session scope in headroom_stats ([headroomlabs-ai#1428](headroomlabs-ai#1428)) ([1c0e152](headroomlabs-ai@1c0e152)) * **memory:** cap local embedder CPU thread oversubscription ([headroomlabs-ai#198](headroomlabs-ai#198)) ([headroomlabs-ai#1559](headroomlabs-ai#1559)) ([b84afbf](headroomlabs-ai@b84afbf)) * **memory:** singleflight LocalBackend init to stop cold-start races ([headroomlabs-ai#1691](headroomlabs-ai#1691)) ([bec47a1](headroomlabs-ai@bec47a1)) * **openclaw:** detect uv-installed headroom binary in ~/.local/bin ([headroomlabs-ai#1459](headroomlabs-ai#1459)) ([adaeb88](headroomlabs-ai@adaeb88)) * **opencode:** preserve custom OpenAI gateway paths ([headroomlabs-ai#1596](headroomlabs-ai#1596)) ([c19347c](headroomlabs-ai@c19347c)) * **opencode:** route native providers + load transport plugin, fix Serena context ([headroomlabs-ai#1573](headroomlabs-ai#1573)) ([ad0034f](headroomlabs-ai@ad0034f)) * preserve anthropic passthrough tool order ([headroomlabs-ai#1427](headroomlabs-ai#1427)) ([a932247](headroomlabs-ai@a932247)) * **proxy/auth:** match real Anthropic OAuth token prefix (sk-ant-oat) ([headroomlabs-ai#1672](headroomlabs-ai#1672)) ([8cddf9b](headroomlabs-ai@8cddf9b)) * **proxy:** expose persistent savings metrics ([headroomlabs-ai#1647](headroomlabs-ai#1647)) ([5fe4e7b](headroomlabs-ai@5fe4e7b)) * **proxy:** fail open when kompress saturation would exhaust pre-upstream budget ([headroomlabs-ai#1430](headroomlabs-ai#1430)) ([15ac650](headroomlabs-ai@15ac650)) * **proxy:** handle streaming CCR retrieval ([headroomlabs-ai#1451](headroomlabs-ai#1451)) ([d337e3b](headroomlabs-ai@d337e3b)) * **proxy:** include system/tools/sampling in cache key ([headroomlabs-ai#1473](headroomlabs-ai#1473)) ([312129a](headroomlabs-ai@312129a)) * **proxy:** preserve Responses passthrough bytes ([headroomlabs-ai#1598](headroomlabs-ai#1598)) ([2a34a82](headroomlabs-ai@2a34a82)) * **proxy:** strip Codex lite header on the HTTP /responses path ([headroomlabs-ai#1663](headroomlabs-ai#1663)) ([9fbd47b](headroomlabs-ai@9fbd47b)) * **proxy:** wire --compression-max-workers / HEADROOM_COMPRESSION_MAX_WORKERS ([headroomlabs-ai#1632](headroomlabs-ai#1632)) ([814ffa3](headroomlabs-ai@814ffa3)) * **savings:** count cache-read tokens in input cost estimate ([headroomlabs-ai#1429](headroomlabs-ai#1429)) ([72ade37](headroomlabs-ai@72ade37)) * skip Magika backend on x86 CPUs without AVX2 ([headroomlabs-ai#1162](headroomlabs-ai#1162)) ([64783d8](headroomlabs-ai@64783d8)) * **transforms/content-router:** route grep/log output away from HTML extractor ([headroomlabs-ai#1719](headroomlabs-ai#1719)) ([0d18ef2](headroomlabs-ai@0d18ef2)) * **transforms:** bound native content detection with a Windows watchdog ([headroomlabs-ai#575](headroomlabs-ai#575)) ([headroomlabs-ai#1563](headroomlabs-ai#1563)) ([95abca3](headroomlabs-ai@95abca3)) * Vertex AI support for Claude Code with ANTHROPIC_VERTEX_BASE_URL ([headroomlabs-ai#1393](headroomlabs-ai#1393)) ([cff7247](headroomlabs-ai@cff7247)) * **wrap:** detach the shared proxy on Windows so it survives an ungraceful agent close ([headroomlabs-ai#1464](headroomlabs-ai#1464)) ([6cba441](headroomlabs-ai@6cba441)) * **wrap:** preserve custom Vertex base URL ([headroomlabs-ai#1477](headroomlabs-ai#1477)) ([75427bb](headroomlabs-ai@75427bb)) * **wrap:** remove rtk instructions from Codex AGENTS.md on unwrap ([headroomlabs-ai#1604](headroomlabs-ai#1604)) ([c9d717c](headroomlabs-ai@c9d717c)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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
Closes #1342
Codex shell output currently goes through the unit-router compression path as a plain
local_shell_call_outputstring. When that path picks a lossy strategy and the compressed text carries no CCR retrieval marker, the agent gets a summary that can't be reversed back to the original shell log. That breaks the point of showing command output at all.This change keeps structured shell output verbatim unless the replacement stays recoverable. Other tool-output paths stay unchanged.
Type of Change
Changes Made
headroom/transforms/compression_units.py: add a lossy-strategy set and a structured-shell heuristic, then reject lossy unmarked replacements forrole="tool"plusitem_type="local_shell_call_output"by returning the original text withreason="lossy_unrecoverable_tool_output".tests/test_compression_units.py: add regression coverage for the failing case, the recoverable-marker case, non-shell tool output, and assistant text so the guard stays scoped.Testing
pytest)ruff check .)mypy headroom)Test Output
Real Behavior Proof
6790f034862cb7f6e698c745d729061b4d112aaein GitHub Actions.compress_unit_with_routerwith structured multi-linelocal_shell_call_output, return a lossy unmarked replacement, and assert the original shell text is kept withreason="lossy_unrecoverable_tool_output". Paired tests prove that CCR-marked replacements still compress, non-shell tool output still compresses, and assistant text still compresses when explicitly allowed.Review Readiness
Checklist
Screenshots (if applicable)
N/A, backend compression-path change.
Additional Notes
mypy headroomwas not run for this PR body refresh, so the type-check box stays unchecked here. The changelog and docs boxes are N/A for this targeted bug fix.