fix(agent): recover primary before fast transport fallback - #69190
WallaceNodded wants to merge 3 commits into
Conversation
|
Reviewed against #69186 — this looks correct and complete. Root cause & fix. Splitting the old #22277 not regressed — verified. All three stale-kill sites ( One minor, non-blocking note: |
|
Spent some time verifying this at current main (0157180). The failure is real and this fixes it: One gap in the provenance invariant worth fixing before this merges: the two Codex watchdog kill sites in |
|
Addressed in 331c6c9. Both Codex watchdog kill paths now bump the stale streak before returning to the retry loop, preserving bounded eager fallback and stale-breaker provenance. Added assertions for both TTFB and stream-idle kills. Verified against the latest upstream/main state: 32 targeted fallback/watchdog/stale-breaker tests passed, and Ruff passed. |
|
Thanks for the focused recovery-path fix. The premise remains present on current main: The updated PR also addresses the watchdog provenance gap: current main's Codex TTFB and idle branches break at The approach preserves the bounded stale-stream behavior introduced by Automated hermes-sweeper review. |
…y-before-fast-fallback # Conflicts: # tests/agent/test_codex_ttfb_watchdog.py
|
Synced the branch with current upstream/main (v0.19.1) and resolved the test-file conflict without changing the fix scope. The effective PR diff remains limited to the recovery/fallback gate, Codex watchdog stale provenance, and regression coverage. Verification on the updated head: 172 targeted fallback/watchdog/stale-breaker/streaming tests passed, and Ruff passed for all PR-touched Python files. |
SummaryTwo PRs address #69186 by separating ordinary timeout-classified APIConnectionError failures from stale-stream failures so primary transport recovery remains reachable. #69190 also records stale provenance in both Codex watchdog paths and adds regression coverage, whereas #69419 changes only the fallback gate and uses a different stale-stream threshold. Related pull requests
Duplicates#69190 and #69419 substantially overlap on the primary-recovery change, but they are not exact duplicates: #69419 uses a one-retry stale-stream threshold, while #69190 retains the existing two-failure threshold and additionally covers Codex watchdog provenance and regression tests. Suggested consolidationKeep #69190 open with a salvage path focused on its recovery/fallback gate, both Codex watchdog stale-provenance increments, and the accompanying regression coverage, in line with the visible keep_open verdict. Keep #69419 closed as superseded by #69190 rather than labeling it an exact duplicate, because its contributor-noted one-retry stale-stream policy differs from #69190. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I69186(["issue #69186 (open)"])
P69190["PR #69190 (open)"]
P69190 -->|best fix| I69186
class I69186 open
class P69190 open
class P69190 best
class P69190 target
click I69186 "https://github.com/NousResearch/hermes-agent/issues/69186"
click P69190 "https://github.com/NousResearch/hermes-agent/pull/69190"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 2 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 16 kB of PR diffs, 12 kB of issue/PR text, 4 kB of discussion (6 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
What does this PR do?
Separates fast connection failures from stale-stream failures at the eager-fallback gate.
Ordinary
APIConnectionError/ timeout failures now complete the existing retry cycle, rebuild the primary client through_try_recover_primary_transport(), and receive one fresh primary cycle before fallback. Provider overload and stale-detector-derived timeouts retain the bounded eager fallback behavior introduced for #22277.This reuses existing retry, recovery, and fallback machinery. It adds no config key, environment variable, or provider-specific branch.
Related Issue
Fixes #69186
Related: #22277, #53911
Type of Change
Changes Made
agent/conversation_loop.pyoverloadedeligible for bounded eager fallback.tests/run_agent/test_fast_transport_recovery_before_fallback.pyWhy this shape?
#22277 correctly fixed a different failure mode: each stale-stream attempt can consume 180–300 seconds, so exhausting every retry cycle can leave a configured fallback idle for 15+ minutes. A normal SDK/socket connection error often returns in seconds and is exactly what the existing primary-client rebuild is designed to recover.
Both currently classify as
FailoverReason.timeout, so the eager gate loses the provenance and activates fallback on attempt two. The stale-stream streak already records that provenance; consulting it keeps #22277 intact without adding another retry mechanism.How to Test
Result:
281 passedon Python 3.11.15 / macOS Apple Silicon.ruff check agent/conversation_loop.py tests/run_agent/test_fast_transport_recovery_before_fallback.pyalso passes.The canonical full-suite runner was attempted. Before it was stopped, 7,200+ tests passed and three tests in
tests/agent/test_anthropic_adapter.py::TestRunOauthSetupTokenfailed because the mockedsubprocess.run().stdoutreachedjson.loads()as aMagicMock. The same3 failed, 175 passedresult reproduces on a cleanupstream/mainworktree at9ecacd6bf, so it is unrelated to this change.Checklist
Code
pytest tests/ -qand all tests pass (see documented upstream baseline failure above)Documentation & Housekeeping
cli-config.yaml.example: N/A; no config keys changedCONTRIBUTING.md/AGENTS.md: N/A; no architecture or workflow changedScreenshots / Logs
Redacted production sequence from an
openai-codexprimary:There was no stale-stream warning and no primary-recovery event before fallback.