mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(compression): keep Codex gpt-5.5 autoraise from lowering a higher threshold
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
This commit is contained in:
parent
0b6df665a9
commit
bdca94e749
3 changed files with 126 additions and 19 deletions
|
|
@ -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": <slug>, "from": <old>, "to": <new>}`` 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"}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue