fix(agent): manual /compress "nothing to compress" gate stays gateway-only

The shared core applied `has_content_to_compress(head) is False -> nothing_to_do`
on every surface, where origin/main only had it in the gateway handler. That
predicate only knows the local summarizer's window: on CLI/TUI/ACP,
`_compress_context(force=True)` still routes codex_app_server sessions to native
compaction before any local-compressor check, and `ContextCompressor.compress`
commits the phase-1 tool-result prune / blank-echo drop even when no summary
window exists -- so the gate wrongly skipped real work there. It is now an opt-in
`skip_without_window` that only the gateway passes, restoring each surface's
prior behavior.

Review follow-up on #109610.
This commit is contained in:
teknium1
2026-09-12 23:46:10 -07:00
committed by Teknium
parent e3281fe249
commit b5f072e2ce
3 changed files with 27 additions and 4 deletions
+7 -3
View File
@@ -68,7 +68,7 @@ def estimate_request_tokens(agent: Any, messages: Sequence[Dict[str, Any]]) -> i
def compress_now(
agent: Any, history: Sequence[Dict[str, Any]], request: CompressRequest, *,
system_message: Any = None, task_id: str = "default",
system_message: Any = None, task_id: str = "default", skip_without_window: bool = False,
) -> CompressResult:
"""Run one manual compression of ``history`` on ``agent`` and return the outcome; the caller installs
``after_messages`` (and re-anchors session ids) — history is never mutated here.
@@ -78,7 +78,11 @@ def compress_now(
discarded; otherwise the caller must call ``finalize_context_engine_compression_notification(agent,
committed=True)`` once its own history transaction commits (``committed=False`` on failure).
``system_message=None`` makes ``_compress_context`` rebuild the prompt; passing the cached prompt
duplicated the identity block (#15281)."""
duplicated the identity block (#15281). ``skip_without_window`` (gateway) answers ``nothing_to_do``
when the local compressor sees no summarizable middle; the in-process surfaces leave it off because
``_compress_context`` still does useful work there — codex_app_server native compaction, and the
phase-1 tool-result prune / blank-echo drop that ``ContextCompressor.compress`` commits even when no
summary window exists."""
from agent.conversation_compression import finalize_context_engine_compression_notification
from agent.manual_compression_feedback import summarize_manual_compression
from hermes_cli.partial_compress import (
@@ -97,7 +101,7 @@ def compress_now(
compressor = getattr(agent, "context_compressor", None)
has_content = getattr(compressor, "has_content_to_compress", None)
if callable(has_content) and has_content(head) is False:
if skip_without_window and callable(has_content) and has_content(head) is False:
return CompressResult("nothing_to_do", before, before, before_tokens, before_tokens, request)
try:
compressed, _ = agent._compress_context(
+1 -1
View File
@@ -550,7 +550,7 @@ class GatewaySessionCommandsMixin:
# Not a bare run_in_executor: the profile secret scope is a contextvar the default
# executor hop would drop, failing aux-client credential resolution closed.
result = await self._run_in_executor_with_context(
lambda: compress_now(tmp_agent, msgs, request, system_message=""))
lambda: compress_now(tmp_agent, msgs, request, system_message="", skip_without_window=True))
if result.status == "nothing_to_do":
return t("gateway.compress.nothing_to_do")
if result.status != "compressed":
@@ -87,6 +87,25 @@ def test_every_surface_honours_preview_without_compressing(surface, monkeypatch)
agent._compress_context.assert_not_called()
def test_windowless_gate_is_gateway_only_so_in_process_surfaces_still_reach_compress_context():
"""``has_content_to_compress`` only knows the local summary window; codex_app_server native compaction
and the phase-1 tool-result prune inside ``_compress_context`` do useful work without one, so CLI/TUI/
ACP (the default) must not short-circuit on it. The gateway keeps its historical early answer."""
agent, history = _agent(), _history()
agent.context_compressor = MagicMock()
agent.context_compressor.has_content_to_compress.return_value = False
agent._compress_context.return_value = (history[:-1], "") # e.g. a pruned tool result, no summary
default = compress_now(agent, history, parse_compress_args(""))
assert default.status == "compressed" and default.removed == 1
agent._compress_context.assert_called_once()
agent._compress_context.reset_mock()
gateway = compress_now(agent, history, parse_compress_args(""), system_message="", skip_without_window=True)
assert gateway.status == "nothing_to_do" and gateway.after_messages == history
agent._compress_context.assert_not_called()
def _coro(value):
async def _inner(*_a, **_k):
return value