Repository navigation
fix(omp): chain Sol to direct DeepSeek Flash on provider failure - #154
Merged
Merged
Conversation
The session overlay emptied the Sol fallback chain, so when both Codex accounts hit their weekly limit omp had no credential to rotate to and no chain to walk, and it stopped at its wait cap (retry.maxDelayMs) instead. Chain openai-codex/gpt-6.1-sol to deepseek/deepseek-v4-flash:high and set retry.fallbackOn to usage-limit, so only quota-class errors move a session and a provider error burst still stops on Sol. fallbackRevertPolicy stays cooldown-expiry. The live fallback-chain guard now asserts the move to Flash and fails on the old overlay.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Both Codex accounts reached their weekly limit on 2026-10-16 at about 21:50 CDT and stay out until about 2026-10-22. Fleet lanes that ran on omp with the model openai-codex/gpt-6.1-sol stopped with: "You have hit your ChatGPT usage limit" and "Retry failed after 1 attempts: Provider requested 444099752ms wait, exceeds retry.maxDelayMs (300000ms)". One lane stayed dead for about an hour until the supervisor noticed. The captain's standing rules: never hold work for quota; when a quota is out, switch at once to the dispatch rule's named stand-in; the approved stand-in is direct DeepSeek Flash (omp provider deepseek, model deepseek/deepseek-v4-flash, never through OpenRouter). Measured cause (read-only investigation, report at ~/.claude/jobs/eb3cf121/tmp/omp-fallback-report.md): the tracked overlay .omp/fm-session-overlay.yml (lines 30-40, added by PR #89) sets retry.fallbackChains for openai-codex/gpt-6.1-sol to an empty list on purpose, so with both accounts out and no credential to rotate to, omp had no fallback candidate and the 444099752 ms wait hit the 300000 ms cap. omp 18.11.0 tries credential rotation first, then walks the chain, and only then applies the cap; it watches every --config overlay file and reads the chains fresh at each failure, so an overlay edit reaches live sessions without a restart. The global ~/.omp/agent/config.yml chain (OpenRouter mimo then haiku) is approved and must not change.
What Changed
.omp/fm-session-overlay.ymlnow setsretry.fallbackChainsforopenai-codex/gpt-6.1-soltodeepseek/deepseek-v4-flash:highinstead of an empty list. A Sol session whose provider fails, including a usage window that is out, now moves to direct DeepSeek Flash instead of stopping atretry.maxDelayMs. The chain applies on any eligible provider error, not only a quota-out.retry.fallbackRevertPolicy: cooldown-expiryreturns the session to Sol once the suppression window ends. The global chain in~/.omp/agent/config.ymlis untouched.tests/fm-omp-fallback-chain-live-e2e.test.shreplaces the "empty Sol chain stops the run" check with two checks against the installed omp: one scriptedusage_limit_reachedanswer with a one-hour retry-after, and one 500 answer. Each requires a request to reachdeepseek-v4-flash, none to the global chain'sdeepseek-v4-pro, a successful run, and a live-model record that follows the session to the stand-in. The scripted provider gainsusageanderrorbehaviors, and the guard header now cites omp 18.11.0.docs/configuration.mddescribes the overlay chain as a run-time exception to dispatch-time strongest-class selection (directdeepseekonly, never OpenRouter) and namesstate/<id>.omp-fallback.ymlas the per-task overlay.docs/verification/runtime-backends.mdrecords the omp 18.11.0 run,bin/fm-spawn.sh,bin/fm-crew-state.sh, and the omp harness reference are reworded to drop the "stops rather than downgrades" wording.Risk Assessment
✅ Low: The change is one YAML value (Sol chained to direct DeepSeek Flash) that matches the investigation report, the round-1 fallbackOn removal is complete (no fallbackOn remains in the tree), and the live guard would fail on the old empty chain; the only issue is one doc sentence that claims a lab result the guard does not run.
Testing
I ran the live omp fallback-chain guard against the real installed omp 18.11.0 with local fake providers. No model tokens were spent. All 6 checks passed on the change. The same guard fails on the old empty-chain overlay, so it does catch the incident. I also drove incident-size waits, per-task worker overlays, a Flash outage, and a real chat-completions Flash answer through real omp. All passed. The changes in bin, docs and skill files are comments and docs only. I saw no problems. Scratch files are removed and the worktree is clean. This change has no screenshot surface, so there are CLI transcripts only.
fallbackOntext remains anywhereEvidence: Live e2e on the new overlay (6 ok)
Source: Live e2e on the new overlay (6 ok)
Evidence: Live e2e on the old empty-chain overlay (fails as the incident did)
Source: Live e2e on the old empty-chain overlay (fails as the incident did)
Evidence: Incident-size wait and worker-overlay scenarios
Source: Incident-size wait and worker-overlay scenarios
Evidence: Flash outage guard and real Flash answer
Source: Flash outage guard and real Flash answer
Evidence: fm-spawn --help excerpt with the new header
Source: fm-spawn --help excerpt with the new header
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
.omp/fm-session-overlay.yml:43- Unrequired component:retry.fallbackOn: usage-limit. The intent asks for one thing: chainopenai-codex/gpt-6.1-solto directdeepseek/deepseek-v4-flashso a quota-out Sol lane switches at once (the investigation report's 'Minimal change' is only thefallbackChainsvalue). It does not ask to restrict fallback to quota-class errors. omp readsfallbackOnfor every chain in the session (omp 18.11.0:oB(settings.fallbackOn, isUsageLimit)gates bothisHardErrorFallbackEligibleand the retry-handler chain walk), and nothing later in the--configstack overrides it. Concrete effects beyond the intent: (1) The intent says the approved global chain (OpenRouter mimo then haiku) must not change; under the overlay it now fires only on a quota-class error, where before it fired on any eligible error. (2) Every worker lane gets the session overlay first, then.omp/fm-worker-overlay.yml, then the per-taskstate/<id>.omp-fallback.yml(bin/fm-spawn.sh:6091-6097, 6565), and none of them setsfallbackOn. So a worker on Sol with a declared dispatch-matrix stand-in ([deepseek/deepseek-v4-flash:high]) used to move to it on a 5xx or overload burst, and now stops with the provider's error and holds work. The docs for that per-task chain (.agents/skills/harness-adapters/references/harness/omp.md:42,docs/configuration.md~1686) and the overlay comment name only the global chain, not the worker chains. Smallest honest remedy: remove thefallbackOnline and its supporting text, which leaves the plain chain from the report. If the author wants the earlier PR fix(bin): stop omp Sol sessions falling to weaker models and surface model drift #89 protection against a weak-model strand after an error burst, that is a product decision and needs the captain's approval. The same sites must move together: .omp/fm-session-overlay.yml:36-42 (comment), bin/fm-spawn.sh:313-318 (header text), docs/verification/runtime-backends.md:1747-1750 (bullet; its 402 claim and its 'same chain without the key moved the session' claim are not exercised by the guard, which has no 402 case and no control without the key), tests/fm-omp-fallback-chain-live-e2e.test.sh:13 and :20-22 (header), :96-97 (errorserver behavior), :230-246 (500 scenario), :249-250 (serve weak usage 3600for the other-model case).🔧 Fix applied.
1 info still open:
docs/verification/runtime-backends.md:1744- Unexercised lab claim in a "Verified on 2026-10-16 ... omp 18.11.0" entry. docs/verification/runtime-backends.md:1744 says "An empty Sol chain stopped the same run at the wait cap with the provider's usage-limit error". The guard has no empty-chain run. Its six checks are: control with no overlay and alimitserver, overlay withusage, overlay witherror, the other-model case, the return case, and the no-overlaynevercontrol. So the sentence reads as a lab result that no test reproduces. This is the same class the round-1 decision asked to drop or label as stated, not tested, and the round-1 fix left a new instance of it behind. Smallest fix: reword the sentence to cite the 2026-10-16 incident and the investigation report as the source, or label it as not exercised by the guard. Adding an empty-chain control to the guard would extend the change, so it is not needed. Other sites checked: the .omp/fm-session-overlay.yml:32-34 comment and the tests/fm-omp-fallback-chain-live-e2e.test.sh:9-11 header describe the incident, not a lab result, so they are fine.bin/fm-spawn.sh:6075- A worker on Sol whose dispatch rule names no eligible native stand-in still stops on a quota-out. Its per-task overlay,state/<id>.omp-fallback.yml, loads after the tracked overlay and sets the Sol chain to[]. This is the documented design (docs/configuration.md says the chain is empty when the rule names no stand-in), and it matches the recorded decision. The new tracked chain covers every launch with no per-task Sol entry. A Sol worker whose rule names no stand-in is covered only if the operator adds direct DeepSeek Flash to that dispatch rule.fallbackOntext remains anywhereFM_OMP_FALLBACK_LIVE=1 tests/fm-omp-fallback-chain-live-e2e.test.shon the change: all 6 checks pass with real omp 18.11.0 and fake local providers (rc=0)The same live test with the old base-commit overlay (empty Sol chain) put back for one run: fails at the usage-window check, then the worktree was restored (git statusclean)A scratch driver with an incident-size wait (retry-after 444100 s, wait cap 300000 ms): new overlay moves to Flash; old overlay stops with the usage-limit errorA scratch driver with a per-task worker overlay built by the same jq program as bin/fm-spawn.sh: Sol with Flash stand-in moves to Flash; Sol with no stand-in stopsA scratch driver where Flash is also out: the session stops at Flash and never reaches the global chain's Pro or the OpenRouter-style equal routeA scratch driver with a real chat-completions Flash server: omp prints the Flash answer text and exits 0bin/fm-spawn.sh --helpshows the new header textgrep -rn fallbackOn .finds no referencesdocs/configuration.md:1692- Docs now disagree about downgrades. The tracked overlay moves every Sol session to DeepSeek Flash on any eligible provider error. This is the new text at docs/configuration.md:1692. Other docs still say strongest-class work stops and reports instead of downgrading: docs/configuration.md:1674-1675, .agents/skills/quota-array-dispatch/SKILL.md:65 and :116, and AGENTS.md:105. The overlay behavior matches the captain's rule to switch to direct DeepSeek Flash at once, so I left all of it as written. AGENTS.md is agent memory, and I may only fix facts that are wrong there. Captain decision needed: either say in the dispatch docs that the overlay's Sol-to-Flash chain is a standing exception to the strongest-class rule, or narrow the overlay. Pick the first or second option and apply it in the one owner doc.🔧 Fix applied.
1 info still open:
docs/configuration.md:1698- The instruction said a lane that fell back stays on the lower class 'until relaunched'. The same doc (line 1692) and the overlay say cooldown-expiry returns the session to Sol once the suppression window ends. I wrote both exits: 'until omp returns it to Sol when the suppression window ends, or until the lane is launched again'. This follows the code and the recorded review decision to state the cooldown-expiry return.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.