From be834681dcb9c78b1a11982558fd0c8e1c7201d3 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sun, 27 Sep 2026 13:12:47 +0530 Subject: [PATCH] fix(context): measure pruned regions, bound arg redaction, drop dead counters Follow-ups to making tool-call args byte-exact: - _record_compression_regions measured canonical_messages slices while compress_start/compress_end are indices into the pruned copy that head/tail are assembled from; measure the pruned rows actually sent, as before. This also removes the only canonical slicing, so blank-echo classification drift between the two copies can no longer misalign anything. - _render_tool_call_for_summary redacted the full (now unbounded) args before cutting to 1200 chars; cut to head+4096 first. Output unchanged for args within that window. - pressure_hits always equalled demoted once arg truncation left; fold it. - Drop the fixture-only tautological assert in the guardrail test helper. - Reword stale compress()/compression_marker docstrings that still described canonical head/tail and compressor-written arg markers. --- agent/compression_marker.py | 10 ++++--- agent/context_compressor.py | 30 +++++++++---------- .../agent/test_tool_call_guardrail_runtime.py | 9 ++---- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/agent/compression_marker.py b/agent/compression_marker.py index 18b127850a..51b97cf79f 100644 --- a/agent/compression_marker.py +++ b/agent/compression_marker.py @@ -1,8 +1,10 @@ -"""The model-visible marker the context compressor leaves in pruned tool-call arguments. +"""The model-visible compression/elision marker and its matcher. -Dependency-free leaf shared by the producer (``agent.context_compressor``) and the -dispatch-boundary detector (``agent.tool_dispatch_helpers``), so the matcher is derived -from the template instead of re-typing its wording. +Tool-call arguments are no longer rewritten by the compressor, but legacy sessions may still +carry this marker inside replayed tool-call arguments. Dependency-free leaf shared by the +elision renderers (``agent.context_compressor``) and the dispatch-boundary detector +(``agent.tool_dispatch_helpers``), so the matcher is derived from the template instead of +re-typing its wording. """ from __future__ import annotations diff --git a/agent/context_compressor.py b/agent/context_compressor.py index a2b6cd370b..12e523ea94 100644 --- a/agent/context_compressor.py +++ b/agent/context_compressor.py @@ -3141,15 +3141,14 @@ class ContextCompressor(SummaryDispatchMixin, MicroCompactionMixin, ContextEngin def _protected_region_tokens() -> int: return sum(_estimate_msg_budget_tokens(result[i]) for i in range(start, len(result))) - demoted = pressure_hits = 0 + demoted = 0 def _shrink_at(i: int) -> None: - nonlocal demoted, pressure_hits + nonlocal demoted if i in spared: return if self._demote_tool_result_at(result, i, call_id_to_tool, min_prune_chars): demoted += 1 - pressure_hits += 1 if demote_end <= prune_boundary or _protected_region_tokens() <= soft_ceiling: return 0 @@ -3169,12 +3168,11 @@ class ContextCompressor(SummaryDispatchMixin, MicroCompactionMixin, ContextEngin and _protected_region_tokens() > soft_ceiling ) and self._demote_tool_result_at(result, last_tool_idx, call_id_to_tool, min_prune_chars): demoted += 1 - pressure_hits += 1 - if pressure_hits and not self.quiet_mode: + if demoted and not self.quiet_mode: logger.info( "Pre-compression pressure demotion: reclaimed protected-tail tool output (%d change(s); " "protected region now ~%s tokens, soft ceiling %s)", - pressure_hits, f"{_protected_region_tokens():,}", f"{soft_ceiling:,}", + demoted, f"{_protected_region_tokens():,}", f"{soft_ceiling:,}", ) return demoted @@ -3401,7 +3399,10 @@ class ContextCompressor(SummaryDispatchMixin, MicroCompactionMixin, ContextEngin fn = getattr(tc, "function", None) return f" {getattr(fn, 'name', '?') if fn else '?'}(...)" fn = tc.get("function", {}) - args = _redact_compaction_text(fn.get("arguments", "")) + raw = fn.get("arguments", "") or "" + # Args are byte-exact now, so they can be huge: cut before the (costly) redaction pass, keeping + # slack past the head so a secret straddling the cut still matches and length checks stay honest. + args = _redact_compaction_text(raw[:self._TOOL_ARGS_HEAD + 4096] if len(raw) > self._TOOL_ARGS_HEAD + 4096 else raw) if len(args) > self._TOOL_ARGS_MAX: args = args[:self._TOOL_ARGS_HEAD] + "..." return f" {fn.get('name', '?')}({args})" @@ -5385,8 +5386,8 @@ Write only the summary body. Do not include any preamble or prefix.""" self, messages: List[Dict[str, Any]], current_tokens: Optional[int] = None, focus_topic: Optional[str] = None, force: bool = False, memory_context: str = "", bypass_cooldown: bool = False, ) -> List[Dict[str, Any]]: - """Summarize the middle turns: use pruned tool results only as summary input, protect a canonical - head/tail, summarize, then clean orphaned tool pairs. ``force`` clears the failure cooldown and bypasses + """Summarize the middle turns: prune tool-result bodies (tool-call args untouched), protect head and a + token-budget tail from the pruned copy, summarize, then clean orphaned tool pairs. ``force`` clears the failure cooldown and bypasses the feasibility skip; ``bypass_cooldown`` runs the summary LLM without clearing the cooldown. Args: focus_topic: Optional focus string for guided compression. When provided, the summariser will @@ -5416,7 +5417,8 @@ Write only the summary body. Do not include any preamble or prefix.""" return messages display_tokens = current_tokens if current_tokens else self.last_prompt_tokens or estimate_messages_tokens_rough(messages) spare_pending_images = bool(self._spared_pending_tool_round(messages)) - # Head rows are carried from canonical history; pruning only feeds the summary and the tail. + # Unpruned copy for no-op/abort returns (history stays lossless) and finalize; head/tail are + # assembled and measured from the pruned copy (#61932). canonical_messages = self._drop_blank_echoes(messages) # Phase 1: Prune old tool results (cheap, no LLM call) messages, pruned_count = self._prune_old_tool_results( @@ -5430,9 +5432,7 @@ Write only the summary body. Do not include any preamble or prefix.""" compress_start, compress_end = self._compress_window(messages) if compress_start >= compress_end: self._record_compression_regions( - head_messages=canonical_messages[:compress_start], - middle_messages=[], - tail_messages=canonical_messages[compress_end:], + head_messages=messages[:compress_start], middle_messages=[], tail_messages=messages[compress_end:], ) self._structural_no_op_result( telemetry, "no_compressible_window", @@ -5446,9 +5446,7 @@ Write only the summary body. Do not include any preamble or prefix.""" scan = self._scan_window_handoffs(messages, compress_start, compress_end, turns_to_summarize) turns_to_summarize = scan.turns_to_summarize self._record_compression_regions( - head_messages=canonical_messages[:compress_start], - middle_messages=turns_to_summarize, - tail_messages=canonical_messages[compress_end:], + head_messages=messages[:compress_start], middle_messages=turns_to_summarize, tail_messages=messages[compress_end:], ) telemetry["chunk_count"] = 1 if turns_to_summarize else 0 if not turns_to_summarize: diff --git a/tests/agent/test_tool_call_guardrail_runtime.py b/tests/agent/test_tool_call_guardrail_runtime.py index 0df8956a45..85b3d34841 100644 --- a/tests/agent/test_tool_call_guardrail_runtime.py +++ b/tests/agent/test_tool_call_guardrail_runtime.py @@ -372,19 +372,14 @@ def test_plugin_pre_tool_block_wins_without_counting_as_toolguard_block(): def _compressed_args(field: str) -> dict: """Generate a legacy compression marker for already-contaminated session coverage.""" - from agent.compression_marker import ( - _COMPRESSION_MARKER_PREFIX, - _COMPRESSION_MARKER_TEMPLATE, - ) + from agent.compression_marker import _COMPRESSION_MARKER_TEMPLATE original = "z" * 2000 marker = _COMPRESSION_MARKER_TEMPLATE.format( omitted=len(original) - 200, total=len(original), ) - parsed = {field: original[:200] + marker} - assert _COMPRESSION_MARKER_PREFIX in parsed[field] - return parsed + return {field: original[:200] + marker} def test_context_pruned_effectful_call_blocks_before_dispatch():