diff --git a/agent/chat_completion_helpers.py b/agent/chat_completion_helpers.py index cd6fbe50b7..ad2156422f 100644 --- a/agent/chat_completion_helpers.py +++ b/agent/chat_completion_helpers.py @@ -1098,6 +1098,13 @@ class _RequestClientRegistry: # timeout's run-budget cap is applied AFTER this floor (AIAgent._compute_non_stream_stale_timeout). HIGH_EFFORT_SILENCE_FLOOR_SECONDS = 300.0 +# First-progress budget for a lifecycle-only stream on an official-Codex large request: the +# stream opened but no substantive model event has arrived. Measured from the physical-attempt +# start (a reconnect restarts it; lifecycle frames do not), and applied regardless of reasoning +# effort. Equal to the high-effort floor today, but a separate knob so tuning one cannot silently +# retune the other. +CODEX_FIRST_PROGRESS_TIMEOUT_SECONDS = 300.0 + def _high_effort_silence_floor(agent) -> float: """``HIGH_EFFORT_SILENCE_FLOOR_SECONDS`` when the wire reasoning config is enabled at ``high`` or any @@ -1216,7 +1223,7 @@ def _resolve_nonstream_watchdogs(agent, api_kwargs: dict) -> _NonStreamWatchdogs idle_timeout=idle_timeout, idle_requires_progress=progress_gated, # A lifecycle frame proves transport liveness, not model progress. Bound that phase # from the physical-attempt start; events cannot restart the grace period. - progress_timeout=HIGH_EFFORT_SILENCE_FLOOR_SECONDS if progress_gated else 0.0) + progress_timeout=CODEX_FIRST_PROGRESS_TIMEOUT_SECONDS if progress_gated else 0.0) def _codex_silent_hang_hint(agent, api_kwargs: dict) -> Optional[str]: diff --git a/tests/agent/test_codex_ttfb_watchdog.py b/tests/agent/test_codex_ttfb_watchdog.py index 318db3ec3a..7947a13f2f 100644 --- a/tests/agent/test_codex_ttfb_watchdog.py +++ b/tests/agent/test_codex_ttfb_watchdog.py @@ -71,14 +71,18 @@ def _make_codex_agent( return agent -def _shorten_implicit_idle_watchdog(monkeypatch, helpers, timeout=2.0): - """Keep the resolver on its implicit branch while scaling time for tests.""" +def _shorten_implicit_idle_watchdog(monkeypatch, helpers, timeout=2.0, **overrides): + """Keep the resolver on its implicit branch while scaling time for tests. + + ``timeout`` shortens ``idle_timeout``; ``overrides`` set any other resolved field.""" monkeypatch.delenv("HERMES_CODEX_EVENT_STALE_TIMEOUT_SECONDS", raising=False) original = helpers._resolve_nonstream_watchdogs def resolve(agent, api_kwargs): watchdogs = original(agent, api_kwargs) watchdogs.idle_timeout = timeout + for field, value in overrides.items(): + setattr(watchdogs, field, value) return watchdogs monkeypatch.setattr(helpers, "_resolve_nonstream_watchdogs", resolve) @@ -343,15 +347,7 @@ def test_large_codex_lifecycle_only_stream_hits_attempt_progress_budget(tmp_path from agent import chat_completion_helpers as h agent = _make_codex_agent(tmp_path, monkeypatch) - original = h._resolve_nonstream_watchdogs - - def resolve(agent, kwargs): - wd = original(agent, kwargs) - wd.ttfb_timeout = 0.9 - wd.progress_timeout = 0.9 - return wd - - monkeypatch.setattr(h, "_resolve_nonstream_watchdogs", resolve) + _shorten_implicit_idle_watchdog(monkeypatch, h, ttfb_timeout=0.9, progress_timeout=0.9) closes = [] def stream_attempt():