Closes#847
## What
Three safety rails, each of which only ever makes compression LESS
aggressive — zero behavior change for content that compresses normally:
1. **Error-output protection** (`ContentRouter`) — failed tool calls
pass through verbatim on both the OpenAI `role=tool` string path and the
Anthropic `tool_result` block path. Triggered by the explicit `is_error:
true` flag or the existing Rust error-indicator detector
(`headroom._core.content_has_error_indicators`, previously only used for
TOIN signatures). Capped by `error_protection_max_chars` (8000, ~2K
tokens) so big error-laden CI logs still reach `LogCompressor`, which
preserves error lines — the two features stay complementary.
`protect_error_outputs=False` disables.
2. **Pipeline circuit breaker** (`TransformPipeline`) — after 3
consecutive transform failures, `apply()` passes messages through
untouched for a 60s cooldown instead of re-running (and re-failing)
transforms on every request. Env-tunable:
`HEADROOM_PIPELINE_BREAKER_THRESHOLD` (0 disables),
`HEADROOM_PIPELINE_BREAKER_COOLDOWN_S`. Passthrough results tagged
`pipeline:circuit_open`; a clean run closes the breaker. Thread-safe
(lock-guarded counters, `time.monotonic`).
3. **Library inflation guard** (`compress()`) — all four proxy handlers
already revert when "optimization" inflates tokens; the public library
path returned inflated messages as-is. Now mirrors the proxy guard and
tags `inflation_guard:reverted`.
## Why
Production agent research backs each rail: keeping error outputs
verbatim measurably improves agent recovery (Manus context-engineering;
JetBrains "Complexity Trap", arXiv:2508.21433); Claude Code added its
consecutive-compaction-failure cap after telemetry showed failure loops;
the inflation guard closes a library/proxy asymmetry.
All three follow CONTRIBUTING's "Safety first: never drop user/assistant
content, prefer false negatives."
## Tests
`tests/test_compression_safety_rails.py` — 10 tests:
- error protection: string path, `is_error` flag (with neutral text
proving the flag alone triggers), indicator scan, size-cap fall-through,
config-disable
- circuit breaker: opens after threshold + passthrough, success resets
count, cooldown expiry closes, env-disable
- inflation guard: inflated result reverts to originals
Regression: `test_transforms_content_router`, `test_pipeline`,
`test_compress_api`, `test_compress_failure`, `test_canonical_pipeline`,
`test_proxy_pipeline_lifecycle`, `test_observability_*`,
`test_compression_policy` — 69 passed. `ruff check` + `ruff format
--check` clean.
---------
Co-authored-by: Ash Rhodes <ashley.rhodes@king.com>