En kaskade av agenter: terminalen velger den som faktisk virker
Da Claude delvis gikk ned og noen modeller sluttet å svare, prøvde jeg å bytte til Gemini — og oppdaget at reserven min hadde vært ødelagt i månedsvis. Slik bygger du en som faktisk holder.
TL;DR
En bash-starter kalt agent som velger den beste fungerende AI-koding-CLI-en før den starter. Den degraderer langs to akser: først modellen (Opus → Sonnet, fortsatt innenfor Anthropic), så transporten, og først deretter leverandøren. Hver probe gjør et ekte API-kall, fordi modellister lyver. En vakthund sjekker de nedre trinnene annenhver time og varsler på Telegram.
$ agent --status
OK claude / claude-opus-5 api.anthropic.com reachable, claude-opus-5 answered
OK claude / claude-sonnet-5 api.anthropic.com reachable, claude-sonnet-5 answered
OK claude-proxy / claude-opus-5 claude-opus-5 answered
OK claude-proxy / claude-sonnet-5 claude-sonnet-5 answered
OK agy antigravity answered on Gemini 3.6 Flash (Low)
OK gemini localhost:8317 answered for gemini-3.1-pro-lowProblemet
Når Claude er utilgjengelig, vil jeg at terminalen skal fortsette å virke. Den naive varianten er «hvis Claude er nede, kjør Gemini» — og det trodde jeg at jeg hadde. Men de interessante feilene er ikke nedetid. Den vanligste: Opus går tom mens Sonnet svarer helt fint. Å hoppe til en annen leverandør der er absurd.
En ekte reserve må altså degradere per modell før den degraderer per leverandør. Og den må vite hvilket trinn som faktisk lever — det viste seg å være den vanskelige delen.
Fem måneders løgn
Før jeg skrev noe, sjekket jeg det jeg allerede hadde. Proxyens auth-katalog fortalte hele historien i to tidsstempler:
claude-newiqa@gmail.com.json — fornyet i dag, 06:41 · gemini-newiqa@gmail.com-….json — sist berørt 27. mars
Claude-tokenet ble fornyet daglig. Gemini-tokenet hadde ikke rørt seg på fem måneder. Helsesjekken min så bare på claude-*.json, så ingen merket noe. Reserven hadde vært dekorativ siden våren — et grønt lys over et tomt rom. En reserve ingen faktisk kaller, er ingen reserve.
Hvorfor en gateway ikke løser dette
Det åpenbare trekket er en lokal LLM-gateway — Bifrost, LiteLLM — med leverandørbytte i konfigurasjonen. Jeg kjører allerede én (CLIProxyAPI), så en til hadde bare gitt enda en daemon å passe på.
Viktigere: en gateway feiler på selve oppgaven. Bytte på API-nivå betyr å bytte modell under samme klient. Gi Claude Code et Gemini-svar via et oversettelseslag, og den faller fra hverandre på tool-use og streaming. Reserven må bytte CLI, ikke modellen bak.
Seks trinn, to akser
Leverandør og modell svikter uavhengig, så stigen fletter dem. Hvert trinn gir noe bestemt, og prisen for å gå ned er uttalt:
| Rung | What it survives | Cost of getting there |
|---|---|---|
| claude + opus | nothing — this is the happy path | — |
| claude + sonnet | Opus capped or overloaded | weaker model, same session shape |
| claude-proxy + opus | stale local login, broken CLI auth | separate OAuth token |
| claude-proxy + sonnet | both of the above at once | weaker model via proxy |
| agy | Anthropic outage / exhausted plan | different vendor, different CLI |
| gemini | agy itself broken | different CLI again, shares agy's quota |
I koden er et trinn en trippel: backend, modell å probe, modell å kjøre med. Den tomme kjøremodellen på øverste trinn betyr mer enn den ser ut: å ikke sende --model bevarer profilens opus[1m] og dens 1M-kontekst.
# Each rung: backend | model to probe | model to run with
# An empty run-model means "let the CLI use its configured default", which keeps
# the profile's opus[1m] (and its 1M context) instead of downgrading it to plain
# opus just to name it explicitly.
RUNGS=(
"claude|$AGENT_OPUS_MODEL|"
"claude|$AGENT_SONNET_MODEL|sonnet"
"claude-proxy|$AGENT_OPUS_MODEL|$AGENT_OPUS_MODEL"
"claude-proxy|$AGENT_SONNET_MODEL|$AGENT_SONNET_MODEL"
"agy||"
"gemini||"
)Når øverste trinn er utilgjengelig, annonseres nedstigningen i stedet for å skje stille — en degradert økt må aldri forveksles med en normal:
$ agent -p "explain this bug"
[agent] Claude Code (claude-opus-5) unavailable — claude-opus-5 unavailable: {"error":...}
[agent] using Claude Code (claude-sonnet-5)
# ...the session continues on Sonnet, inside Anthropic, with no vendor switch.Modellister lyver
Første instinkt er å probe billig: spørre gatewayen om /v1/models. Det er verdiløst. Proxyen min annonserte muntert gemini-* hele tiden mens hvert kall ga auth_unavailable. Antigravity-CLI-en gjør det samme — agy models svarer fra en katalog.
Det samme gjelder modellhelse. Ingen liste-endepunkt forteller at Opus er ratebegrenset akkurat nå. Bare en ekte forespørsel viser det — en 429 eller 529:
# Real /v1/messages call for ONE model. This is what distinguishes "Opus is
# capped" from "Anthropic is down" — no model list can tell you that.
probe_model() {
local m="$1" body
body=$(curl -sS --max-time "$PROBE_TIMEOUT" \
"$AGENT_PROXY_URL/v1/messages" \
-H "Authorization: Bearer $AGENT_PROXY_KEY" \
-H 'content-type: application/json' \
-d "{\"model\":\"$m\",\"max_tokens\":1,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}")
case "$body" in
*'"type":"message"'*) REASON="$m answered"; return 0 ;;
esac
REASON="$m unavailable: $(printf '%s' "$body" | tr -d '\n' | cut -c1-140)"
return 1
}Modelltilgjengeligheten for de offisielle trinnene lånes fra proxyen, som fronter samme Anthropic-konto. Hele forhåndssjekken ble målt til 7 tokens og rundt 1,3 sekunder.
To sikkerhetsnett, ikke ett
En forhåndsprobe fanger ikke alt. Hvis kvoten tar slutt i mellomrommet mellom probe og start, hadde proben rett og var likevel ubrukelig. Derfor et nett nummer to: sluttkode pluss medgått tid.
start=$SECONDS
run_rung "$backend" "$rmodel" "$@"
rc=$?
elapsed=$(( SECONDS - start ))
# Clean exit, or the user interrupted it — either way, done.
if [ $rc -eq 0 ] || [ $rc -eq 130 ]; then
exit $rc
fi
# Survived long enough to have been genuinely used: a real error, not a rung
# that never started. Do not silently rerun the work somewhere else.
if [ $elapsed -ge $FASTFAIL_SECONDS ]; then
exit $rc
fi
warn "$(label "$backend" "$pmodel") exited $rc after ${elapsed}s — treating as unavailable"Terskelen på 25 sekunder koder en vurdering. Et trinn som dør nesten umiddelbart, startet aldri egentlig. Et trinn som kjørte i minutter og så feilet, arbeidet faktisk, og å stille kjøre det arbeidet på nytt hos en annen leverandør ville vært verre enn å vise feilen.
Vakthunden og fellen i den
Starteren kjører bare når jeg kjører den, så et trinn kan råtne mellom økter. En sjekk innebygd i helsedroiden min (launchd, annenhver time, Telegram-varsler) kaller samme probekode. Første versjon hadde nøyaktig den buggen denne artikkelen handler om: den returnerte suksess på første grønne trinn.
# Healthy — every non-Anthropic rung answers
$ agent --check-fallback
all provider-independent fallbacks OK: agy — antigravity answered...; gemini — ...
rc=0
# One rung rotted while the other still works. This is the case that used to
# pass silently, and the whole reason the check exists.
$ GEMINI_API_KEY=broken agent --check-fallback
DEGRADED: still covered by agy — antigravity answered on Gemini 3.6 Flash (Low)
but a rung died: gemini — gemini call failed: {"error":"Invalid API key"}
rc=1Det er feil, fordi de to ikke-Anthropic-trinnene holder separate tokens — Antigravity-CLI-en sitt eget, Gemini-trinnet proxyens. Ett kan råtne mens det andre ser perfekt ut. Derfor prober sjekken alle trinn og behandler DEGRADED som en feil.
Tre feil verdt å skrive ned
Hver av dem ga grønt signal over ødelagt maskineri — samme feilmodus, tre ganger på én ettermiddag:
- Proben testet en modell jeg aldri bruker. Jeg probet
gemini-3.1-pro-previewmens CLI-en var konfigurert forgemini-3.1-pro-low. De løses til ulike leverandører, så jeg diagnostiserte et «utløpt token» som aldri fantes. En probe må gå nøyaktig den veien den ekte klienten tar. - En stille tom variabel. Å parse proxyens YAML-nøkkel med
awkog\x27ga tom streng under BSD awk — uten feil eller advarsel. Sjekken gikk likevel gjennom. Løst medawk -v q="'". - Proben gikk gjennom mens det ekte feilet. En
curl-probe ga 200, men det ektegemini -pnektet å starte: headless-modus blokkerer på ikke-betrodde kataloger. Trinnet var ødelagt nettopp i situasjonen det finnes for.
Bruk
Å skrive agent gir beste tilgjengelige trinn med den vanlige Claude Code-opplevelsen. Eksplisitt valg hopper over probing helt — ber du om Gemini, får du Gemini:
agent # auto-pick the best live rung
agent --use gemini # force a backend
agent --use claude:sonnet # force a backend AND a model
agent --use claude-proxy:opus # opus/sonnet/haiku map to full ids per backend
agent --list # backend names
agent --status # probe every rung and report
agent --check-fallback # exit 1 if a non-Anthropic rung is missing
agent -p "..." # flags pass straight through to the chosen CLIKonklusjoner
Ingeniørarbeidet her er umerkelig: et bash-skript, noen curl-kall, et ordnet array. Det som krevde innsats, var å mistro mine egne sjekker. Tre ganger bygde jeg noe som meldte suksess mens det under var ødelagt — og hver gang var den billige, raske proben synderen.
Robusthet er ikke listen over backends du konfigurerte. Det er om noe verifiserer at listen fortsatt stemmer. Min hadde seks trinn på papiret og ett ekte i fem måneder.
Kilder
- Claude Code Documentation — headless mode,
ANTHROPIC_BASE_URL, model flags - CLIProxyAPI — the local multi-provider gateway behind the proxy rungs
- Gemini CLI — the last rung, and its trusted-folders behaviour
- Apple Developer — launchd jobs, used by the watchdog
Fant du en feil?
Et feil faktum, en skjev oversettelse, noe som virker usant i denne artikkelen? Skriv til meg — på ditt eget språk.