Wzorce odzyskiwania
ENWzorce odzyskiwania dla agentów AI to receptury, w których Claude Code, Codex lub Cursor chronią jedną niestabilną zależność za pomocą utrzymywanej biblioteki (opossum, cockatiel, Resilience4j lub tenacity) zamiast ręcznie pisanej maszyny stanów: circuit breakerem, ponowieniem, które nie pogłębia awarii, i uporządkowanym fallbackiem. Działanie każdego z nich potwierdza test kontraktu, który celowo otwiera obwód.
Ta strona jest dla programistów, którzy odpowiadają za usługę z kruchym wywołaniem zależności, i dla tech leadów, którzy decydują, co musi udowodnić pull request dodający odporność. Twój dostawca płatności ma 90-sekundową awarię o 2 w nocy. Każde żądanie checkoutu czeka na martwą zależność, pula połączeń się wyczerpuje i pada cała aplikacja, nie tylko płatności. Poproszony o „dodanie circuit breakera” agent pisze 200 linii class CircuitBreaker, których nikt nie testuje. Poniższe receptury dają mu zamiast tego nazwaną bibliotekę, nazwane wywołanie i test do spełnienia.
Co dają te receptury odzyskiwania
Dział zatytułowany „Co dają te receptury odzyskiwania”- Kontrakt odporności zapisany jako test, zanim agent zmieni wywołanie, więc „breaker się otwiera” i „błąd 400 nigdy nie jest ponawiany” to wynik zaliczony albo nie
- Pięć promptów do skopiowania: test kontraktu, circuit breaker z opossum, ponowienie z cockatiel nałożone na breaker, fallback ścieżki odczytu i review gotowego diffu
- Dokładne polecenia do wykonania tej pracy w Claude Code i Codeksie oraz odpowiednik w Cursorze
- Pętlę z serwerem Sentry MCP, która wybiera wywołanie do ochrony na podstawie danych z produkcji, a nie zgadywania
- Sposoby, w jakie napisany przez agenta kod odpornościowy przechodzi review i pogłębia awarię, z wyjściem z każdego z nich
Którego wzorca odzyskiwania potrzebuje to wywołanie?
Dział zatytułowany „Którego wzorca odzyskiwania potrzebuje to wywołanie?”Decyduj dla każdego wywołania osobno, nie dla całej usługi. Wzorzec zależy od tego, czy wywołanie odczytuje, czy zapisuje, i czy jego powtórzenie jest bezpieczne.
| Wywołanie | Wzorzec | Biblioteka w Node | Sprawdzenie, które to dowodzi |
|---|---|---|---|
| Wolna lub martwa zależność na ścieżce żądania | Circuit breaker z timeoutem i fallbackiem | opossum 10.0.0 | Breaker otwiera się po N błędach, fallback działa, próba half-open go zamyka |
Idempotentny odczyt (GET, wyszukiwanie po ID) | Ponowienie z backoffem z jitterem, nałożone na breaker | cockatiel 4.0.0 | 5xx jest ponawiane, 4xx nigdy, łączna liczba wywołań ma limit |
Nieidempotentny zapis (POST /charge) | Breaker i kolejka; żadnych ślepych ponowień | opossum plus twoja kolejka zadań | Ten sam klucz idempotencji trafia do dostawcy przy każdej próbie |
| Odczyt, dla którego istnieje zdegradowana odpowiedź | Uporządkowany fallback: główna baza, replika, cache, wartość domyślna | Zwykłe funkcje asynchroniczne | 404 jest propagowane; dalej przechodzą tylko błędy infrastruktury |
Wersje to tagi latest z npm sprawdzone 2026-09-26. Na JVM wskaż Resilience4j i jego adnotację Spring Boot @CircuitBreaker(name = ..., fallbackMethod = ...). W Pythonie wskaż tenacity (9.1.4) do ponowień i pybreaker (1.4.1) do breakera. Właśnie wskazanie biblioteki z nazwy powstrzymuje agenta przed pisaniem własnej.
Najpierw zapisz kontrakt odporności jako test
Dział zatytułowany „Najpierw zapisz kontrakt odporności jako test”Circuit breaker, którego nigdy nie otworzyłeś w teście, to circuit breaker, który debugujesz w trakcie incydentu. Najpierw napisz kontrakt, zobacz, jak nie przechodzi na niechronionym wywołaniu, i dopiero wtedy pozwól agentowi zmienić kod. Kontrakt jest tym, co recenzent czyta zamiast diffu.
Test steruje breakerem przez fabrykę, która przyjmuje nadpisania opcji. Ta decyzja projektowa ma znaczenie: test przekazuje resetTimeout: 50 i używa prawdziwych timerów, bo sztuczne timery mogą zablokować asynchroniczne I/O zaślepki HTTP i zawiesić test.
Przypadki 5 i 6 to te, które agenci pomijają. Timeout w opossum odrzuca wywołanie i uruchamia fallback, ale nie anuluje żądania, więc dostawca może dokończyć obciążenie, które twój kod już wysłał do kolejki fallbacku. opossum potrafi przerywać żądania przez AbortController, ale w opossum 10.0.0 ten kontroler jest wspólny dla całego breakera: jeden timeout przerywa wszystkie obciążenia w toku, a autoRenewAbortController odnawia go dopiero wtedy, gdy breaker przechodzi w half-open albo się zamyka, więc kolejne wywołania startują z już przerwanym sygnałem. Przypadek 6 to wyłapuje; sygnał tworzony osobno dla każdego wywołania go przechodzi. Nawet przerwane żądanie mogło dotrzeć do dostawcy, więc to klucz idempotencji sprawia, że ponowienie z kolejki jest nieszkodliwe.
Opakuj niestabilne wywołanie w circuit breaker
Dział zatytułowany „Opakuj niestabilne wywołanie w circuit breaker”Gdy kontrakt jest czerwony, implementacja to mała zmiana o jasnych granicach. Prompt jest taki sam dla każdego narzędzia; różni się sposób uruchomienia.
# Terminal, repository root. Flags checked against Claude Code 2.1.283; the# v2.1.285 change is from the Claude Code changelog (checked 2026-10-12).# claude -p starts in Manual mode (auto mode on third-party providers or with# telemetry off, from v2.1.285); --permission-mode dontAsk denies anything that# would prompt, on every provider. Reads and read-only commands still run.claude -p "$(cat prompts/payment-breaker.txt)" \ --permission-mode dontAsk \ --allowedTools "Read" "Edit" "Write" "Bash(npm test *)"Zapisz poniższy prompt jako prompts/payment-breaker.txt. Najpierw sam zainstaluj opossum, żeby agent nie potrzebował dostępu do sieci i nie mógł dodawać pakietów. Reguła Bash(npm test *) nie ogranicza tego, co uruchamiają testy: npm test wykonuje to, co wpisano w package.json, a Write może ten plik zmienić. Przejrzyj w diffie każdą zmianę package.json albo dodaj --disallowedTools "Edit(package.json)" "Write(package.json)".
# Terminal, repository root. Checked against codex-cli 0.157.1.# Permission profiles are beta (Codex CLI 0.138.0 or later).codex exec -c default_permissions=":workspace" "$(cat prompts/payment-breaker.txt)"Profil :workspace pozwala Codeksowi pisać tylko wewnątrz repozytorium. W wersji Codeksa bez profili uprawnień użyj zamiast tego --sandbox workspace-write; nie łącz obu. Jeśli chcesz dostać zmianę jako pull request zamiast lokalnych edycji, uruchom ten sam prompt jako zadanie w chmurze Codeksa.
Otwórz obok siebie src/routes/checkout.ts i test kontraktu, wklej prompt do Agenta i przejrzyj diff, zanim go zaakceptujesz. Potem uruchom npm test w terminalu. Gdy niestabilne wywołanie żyje w usłudze, której nie chcesz edytować lokalnie, przekaż ten sam prompt Cloud Agentowi i przejrzyj jego pull request.
Wynik powinien być krótki. Z opossum to konfiguracja, filtr i fallback, bez żadnej maszyny stanów:
import CircuitBreaker from 'opossum';
export function createPaymentBreaker({ charge, enqueue }: PaymentDeps, overrides = {}) { const breaker = new CircuitBreaker( // A signal per call: aborting it cancels this charge and no other (order: Order, key: string) => charge(order, key, { signal: AbortSignal.timeout(2500) }), { name: 'payments.charge', timeout: 3000, // backstop above the per-call abort errorThresholdPercentage: 50, volumeThreshold: 20, // never open on a handful of requests resetTimeout: 30000, errorFilter: (err: HttpError) => err.status >= 400 && err.status < 500, ...overrides, }, ); // opossum calls the fallback with the same arguments, so the key is reused breaker.fallback(async (order: Order, key: string) => { await enqueue('payments', { order, idempotencyKey: key }); return { status: 'queued' as const }; }); for (const state of ['open', 'halfOpen', 'close'] as const) { breaker.on(state, () => recordBreakerState(breaker.name, state)); } return breaker;}
// In the route: breaker.fire(order, order.idempotencyKey)opossum 10.0.0 nie dostarcza typów TypeScript, a @types/opossum jest w wersji 8.1.9 (oba sprawdzone 2026-09-26). Opcja nowsza niż typy, na przykład coalesce, nie przejdzie sprawdzania typów, choć opossum ją odczytuje. Każ agentowi rozszerzyć typ lokalnie, zamiast rzutować cały obiekt opcji na any.
Dodaj ponowienia, które nie pogłębią awarii
Dział zatytułowany „Dodaj ponowienia, które nie pogłębią awarii”Naiwne ponowienia mnożą obciążenie w czasie częściowej awarii: trzy ponowienia na każdym kliencie to samodzielnie wywołany skok ruchu na zależność, która już ledwo działa. Zapobiegają temu dwie zasady. Backoff z jitterem sprawia, że klienci nie ponawiają w tym samym rytmie, a ścisły predykat sprawia, że 400 ani nieidempotentny POST w ogóle nie są ponawiane.
Nałóż ponowienie na zewnątrz breakera, żeby każde ponowienie liczyło się do współczynnika błędów breakera, a długotrwała awaria go otwierała. W cockatiel robi to jedna linia: wrap(retryPolicy, breakerPolicy). Jeden szczegół psuje to połączenie: jeśli breaker ma fallback, ponowienie nigdy nie widzi błędu i nigdy nie ponawia. Umieść fallback na zewnątrz obu.
Zapis płatności zostaw poza tą recepturą. POST /charge dostaje breaker i kolejkę, a worker kolejki ponawia z tym samym kluczem idempotencji. Jeśli twój dostawca płatności nie przyjmuje klucza idempotencji, w ogóle nie ponawiaj obciążenia automatycznie.
Degraduj odczyty uporządkowanym fallbackiem
Dział zatytułowany „Degraduj odczyty uporządkowanym fallbackiem”Przy odczytach łańcuch fallbacków utrzymuje funkcję przy życiu, gdy główne źródło leży: główna baza, potem replika do odczytu, potem cache oznaczony jako nieaktualny, potem bezpieczna wartość domyślna. Każdy poziom musi być oznaczony w odpowiedzi, inaczej wdrażasz cichą nieaktualność. Zostaw to jako uporządkowaną listę funkcji asynchronicznych, a nie framework FallbackChain<T>.
Reguła ponownego rzucenia odróżnia odporność od fabryki błędów. Fallback, który połyka uzasadnione 404 i serwuje profil z cache, jest gorszy niż awaria, którą miał ukryć.
Niech błędy z produkcji wskażą wywołanie do ochrony
Dział zatytułowany „Niech błędy z produkcji wskażą wywołanie do ochrony”Bez danych z produkcji agent zgaduje, które wywołanie jest niestabilne. Podłącz serwer Sentry MCP, a agent przeczyta prawdziwe zgłoszenie (stack trace, liczbę zdarzeń, zawodzący span), zanim zaproponuje breaker. Serwer Sentry jest zdalny, więc nie ma czego instalować; między narzędziami różni się tylko rejestracja. Pełna konfiguracja tylko do odczytu, opcje tokenów i zakresy są na stronie Serwery MCP do obserwowalności.
# Checked against Claude Code 2.1.283claude mcp add -s project --transport http sentry https://mcp.sentry.dev/mcpclaude mcp login sentry # or run /mcp inside a session# Checked against codex-cli 0.157.1codex mcp add sentry --url https://mcp.sentry.dev/mcpcodex mcp login sentryDodaj serwer do .cursor/mcp.json w projekcie, a potem zatwierdź go w ustawieniach MCP w Cursorze:
{ "mcpServers": { "sentry": { "url": "https://mcp.sentry.dev/mcp" } } }Na ekranie OAuth zostaw Triage Issues odznaczone: agent ma tu czytać zgłoszenia, a nie je zamykać. Jeśli wolisz gotowy skill od gołego serwera, plugin Sentry (sentry@claude-plugins-official lub codex plugin add sentry@openai-curated dla Codeksa, sprawdzone z pomocą 0.157.1; wymaga zalogowanego Codeksa) łączy ten sam serwer ze skillem sentry-debug-issue. Zainstaluj jedno albo drugie, nie oba, bo inaczej serwer zarejestruje się dwa razy.
Przed podłączeniem „dodaj circuit breaker do checkoutu” daje breaker wokół wywołania, które tylko wygląda na ryzykowne. Po podłączeniu:
Jak udowodnić, że kod odzyskiwania działa, bez czytania każdej linii?
Dział zatytułowany „Jak udowodnić, że kod odzyskiwania działa, bez czytania każdej linii?”Pull request dodający odporność jest mały, ale jego tryb awarii ujawnia się dopiero podczas incydentu. Przeglądaj dowody, nie diff:
- Test kontraktu: czerwony na starym kodzie, zielony na nowym. Obejmuje otwarcie obwodu, filtr
4xx, timeout, próbę half-open, klucz idempotencji i wywołanie następujące po timeoucie. - Test liczby ponowień: sprawdza dokładną liczbę wywołań dla
503,404i otwartego obwodu. Polityka ponowień bez asercji na liczbę wywołań psuje się po cichu: przechodzi każdy inny test, a mnoży obciążenie. - Grepy z pustym wynikiem:
git grep -nE "class [A-Za-z]*(CircuitBreaker|Retry|Backoff)" src/nic nie zwraca, więc obok biblioteki nie wślizgnęła się ręcznie napisana maszyna stanów. - Metryka i alert:
circuit_breaker_state_changeistnieje, a alert odpala, gdy breaker pozostaje otwarty. Wklej do opisu pull requesta jedną prawdziwą linię metryki z lokalnego uruchomienia. - Jedna zainscenizowana awaria: przed pierwszym wdrożeniem na produkcję wstrzyknij awarię na stagingu przez proxy, na przykład Toxiproxy, i obserwuj otwarcie breakera na dashboardzie. Metoda jest opisana w testowaniu przez wstrzykiwanie błędów.
Autor zatwierdza kroki 1–4 w pull requeście. Właściciel usługi lub lider dyżuru zatwierdza krok 5 i progi, bo to on odpowiada za czas odtworzenia, który te progi kodują. Żeby sprawdzić gotowy diff według tej listy, uruchom prompt review w nowej sesji:
Co się psuje, gdy agent pisze kod odpornościowy?
Dział zatytułowany „Co się psuje, gdy agent pisze kod odpornościowy?”- Breaker otwiera się przy chwilowym zakłóceniu.
volumeThresholdw opossum domyślnie wynosi 0, więc przyerrorThresholdPercentage: 50jeden błąd na jedno żądanie wystarczy, żeby otworzyć obwód. Wyjście: ustawvolumeThresholdna liczbę żądań, którą przekracza nawet spokojna minuta ruchu, aresetTimeoutopieraj na zaobserwowanym czasie powrotu zależności (prompt z Sentry powyżej), nie na okrągłej liczbie. - Ponowienia zamieniają spowolnienie w awarię. Ponowienie wewnątrz breakera albo brak breakera pozwala każdemu klientowi dalej zasypywać zależność żądaniami. Wyjście:
wrap(retry, breaker)z ponowieniem na zewnątrz, backoff z jitterem i test sprawdzający łączną liczbę wywołań. - Ponowienie nigdy nie następuje. Fallback zarejestrowany na breakerze zamienia każdy błąd w sukces, więc otaczające go ponowienie nie ma czego ponawiać. Wyjście: przenieś fallback na zewnątrz ponowienia i breakera i zachowaj test liczby ponowień.
- Fallback maskuje prawdziwy błąd. Fallback łapiący wszystko zamienia
400, błędy autoryzacji i brakujące rekordy w ciche, błędne odpowiedzi. Wyjście:errorFilterw opossum dla4xx, jawny predykatisRetryabledla cockatiel i test, że404jest propagowane. - Timeout obciąża klienta dwa razy. Breaker przekracza timeout, fallback kolejkuje zamówienie, a pierwotne żądanie i tak kończy się u dostawcy. Wyjście: daj każdemu wywołaniu własny
AbortSignal.timeout(), nieco krótszy niżtimeoutw opossum, zamiast wspólnego sygnału breakera zgetSignal(), oraz wysyłaj ten sam klucz idempotencji w pierwotnym wywołaniu i w zakolejkowanym. - Otwarty breaker ukrywa awarię. Gdy fallback działa, odsetek błędów widocznych dla użytkowników wygląda dobrze i nikt nie dostaje powiadomienia, choć zależność leży całkowicie. Wyjście: alertuj na metrykę stanu breakera i na licznik fallbacków, a nie tylko na odsetek 5xx. Kierowanie takiego alertu opisują wzorce monitoringu i logowania.
- Agent zgaduje API biblioteki. Nazwy opcji różnią się między opossum, cockatiel i starszymi forkami, a typy mogą nie nadążać za pakietem. Wyjście: rozstrzyga test kontraktu; podłącz serwer MCP z dokumentacją, na przykład Context7, żeby agent czytał dokumentację zainstalowanej wersji.
Dokąd dalej ze wzorcami odzyskiwania
Dział zatytułowany „Dokąd dalej ze wzorcami odzyskiwania”- Wzorce debugowania: znajdź przyczynę źródłową, która w ogóle uzasadnia breaker
- Wzorce logowania: zamień każde przejście breakera w strukturalną, skorelowaną linię logu
- Wzorce monitoringu i logowania: zamień otwarty breaker w alert, który kogoś budzi
- Testowanie przez wstrzykiwanie błędów: wstrzykiwanie awarii na poziomie systemu, poza kontraktem jednostkowym
- Reagowanie na incydenty z AI: co się dzieje, gdy breaker otworzy się o 2 w nocy
- Receptury obsługi błędów: reszta tej sekcji
Czy ten artykuł Ci pomógł?
Jedno kliknięcie, bez logowania.
Co z tym zrobisz?
Jak się czytało?
Co przeszkadzało? Możesz zaznaczyć kilka
Twoja rola?
Dzięki. Czytam każdą odpowiedź. Michał Jaskólski
Pomógł Ci ten artykuł?
Każde to jedno kliknięcie. Zamknij, kiedy chcesz.
bez logowania