fix(compression): autoraise gpt-5.3-codex-spark threshold to 70% (#48621)

gpt-5.3-codex-spark has a native 128K context window but the default
50% compaction trigger fires at ~64K, wasting half the usable window
before the session has accumulated enough turns to summarize
meaningfully.  This raises the trigger to 70% (~90K) on the Codex OAuth
route only, leaving ~38K headroom for the summary and continued
conversation before the 128K hard limit.

The override is not gated by allow_codex_gpt55_autoraise because 128K
is the model's native window (unlike gpt-5.5's artificial 272K Codex
cap).  Non-Codex routes are unaffected.

Also adds a boundary regression test verifying the short-session
scenario from the issue always yields a non-empty compressible window
(no silent context wipe).
This commit is contained in:
Tranquil-Flow 2026-06-19 00:13:12 +02:00 committed by Teknium
parent 948993cd62
commit 0b6df665a9
3 changed files with 157 additions and 0 deletions

View file

@ -325,6 +325,15 @@ def _is_arcee_trinity_thinking(model: Optional[str]) -> bool:
# gpt-5.5 sessions use the window they actually have.
_CODEX_GPT54_GPT55_COMPACTION_THRESHOLD = 0.85
# gpt-5.3-codex-spark is Codex-OAuth-only (ChatGPT Pro entitlement) with a
# native 128K context window. The default 50% compaction trigger fires at
# ~64K — wasting half the usable window, often before the session has enough
# turns to summarize meaningfully. We raise the trigger to 70% (~90K) so
# spark sessions use more of the window before summarization, while still
# leaving ~38K headroom for the summary and continued conversation before
# the 128K hard limit.
_CODEX_SPARK_COMPACTION_THRESHOLD = 0.70
def _is_codex_gpt54_or_gpt55(model: Optional[str], provider: Optional[str] = None) -> bool:
"""True for gpt-5.4 / gpt-5.5 on the ChatGPT Codex OAuth backend.
@ -350,6 +359,21 @@ def _is_codex_gpt54_or_gpt55(model: Optional[str], provider: Optional[str] = Non
)
def _is_codex_spark(model: Optional[str], provider: Optional[str] = None) -> bool:
"""True for ``gpt-5.3-codex-spark`` on the ChatGPT Codex OAuth backend.
The model is Codex-OAuth-only (ChatGPT Pro entitlement) with a native
128K context window. Only the Codex OAuth route (provider
``openai-codex``) is matched the slug is not available on other
routes.
"""
prov = (provider or "").strip().lower()
if prov != "openai-codex":
return False
bare = (model or "").strip().lower().rsplit("/", 1)[-1]
return bare == "gpt-5.3-codex-spark"
def _fixed_temperature_for_model(
model: Optional[str],
base_url: Optional[str] = None,
@ -391,6 +415,9 @@ def _compression_threshold_for_model(
~136K. Gated by ``allow_codex_gpt55_autoraise`` (historical config-key
name kept for backward compatibility) so the user can opt back down to
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.
Returns a float in (0, 1] to override the global ``compression.threshold``
config value, or ``None`` to leave the user's config value unchanged.
@ -399,6 +426,8 @@ 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):
return _CODEX_SPARK_COMPACTION_THRESHOLD
return None
# Default auxiliary models for direct API-key providers (cheap/fast for side tasks)

View file

@ -18,6 +18,7 @@ from agent.auxiliary_client import (
_fixed_temperature_for_model,
_is_arcee_trinity_thinking,
_is_codex_gpt54_or_gpt55,
_is_codex_spark,
)
@ -175,3 +176,75 @@ def test_compression_threshold_opt_out_does_not_disable_trinity() -> None:
)
== 0.75
)
# ---------------------------------------------------------------------------
# Codex gpt-5.3-codex-spark compaction-threshold autoraise
#
# gpt-5.3-codex-spark is Codex-OAuth-only (ChatGPT Pro entitlement) with a
# native 128K context window. The default 50% compaction trigger would fire
# at ~64K — wasting half the usable window, often before the session has
# accumulated enough turns to summarize meaningfully. This route raises the
# trigger to 70% (~90K) to preserve more raw context while leaving ~38K
# headroom before the 128K hard limit.
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"model",
[
"gpt-5.3-codex-spark",
"openai/gpt-5.3-codex-spark", # aggregator-prefixed (still on the codex route)
"GPT-5.3-CODEX-SPARK", # case-insensitive
" gpt-5.3-codex-spark ", # whitespace tolerant
],
)
def test_is_codex_spark_matches_on_codex_provider(model: str) -> None:
assert _is_codex_spark(model, "openai-codex") is True
@pytest.mark.parametrize(
"provider",
["openrouter", "openai", "copilot", "openai-api", "", None],
)
def test_is_codex_spark_rejects_non_codex_providers(provider) -> None:
# spark on any non-Codex route is not a real slug — no override.
assert _is_codex_spark("gpt-5.3-codex-spark", provider) is False
@pytest.mark.parametrize(
"model",
[
"gpt-5.5", # different family
"gpt-5.3-codex", # sibling, not spark
"gpt-5.3", # bare 5.3, not spark
"gpt-5.3-codex-spark-mini", # hypothetical variant — not matched yet
"", None,
],
)
def test_is_codex_spark_rejects_non_spark_models(model) -> None:
assert _is_codex_spark(model, "openai-codex") is False
def test_compression_threshold_for_codex_spark() -> None:
assert _compression_threshold_for_model("gpt-5.3-codex-spark", "openai-codex") == 0.70
assert _compression_threshold_for_model("openai/gpt-5.3-codex-spark", "openai-codex") == 0.70
def test_compression_threshold_codex_spark_other_routes_unaffected() -> None:
# Same slug, different route → no override (keep the user's config value).
assert _compression_threshold_for_model("gpt-5.3-codex-spark", "openrouter") is None
assert _compression_threshold_for_model("gpt-5.3-codex-spark", "openai") is None
assert _compression_threshold_for_model("gpt-5.3-codex-spark") is None # no provider
def test_compression_threshold_codex_spark_not_gated_by_gpt55_optout() -> None:
# The spark autoraise is independent of the gpt-5.5 opt-out flag — 128K is
# the model's native window, so 70% is unambiguously correct regardless of
# whether the user opted out of the (artificial-cap) gpt-5.5 autoraise.
assert (
_compression_threshold_for_model(
"gpt-5.3-codex-spark", "openai-codex", allow_codex_gpt55_autoraise=False
)
== 0.70
)

View file

@ -283,3 +283,58 @@ class TestCooldownGuard:
comp.last_prompt_tokens = 65_000
assert comp._summary_failure_cooldown_until == 0.0
assert comp.should_compress(65_000)
# ---------------------------------------------------------------------------
# Test: #48621 — gpt-5.3-codex-spark short-session boundary
#
# Issue #48621 Bug 2 claims that a short high-token session (15-20 messages,
# ~90k tokens on a 128k model with protect_last_n=20) hits
# compress_start >= compress_end, causing a silent context wipe. The
# raw-budget fallback added in the #40803 fix already mitigates this: the
# boundary logic always exposes a minimal compressible window. This test
# locks that behavior in for the exact #48621 parameters.
# ---------------------------------------------------------------------------
class TestCodexSparkShortSessionBoundary:
"""Verify that gpt-5.3-codex-spark's short-session scenario always yields
a non-empty compressible window (no silent wipe)."""
def test_short_high_token_session_has_compressible_window(self):
"""16 messages with large tool outputs on a 128k model must leave
a compressible middle (compress_start < compress_end)."""
comp = _make_compressor(
model="gpt-5.3-codex-spark",
threshold_percent=0.70,
protect_first_n=3,
protect_last_n=20,
config_context_length=128000,
)
# Build system + 3 head pairs + 3 tool groups (large outputs) + tail pair
big_tool = "x" * 20000 # ~5k tokens each
messages = [{"role": "system", "content": "You are a helpful agent."}]
for i in range(3):
messages.append({"role": "user", "content": f"Question {i}"})
messages.append({"role": "assistant", "content": f"Answer {i}"})
for i in range(3):
messages.append({"role": "user", "content": f"Run command {i}"})
messages.append({
"role": "assistant", "content": "",
"tool_calls": [{
"id": f"tc{i}", "type": "function",
"function": {"name": "terminal", "arguments": "{}"},
}],
})
messages.append({"role": "tool", "tool_call_id": f"tc{i}", "content": big_tool})
messages.append({"role": "user", "content": "Final question"})
messages.append({"role": "assistant", "content": "Final answer"})
head = comp._protect_head_size(messages)
compress_start = comp._align_boundary_forward(messages, head)
compress_end = comp._find_tail_cut_by_tokens(messages, compress_start)
assert compress_start < compress_end, (
f"No compressible window: start={compress_start}, end={compress_end}. "
f"This would cause the silent context wipe described in #48621."
)
assert comp.has_content_to_compress(messages) is True