Eliminates P0-2 universally. Every Python forwarder (server.py
`_retry_request`, handlers/streaming.py `_stream_response`,
handlers/openai.py `_ws_http_fallback`, handlers/batch.py `_batch_passthrough`
+ batch-create + Google batch passthrough, handlers/anthropic.py CCR
continuation + batch endpoint) now switches from `httpx ... json=body` to
`httpx ... content=raw_bytes`. The default httpx JSON encoder was
re-serializing every request with `, `/`: ` separators and `\\uXXXX` ASCII
escapes — collapsing Anthropic prompt-cache hit-rate.
Forwarder strategy:
- unmutated body → forward `await request.body()` verbatim;
- mutated body → re-serialize once via the new
`serialize_body_canonical(body) -> bytes` helper (compact separators,
`ensure_ascii=False`, dict insertion order preserved).
`HEADROOM_PROXY_PYTHON_FORWARDER_MODE` env var configures the mode:
- `byte_faithful` (default) — the new behavior;
- `legacy_json_kwarg` — explicit operator opt-in for emergency rollback.
Documented in `docs/content/docs/configuration.mdx`. NOT a fallback —
unknown values raise loudly per build constraint #4.
`BodyMutationTracker` accompanies each request through the handler so
transform sites mark the tracker (`memory_injection`,
`image_compression`, `compression_*`, `batch_compression`,
`ccr_continuation`, etc.). At forwarder dispatch we additionally compare
the final body dict against the parsed original bytes as a structural
safety net — any silent mutation we missed still triggers canonical
re-serialization.
A2 follow-up: `handlers/openai.py:534-540` (Chat Completions memory
injection) was prepending a system message; replaced with
`append_text_to_latest_user_chat_message`, the OpenAI Chat Completions
analog of `_append_context_to_latest_non_frozen_user_turn`. The cache
hot zone (system messages) is now sacrosanct on /v1/chat/completions
too. Honors `HEADROOM_MEMORY_INJECTION_MODE=disabled`.
Structured logging: every forwarder emits an `event=outbound_request`
log line with `forwarder`, `path`, `body_bytes`, `body_mutated`,
`mutation_reasons`, `source` (passthrough|canonical|legacy),
`request_id`. Never logs Authorization or full body.
`_read_request_json` factored to share `_read_request_body_bytes` with
new `read_request_json_with_bytes` so the anthropic handler can capture
both the parsed dict and the original (decompressed) bytes.
Tests:
- `tests/test_proxy_byte_faithful_forwarding.py` (28 tests):
SHA-256 byte-equality on /v1/messages and streaming, unicode
preservation, numeric precision, mutation-tracker invariants,
canonical-serializer properties, legacy-mode rollback, OpenAI
Chat memory routing.
- Existing test mocks updated to accept the new `**kwargs` on
`_retry_request` (no behavior change).
- `tests/test_proxy_handlers_batch.py` updated to read the captured
`content=` bytes (formerly `json=`).
- One A2 test corrected (`test_anthropic_tool_sort_and_context_append_helpers`)
to match the live-zone-tail semantics introduced by A2.
Constraints satisfied: configurable env var; no new regex / hardcodes;
no silent fallback (`legacy_json_kwarg` is operator opt-in);
performant (`prepare_outbound_body_bytes` is O(1) for passthrough);
elegant single-responsibility helpers; structured tracing logs.
P0-1: Delete `_inject_system_context` from `proxy/server.py`. Memory
context now routes exclusively to the first text block of the latest
non-frozen user message via `_append_context_to_latest_non_frozen_user_turn`
(promoted to the canonical default in handlers/anthropic.py). Mirror
applied to OpenAI Responses API at handlers/openai.py: `body["instructions"]`
is no longer mutated; memory context appends to the latest user item in
`body["input"]`.
P2-23: Replace `headroom/transforms/cache_aligner.py` with a detector-only
implementation. The legacy rewrite path (~400 LOC) is removed. The volatile-
content detector uses no regex — UUIDs via `uuid.UUID`, ISO 8601 via
`datetime.fromisoformat`, JWT shape via base64url segment-count check, hex
hashes via length + `int(token, 16)` validation. Volatile findings surface
through `cache_metrics`/`warnings`/`logger.warning`; the prompt is never
mutated.
Configurability: new env var `HEADROOM_MEMORY_INJECTION_MODE` with values
`live_zone_tail` (default) and `disabled`. No `system_prompt` value — that
path is permanently retired.
Structured logs: every memory injection emits `event=memory_injection`
with `decision`, `bytes_injected`, `query_hash` (BLAKE2b, never raw query),
`session_id`, `request_id`. Auth is never logged.
Tests:
- Add `tests/test_proxy_system_prompt_immutable.py` (7 tests).
- Add `tests/test_cache_aligner_detector_only.py` (20 tests).
- Replace `tests/test_transforms/test_cache_aligner.py` (rewrite-path
tests, 58 cases) with detector-only behavior.
- Update `tests/test_acceptance.py::TestDateTrap` to pin the new
detector-only contract.
Acceptance:
- `git grep -n "_inject_system_context\|_inject_to_system_or_instructions" headroom/`
returns nothing.
- `git grep -n "import re\|from re import" headroom/transforms/cache_aligner.py`
returns nothing.
- Targeted suite (`test_proxy_system_prompt_immutable.py`,
`test_cache_aligner_detector_only.py`, `test_proxy_anthropic_cache_stability.py`,
`test_acceptance.py::TestDateTrap`, `test_memory*.py`, `test_cli/`) green.