mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
The freeze path (both providers) emits the agent's ORIGINAL bytes for a frozen message, but the provider cached whatever we FORWARDED last turn (the compressed form). Forwarding original then mismatches the cached prefix and busts it from that point — re-creating the whole suffix. Measured on a real SWE-bench run: 100% of attributed misses were prefix_change, ~56% of ALL cache-writes were bust-induced (2.8M tokens), driving cache_create +150% and cost +41% vs baseline. Cache mode already avoided this via _extract_cache_stable_delta (replay the previously-forwarded prefix, compress only the delta). Token mode called apply(frozen_count) directly, which forwards original for the frozen region. Fix: add a shared, provider-agnostic overlay_cached_prefix() that replays the previously-forwarded (cached, compressed) prefix byte-identical, append-only guarded and idempotent, and apply it in BOTH the Anthropic and OpenAI handlers right before forwarding. This makes freezing byte-identical in every mode, so the only remaining difference between "token" and "cache" mode is how large a mutable (still-compressible) tail each leaves — not whether the frozen prefix busts the cache. Tests: - test_cache_prefix_overlay.py: the helper (replay, append-only guard, idempotence). - test_cross_turn_cache_safety.py: the invariant that was missing — drive the REAL tracker + freeze + overlay over multiple append-only turns against a simulated provider prefix cache and assert the forwarded prefix stays byte-identical turn-over-turn. Load-bearing: it fails (detects the bust) without the overlay. ## Description <!-- Briefly explain the change and why it is needed. --> Closes # ## Type of Change - [ ] 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 - ## Testing <!-- Check what you actually ran, then paste the real command output below. --> - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [ ] Manual testing performed ### Test Output ```text # Paste relevant command output or artifact links here ``` ## Real Behavior Proof - Environment: - Exact command / steps: - Observed result: - Not tested: ## Review Readiness - [ ] I have performed a self-review - [ ] This PR is ready for human review ## Checklist - [ ] My code follows the project's style guidelines - [ ] 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 - [ ] My changes generate no new warnings - [ ] 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) Add screenshots to help explain your changes. ## Additional Notes <!-- Mention any N/A checklist items, tradeoffs, follow-ups, or maintainer context. -->
79 lines
3.6 KiB
Python
79 lines
3.6 KiB
Python
"""overlay_cached_prefix: freeze must forward the CACHED (compressed) bytes.
|
|
|
|
The freeze path can emit the agent's ORIGINAL bytes for a frozen message, but
|
|
the provider cached whatever we FORWARDED last turn (the compressed form).
|
|
Forwarding original then mismatches the cached prefix and busts the prompt cache
|
|
(observed: 100% of misses were this ``prefix_change``, ~56% of all cache-writes).
|
|
``overlay_cached_prefix`` replays the previously-forwarded prefix byte-identical
|
|
so the cache still hits — in BOTH proxy modes.
|
|
"""
|
|
|
|
from headroom.cache.prefix_tracker import overlay_cached_prefix
|
|
|
|
|
|
def M(role, text):
|
|
return {"role": role, "content": text}
|
|
|
|
|
|
# Previous turn: 2 messages. Original was big; we FORWARDED the compressed form,
|
|
# so that compressed form is what the provider cached.
|
|
PREV_ORIG = [M("user", "READ foo.py:\n<2000 original lines>"), M("assistant", "ok")]
|
|
PREV_FWD = [M("user", "READ foo.py:\n<compressed>"), M("assistant", "ok")]
|
|
# This turn: agent appended one new message (append-only growth).
|
|
CUR_ORIG = PREV_ORIG + [M("user", "grep result:\n<800 original lines>")]
|
|
# What apply() produced in the buggy freeze path: ORIGINAL bytes for the frozen
|
|
# prefix (== PREV_ORIG) + compressed new tail.
|
|
OPTIMIZED_BUGGY = [PREV_ORIG[0], PREV_ORIG[1], M("user", "grep result:\n<compressed>")]
|
|
|
|
|
|
def test_replays_cached_compressed_prefix_byte_identical():
|
|
out = overlay_cached_prefix(OPTIMIZED_BUGGY, CUR_ORIG, PREV_ORIG, PREV_FWD)
|
|
# The frozen prefix now equals what the provider cached (compressed), NOT the
|
|
# agent's original bytes → cache hits instead of busting.
|
|
assert out[:2] == PREV_FWD
|
|
assert out[:2] != PREV_ORIG
|
|
# This turn's compressed tail is preserved.
|
|
assert out[2] == OPTIMIZED_BUGGY[2]
|
|
assert len(out) == len(CUR_ORIG)
|
|
|
|
|
|
def test_is_a_noop_relative_to_cache_when_already_correct():
|
|
# If the freeze path already forwarded the compressed (cached) prefix, the
|
|
# overlay reproduces exactly that — idempotent.
|
|
already_correct = [PREV_FWD[0], PREV_FWD[1], M("user", "grep result:\n<compressed>")]
|
|
out = overlay_cached_prefix(already_correct, CUR_ORIG, PREV_ORIG, PREV_FWD)
|
|
assert out == already_correct
|
|
|
|
|
|
def test_not_append_only_returns_unchanged():
|
|
# An early message changed → previous forwarded bytes may not correspond to
|
|
# the same positions; do NOT overlay (accept a possible bust over corruption).
|
|
changed = [M("user", "TOTALLY DIFFERENT"), PREV_ORIG[1], M("user", "x")]
|
|
out = overlay_cached_prefix(OPTIMIZED_BUGGY, changed, PREV_ORIG, PREV_FWD)
|
|
assert out == OPTIMIZED_BUGGY
|
|
|
|
|
|
def test_no_previous_state_returns_unchanged():
|
|
assert overlay_cached_prefix(OPTIMIZED_BUGGY, CUR_ORIG, None, None) == OPTIMIZED_BUGGY
|
|
assert overlay_cached_prefix(OPTIMIZED_BUGGY, CUR_ORIG, [], []) == OPTIMIZED_BUGGY
|
|
|
|
|
|
def test_forwarded_count_mismatch_returns_unchanged():
|
|
# Defensive: not exactly one forwarded message per original → bail.
|
|
assert (
|
|
overlay_cached_prefix(OPTIMIZED_BUGGY, CUR_ORIG, PREV_ORIG, PREV_FWD[:1]) == OPTIMIZED_BUGGY
|
|
)
|
|
|
|
|
|
def test_shorter_current_or_optimized_returns_unchanged():
|
|
assert overlay_cached_prefix([M("user", "x")], [M("user", "x")], PREV_ORIG, PREV_FWD) == [
|
|
M("user", "x")
|
|
]
|
|
|
|
|
|
def test_cache_hit_property_prefix_matches_last_forward():
|
|
# The invariant that guarantees a cache hit: forwarded[:n] this turn ==
|
|
# forwarded[:n] last turn (== what the provider cached).
|
|
out = overlay_cached_prefix(OPTIMIZED_BUGGY, CUR_ORIG, PREV_ORIG, PREV_FWD)
|
|
n = len(PREV_FWD)
|
|
assert out[:n] == PREV_FWD # exact byte-identical prefix → provider cache hit
|