diff --git a/agent/agent_init.py b/agent/agent_init.py index 36bea43f44b..32a06259441 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -1773,6 +1773,12 @@ def init_agent( if _selected_engine is not None: agent.context_compressor = _selected_engine + # External engines own compaction policy: the host compression + # threshold (including the Codex gpt-5.5 autoraise above) only + # configures the built-in ContextCompressor and never reaches the + # plugin, so the autoraise notice would announce a change that does + # not apply. Drop it. (#44439) + agent._compression_threshold_autoraised = None # Resolve context_length for plugin engines — mirrors switch_model() path from agent.model_metadata import get_model_context_length _plugin_ctx_len = get_model_context_length( @@ -2012,7 +2018,13 @@ def init_agent( if not agent.quiet_mode: if compression_enabled: - print(f"📊 Context limit: {agent.context_compressor.context_length:,} tokens (compress at {int(compression_threshold*100)}% = {agent.context_compressor.threshold_tokens:,})") + # Report the active engine's own threshold — for a plugin engine + # the host compression_threshold is not in effect, and mixing the + # two printed a percent that contradicted the token count. (#44439) + _active_threshold_pct = getattr( + agent.context_compressor, "threshold_percent", compression_threshold + ) + print(f"📊 Context limit: {agent.context_compressor.context_length:,} tokens (compress at {int(_active_threshold_pct*100)}% = {agent.context_compressor.threshold_tokens:,})") else: print(f"📊 Context limit: {agent.context_compressor.context_length:,} tokens (auto-compression disabled)") # Notice with the exact opt-back-out command. Printed inline at startup diff --git a/tests/run_agent/test_plugin_context_engine_init.py b/tests/run_agent/test_plugin_context_engine_init.py index 7285cb1f625..f4b29ef0093 100644 --- a/tests/run_agent/test_plugin_context_engine_init.py +++ b/tests/run_agent/test_plugin_context_engine_init.py @@ -139,3 +139,101 @@ def test_plugin_engine_update_model_args(): assert "model" in kw assert "provider" in kw assert "api_mode" in kw + + +def _codex_agent_kwargs(): + return dict( + model="gpt-5.5", + provider="openai-codex", + api_key="test-key-1234567890", + base_url="https://chatgpt.com/backend-api/codex", + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + + +def test_codex_gpt55_autoraise_suppressed_for_plugin_engine(): + """Codex gpt-5.5 autoraise must not fire when an external engine is active. + + Regression test for #44439 — the host compression threshold (including + the 0.85 autoraise) never reaches a plugin context engine, so the notice + announced a change that did not apply. + """ + engine = _StubEngine() + cfg = { + "context": {"engine": "stub"}, + "compression": {"enabled": True, "threshold": 0.75}, + "agent": {}, + } + + with ( + patch("hermes_cli.config.load_config", return_value=cfg), + patch("plugins.context_engine.load_context_engine", return_value=engine), + patch("agent.model_metadata.get_model_context_length", return_value=272_000), + patch("run_agent.get_tool_definitions", return_value=[]), + patch("run_agent.check_toolset_requirements", return_value={}), + patch("run_agent.OpenAI"), + ): + from run_agent import AIAgent + + agent = AIAgent(**_codex_agent_kwargs()) + + assert agent.context_compressor is engine + assert agent._compression_threshold_autoraised is None + assert agent._compression_warning is None + # The engine's own policy is untouched by the host threshold. + assert engine.threshold_percent == 0.75 + + +def test_codex_gpt55_autoraise_still_applies_to_builtin_compressor(): + """Stock built-in compressor keeps the 50% → 85% Codex gpt-5.5 autoraise.""" + cfg = { + "compression": {"enabled": True, "threshold": 0.50}, + "agent": {}, + } + + with ( + patch("hermes_cli.config.load_config", return_value=cfg), + patch("agent.context_compressor.get_model_context_length", return_value=272_000), + patch("run_agent.get_tool_definitions", return_value=[]), + patch("run_agent.check_toolset_requirements", return_value={}), + patch("run_agent.OpenAI"), + ): + from run_agent import AIAgent + + agent = AIAgent(**_codex_agent_kwargs()) + + assert agent._compression_threshold_autoraised == {"from": 0.50, "to": 0.85} + assert agent.context_compressor.threshold_percent == 0.85 + # Gateway parity: the notice is stashed for replay on turn 1. + assert agent._compression_warning and "85%" in agent._compression_warning + + +def test_codex_gpt55_autoraise_applies_when_plugin_engine_missing(): + """If the configured engine fails to load, the built-in compressor is + active and the autoraise (plus its notice) must still apply.""" + cfg = { + "context": {"engine": "no-such-engine"}, + "compression": {"enabled": True, "threshold": 0.50}, + "agent": {}, + } + + with ( + patch("hermes_cli.config.load_config", return_value=cfg), + patch( + "plugins.context_engine.load_context_engine", + side_effect=ValueError("not found"), + ), + patch("hermes_cli.plugins.get_plugin_context_engine", return_value=None), + patch("agent.context_compressor.get_model_context_length", return_value=272_000), + patch("run_agent.get_tool_definitions", return_value=[]), + patch("run_agent.check_toolset_requirements", return_value={}), + patch("run_agent.OpenAI"), + ): + from run_agent import AIAgent + + agent = AIAgent(**_codex_agent_kwargs()) + + assert agent._compression_threshold_autoraised == {"from": 0.50, "to": 0.85} + assert agent.context_compressor.threshold_percent == 0.85