From bdca94e7491c52c53672a82a1fe28ecd23336211 Mon Sep 17 00:00:00 2001 From: sasquatch9818 <290858493+sasquatch9818@users.noreply.github.com> Date: Sun, 7 Jun 2026 23:29:27 +0300 Subject: [PATCH] fix(compression): keep Codex gpt-5.5 autoraise from lowering a higher threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Codex gpt-5.5 compaction autoraise (#40957) overrode the effective threshold unconditionally. If a user had set compression.threshold above 0.85, agent_init dropped them down to 0.85. That wastes usable window and contradicts the feature's whole point: use more of the context, not less. It happened silently too, since the one-time notice is suppressed when the override doesn't raise. The override is an autoraise. It must only raise. Pulled the apply logic into a small pure helper that clamps the Codex case to never lower a higher-or-equal user threshold, and emits the notice only when it actually fires. Other overrides (Arcee Trinity) keep their existing unconditional behavior. Fixes the Codex gpt-5.5 compaction autoraise lowering a user's higher configured threshold. A user on the Codex OAuth route with compression.threshold > 0.85 was silently clamped to 0.85, compacting earlier than they asked and using less of the 272K window the feature was meant to unlock. The autoraise now only ever raises. N/A - [x] 🐛 Bug fix (non-breaking change that fixes an issue) - [ ] ✨ New feature (non-breaking change that adds functionality) - [ ] 🔒 Security fix - [ ] 📝 Documentation update - [ ] ✅ Tests (adding or improving test coverage) - [ ] ♻️ Refactor (no behavior change) - [ ] 🎯 New skill (bundled or hub) - `agent/agent_init.py`: added `_resolve_compression_threshold()`, a pure helper that combines the global threshold with a per-model override. The Codex gpt-5.5 autoraise never lowers a higher-or-equal user threshold; the notice is returned only when it actually raises. Rewired `init_agent` to call it, replacing the unconditional `compression_threshold = _model_cthresh`. - `tests/agent/test_arcee_trinity_overrides.py`: added 5 cases for the helper — raise from default, never-lower regression, equal-is-noop, no-override passthrough, and non-codex (Trinity) unconditional apply. 1. Set `compression.threshold: 0.90` and run gpt-5.5 on provider `openai-codex`. 2. Before: effective threshold drops to 0.85, no notice. After: stays 0.90. 3. Run `scripts/run_tests.sh tests/agent/test_arcee_trinity_overrides.py`. Stash `agent/agent_init.py` and the new cases fail; restore and they pass. - [x] I've read the [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md) - [x] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) (`fix(scope):`, `feat(scope):`, etc.) - [x] I searched for [existing PRs](https://github.com/NousResearch/hermes-agent/pulls) to make sure this isn't a duplicate - [x] My PR contains **only** changes related to this fix/feature (no unrelated commits) - [x] I've run `pytest tests/ -q` and all tests pass - [x] I've added tests for my changes (required for bug fixes, strongly encouraged for features) - [x] I've tested on my platform: macOS 15 (Darwin 25.5) - [x] I've updated relevant documentation (README, `docs/`, docstrings) — or N/A - [x] I've updated `cli-config.yaml.example` if I added/changed config keys — or N/A - [x] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — or N/A - [x] I've considered cross-platform impact (Windows, macOS) per the [compatibility guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#cross-platform-compatibility) — or N/A - [x] I've updated tool descriptions/schemas if I changed tool behavior — or N/A --- agent/agent_init.py | 73 ++++++++++++++++----- agent/auxiliary_client.py | 6 +- tests/agent/test_arcee_trinity_overrides.py | 66 +++++++++++++++++++ 3 files changed, 126 insertions(+), 19 deletions(-) diff --git a/agent/agent_init.py b/agent/agent_init.py index 188060bd7f2..107e78cd511 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -77,16 +77,54 @@ def _build_codex_gpt5_autoraise_notice(autoraise: Dict[str, float]) -> str: include the exact opt-back-out command. """ model = str(autoraise.get("model") or "gpt-5.4/5.5").strip().lower().rsplit("/", 1)[-1] + # gpt-5.3-codex-spark has a native 128K window; the gpt-5.4/5.5 family is + # capped at 272K by the Codex OAuth backend. + cap = "128K" if model.startswith("gpt-5.3-codex-spark") else "272K" from_pct = int(round(autoraise["from"] * 100)) to_pct = int(round(autoraise["to"] * 100)) return ( - f"ℹ Codex {model} caps context at 272K, so auto-compaction was raised " + f"ℹ Codex {model} caps context at {cap}, so auto-compaction was raised " f"to {to_pct}% (from {from_pct}%) to use more of the window before " f"summarizing.\n" f" Opt back out: hermes config set compression.codex_gpt55_autoraise false" ) +def _resolve_compression_threshold( + global_threshold: float, + model_cthresh: Optional[float], + *, + model: Optional[str] = None, + is_codex_autoraise: bool, +) -> tuple[float, Optional[Dict[str, Any]]]: + """Combine the user's global compaction threshold with a per-model override. + + Returns ``(effective_threshold, autoraise_notice)``. ``autoraise_notice`` is + ``{"model": , "from": , "to": }`` only when a Codex + autoraise (gpt-5.4/5.5 272K family or gpt-5.3-codex-spark) actually raises + the threshold, otherwise ``None``. + + The Codex overrides are *autoraises*: they must never LOWER a higher + user-configured threshold. A user who already set ``compression.threshold`` + above the raised value deliberately keeps more raw context, and silently + dropping them would both waste usable window and contradict the feature's + purpose (use more of the window). Other overrides (e.g. Arcee Trinity) + keep their existing unconditional behaviour. + """ + if model_cthresh is None: + return global_threshold, None + if is_codex_autoraise: + if model_cthresh <= global_threshold + 1e-9: + # Autoraise never lowers; keep the user's higher/equal threshold. + return global_threshold, None + return model_cthresh, { + "model": model, + "from": global_threshold, + "to": model_cthresh, + } + return model_cthresh, None + + def _normalized_custom_base_url(value: Any) -> str: if not isinstance(value, str): return "" @@ -1429,28 +1467,29 @@ def init_agent( from agent.auxiliary_client import ( _compression_threshold_for_model as _cthresh_fn, _is_codex_gpt54_or_gpt55 as _is_codex_gpt54_or_gpt55_fn, + _is_codex_spark as _is_codex_spark_fn, ) _model_cthresh = _cthresh_fn( agent.model, agent.provider, allow_codex_gpt55_autoraise=_codex_gpt55_autoraise, ) - if _model_cthresh is not None: - _prev_threshold = compression_threshold - compression_threshold = _model_cthresh - # Notify only for the Codex gpt-5.4 / gpt-5.5 autoraise (the Arcee - # Trinity override is a long-standing silent default). Skip the - # notice when the user's global threshold already meets/exceeds the - # raised value, since nothing actually changed for them. - if ( - _is_codex_gpt54_or_gpt55_fn(agent.model, agent.provider) - and _model_cthresh > _prev_threshold + 1e-9 - ): - agent._compression_threshold_autoraised = { - "model": agent.model, - "from": _prev_threshold, - "to": _model_cthresh, - } + # The Codex autoraises (gpt-5.4/5.5 272K family and gpt-5.3-codex-spark) + # apply only when they RAISE (never lower a user's higher global + # threshold). The notice is populated only when it actually fires, and + # carries the model slug so the banner names the right family. Arcee + # Trinity keeps its long-standing unconditional behaviour. + compression_threshold, agent._compression_threshold_autoraised = ( + _resolve_compression_threshold( + compression_threshold, + _model_cthresh, + model=agent.model, + is_codex_autoraise=( + _is_codex_gpt54_or_gpt55_fn(agent.model, agent.provider) + or _is_codex_spark_fn(agent.model, agent.provider) + ), + ) + ) except Exception: pass compression_enabled = str(_compression_cfg.get("enabled", True)).lower() in {"true", "1", "yes"} diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 1ed366d2439..2a1b5b085c2 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -417,7 +417,9 @@ def _compression_threshold_for_model( the global default (the caller passes the config flag through here). - gpt-5.3-codex-spark on the Codex OAuth route → 0.70, because the model has a native 128K window and the default 50% trigger would compact at - ~64K — wasting half the usable context. Gated by the same flag. + ~64K — wasting half the usable context. Not gated by the gpt-5.5 + opt-out flag: 128K is the model's native window, so the raise is + unambiguously correct. Returns a float in (0, 1] to override the global ``compression.threshold`` config value, or ``None`` to leave the user's config value unchanged. @@ -426,7 +428,7 @@ def _compression_threshold_for_model( return 0.75 if allow_codex_gpt55_autoraise and _is_codex_gpt54_or_gpt55(model, provider): return _CODEX_GPT54_GPT55_COMPACTION_THRESHOLD - if allow_codex_gpt55_autoraise and _is_codex_spark(model, provider): + if _is_codex_spark(model, provider): return _CODEX_SPARK_COMPACTION_THRESHOLD return None diff --git a/tests/agent/test_arcee_trinity_overrides.py b/tests/agent/test_arcee_trinity_overrides.py index edb1f7250b5..96a8fe9ff04 100644 --- a/tests/agent/test_arcee_trinity_overrides.py +++ b/tests/agent/test_arcee_trinity_overrides.py @@ -13,6 +13,7 @@ from __future__ import annotations import pytest +from agent.agent_init import _resolve_compression_threshold from agent.auxiliary_client import ( _compression_threshold_for_model, _fixed_temperature_for_model, @@ -248,3 +249,68 @@ def test_compression_threshold_codex_spark_not_gated_by_gpt55_optout() -> None: ) == 0.70 ) + + +# ── _resolve_compression_threshold (init_agent application logic) ──────────── +# +# The Codex overrides are *autoraises*: they raise the trigger (0.85 for the +# gpt-5.4/5.5 272K family, 0.70 for spark) but must never LOWER a higher +# user-configured global threshold. + + +def test_resolve_codex_autoraise_raises_from_default() -> None: + # Default 0.50 global → raised to 0.85, notice emitted. + effective, notice = _resolve_compression_threshold( + 0.50, 0.85, model="gpt-5.5", is_codex_autoraise=True + ) + assert effective == 0.85 + assert notice == {"model": "gpt-5.5", "from": 0.50, "to": 0.85} + + +def test_resolve_codex_autoraise_never_lowers_higher_threshold() -> None: + # Regression: a user who set compression.threshold above 0.85 must keep it. + # The autoraise previously clobbered it down to 0.85 (and silently, since + # the notice was suppressed when nothing "raised"). + effective, notice = _resolve_compression_threshold( + 0.90, 0.85, model="gpt-5.5", is_codex_autoraise=True + ) + assert effective == 0.90 + assert notice is None + + +def test_resolve_codex_spark_autoraise_never_lowers_higher_threshold() -> None: + # Same never-lower contract for the spark autoraise (0.70). + effective, notice = _resolve_compression_threshold( + 0.80, 0.70, model="gpt-5.3-codex-spark", is_codex_autoraise=True + ) + assert effective == 0.80 + assert notice is None + + +def test_resolve_codex_autoraise_equal_threshold_is_noop() -> None: + # User already at exactly the raised value: keep it, no notice. + effective, notice = _resolve_compression_threshold( + 0.85, 0.85, model="gpt-5.5", is_codex_autoraise=True + ) + assert effective == 0.85 + assert notice is None + + +def test_resolve_no_override_keeps_global() -> None: + # No per-model override (model_cthresh is None) → global threshold, no notice. + effective, notice = _resolve_compression_threshold( + 0.50, None, is_codex_autoraise=False + ) + assert effective == 0.50 + assert notice is None + + +def test_resolve_non_codex_override_applies_unconditionally() -> None: + # Arcee Trinity (0.75) keeps its long-standing unconditional behaviour: it + # applies even when it lowers the user's global value, and never emits the + # codex autoraise notice. + effective, notice = _resolve_compression_threshold( + 0.90, 0.75, is_codex_autoraise=False + ) + assert effective == 0.75 + assert notice is None