2 Commits

Author SHA1 Message Date
kshitijk4poor
30ca0e107c fix(agent): quantize eviction by the fit window so heavy tool results hold the frontier
The floor cut-back (`step > total - floor` -> `step = total - floor`) fires whenever an
eight-carrier batch is wider than the fit window: tool results carrying several images,
or user uploads filling most of the ceiling. `total - floor` tracks the total, so the
retire count stopped being a step function and the frontier moved on every turn again —
the per-image prefix rewrite this policy exists to prevent.

Replay over 24 turns (prefix collapses; issue #113517 script):

  carrier shape              before  after
  1 image / tool_result          2      2
  2 images / tool_result         3      3
  3 images / tool_result        13      7
  4 images / tool_result        16     11
  15 uploads + 1 / tool_result  15     10

The quantum is now `min(batch, window - floor)` where `window` is how many newest
carriers fit. Each advance keeps at least `floor + 1` frames, so the cut-back clause is
gone; holding for `window - floor` turns per advance is the most the floor allows. The
one-image case (#113517's report) is unchanged: 21/29/37 -> 8/16/24 retired.

Regression test: test_frontier_holds_when_tool_results_carry_several_images — red on
the previous loop shape, green here.
2026-09-17 13:52:36 +05:30
kshitijk4poor
5dcfae44b4 refactor(agent): one image-eviction policy for both send-path passes
The compressor pass and the Anthropic wire pass each carried their own copy of the
limit/batch/floor constants (under different names) and of the retire-count loop.
Retuning one alone would make the wire pass re-evict on a second frontier after the
compressor pass — the exact prefix-rewrite class #113517 fixes.

- agent/image_eviction_policy.py: stdlib-only leaf holding the constants and
  outbound_image_retire_count(); the wire converter stays a leaf (no compaction import).
- evict_stale_outbound_tool_images loses `keep_newest`: no caller passed it, and its
  default was the COMPACTION window constant the comments say the send path must not use.
- _retire_stale_tool_result_images (compaction) back to its main body; behaviour was
  unchanged, so the rewrite was churn.
- One `_tool_result_parts` unwrap instead of three copies; one `_image_payload` walk
  per message instead of two.
- Wire pass counts blocks via `_block_type` (non-dict inner blocks no longer raise).

Tests import the constants from the policy module; the two "stops at the floor" tests
assert the invariant (newest floor survives, request under the limit) instead of the
exact frontier. test_user_uploads_are_not_evicted now builds a request that actually
trips eviction — at 6 blocks it never fired and proved nothing.
2026-09-17 13:52:36 +05:30