Agent zrobił to samo dwa razy

przez Łukasz | sie 24, 2026

Objawy

Ten syndrom różni się od pozostałych jedną rzeczą: objaw widzi klient, nie ty.

Trzy identyczne wiadomości w skrzynce. Dwa zamówienia zamiast jednego. Podwójne obciążenie. Duplikaty rekordów, które ktoś zauważa tydzień później.

W twoim dzienniku wszystko wygląda poprawnie — jedno wywołanie, jeden wynik. Bywa nawet gorzej: log mówi, że operacja się nie udała, a w systemie docelowym jest wykonana.

Ten rozjazd między tym, co widzisz, a tym, co się wydarzyło, jest głównym sygnałem rozpoznawczym.

Mechanizm w trzech zdaniach

W pętli agenta powtórzenie jest regułą, nie wyjątkiem: ponowienia po błędach, zapętlenie modelu, restart procesu, równoległe wywołania, ponowne uruchomienie zadania. W klasycznej aplikacji ścieżki wykonania są przewidywalne — tutaj nie są.

Operacja, która nie jest odporna na powtórzenie, prędzej czy później zostanie powtórzona. Pojęcie porządkujące to idempotencja, a warstwę wykonawczą opisuje artykuł o awariach narzędzi.

Najpierw ustal, gdzie powstaje duplikat

Trzy miejsca, każde z inną naprawą.

Na wejściu — to samo zadanie trafiło do agenta dwa razy. W pętli — agent wywołał narzędzie więcej niż raz. W transporcie — wywołanie było jedno, dotarło dwa razy albo dotarło mimo zgłoszonego błędu.

Rozstrzyga to porównanie trzech liczb za ten sam okres: ile zadań weszło, ile wywołań narzędzia jest w twoim logu, ile operacji ma system docelowy.

Diagnostyka różnicowa

1. Ponowienie po przekroczeniu czasu po stronie klienta

Jak rozpoznać: log mówi „timeout”, system docelowy ma rekord. Liczba operacji w systemie docelowym jest wyższa niż liczba udanych wywołań w twoim logu.

Dlaczego to najgroźniejsze: operacja się wykonała, tylko odpowiedź nie wróciła. Z perspektywy pętli to nieudane wywołanie, więc ponawia. Nie ma sposobu, żeby odróżnić „nie zadziałało” od „zadziałało, ale nie wiem” bez pytania systemu docelowego.

Test: dla kilku spraw z timeoutem sprawdź ręcznie, czy operacja istnieje po drugiej stronie.

2. Ciche ponowienie na poziomie biblioteki

Jak rozpoznać: liczba wywołań w twoim kodzie i w logu dostawcy się różni, a ty nigdzie nie napisałeś pętli ponowień.

Dlaczego się dzieje: SDK, klient HTTP albo framework agentowy ponawiają domyślnie. Zwykle rozsądnie — dopóki operacja nie ma skutków ubocznych.

Test: wypisz faktyczną konfigurację ponowień w używanych bibliotekach, zamiast zakładać, że jest wyłączona.

3. Zapętlenie modelu

Jak rozpoznać: w kontekście widać kilka wywołań tego samego narzędzia z tymi samymi argumentami — świadomych, nie technicznych.

Dlaczego się dzieje: to pierwszy syndrom z konsekwencjami w świecie. Jeśli narzędzie ma skutki uboczne, każde powtórzenie jest osobną operacją.

Test: policz wystąpienia narzędzia w śladzie pojedynczego przebiegu.

4. Restart bez trwałego rejestru

Jak rozpoznać: duplikaty pojawiają się seriami, skorelowanymi z wdrożeniami albo restartami procesu. Dotyczy agentów działających w tle.

Dlaczego się dzieje: agent długo żyjący po restarcie zaczyna od zera. Wszystko, czego nie zapisał trwale, jest dla niego niewykonane.

Test: sprawdź, czy rejestr wykonanych operacji przeżywa restart i czy zapis następuje przed operacją, nie po.

5. Równoległe wywołania z jednej odpowiedzi

Jak rozpoznać: duplikaty mają identyczny znacznik czasu co do sekundy.

Dlaczego się dzieje: model poprosił o kilka wywołań naraz, wśród nich dwa takie same albo różniące się nieistotnie. Pętla wykonała je współbieżnie.

Test: sprawdź, czy zrównoleglasz wywołania i czy obejmuje to narzędzia zapisujące.

6. Brak deduplikacji na wejściu

Jak rozpoznać: liczba zadań, które weszły, jest wyższa niż liczba zgłoszeń od użytkowników.

Dlaczego się dzieje: użytkownik kliknął dwa razy, kolejka dostarczyła wiadomość ponownie, webhook przyszedł podwójnie. Agent zadziałał poprawnie — dwa razy, bo dwa razy go poproszono.

Test: porównaj liczby na wejściu z liczbą realnych zgłoszeń.

7. Wiele instancji tego samego agenta

Jak rozpoznać: duplikaty pojawiły się po skalowaniu poziomym albo po dodaniu drugiego środowiska.

Dlaczego się dzieje: dwie instancje pobrały to samo zadanie z kolejki, bo nie ma blokady ani znacznika przetwarzania.

Test: sprawdź, czy zadanie jest oznaczane jako podjęte, zanim zacznie być wykonywane.

Postępowanie

Natychmiast: wyłącz automatyczne ponawianie dla operacji ze skutkami ubocznymi. Lepiej zgłosić błąd i poprosić o decyzję, niż wykonać coś drugi raz. To zatrzymuje krwawienie w kilkanaście minut.

Potem: klucz idempotencji. Wywołanie niesie identyfikator, po którym system docelowy rozpoznaje powtórzenie i zwraca wynik pierwszego wykonania zamiast wykonywać ponownie. Standard w API płatniczych i najlepsze dostępne rozwiązanie przyczyn pierwszej, drugiej, trzeciej i piątej naraz. Jako klucza użyj identyfikatora przebiegu połączonego z numerem kroku — masz go już w śladzie przebiegu.

Gdy systemu docelowego nie da się zmienić: sprawdzenie stanu przed działaniem. Zanim wyślesz, zapytaj, czy taka operacja już istnieje. Słabsze niż klucz idempotencji, bo między sprawdzeniem a zapisem jest szczelina, ale zwykle wystarczające.

Dla operacji nieodwracalnych: rozdzielenie przygotowania od zatwierdzenia. Agent tworzy operację w stanie oczekującym, zatwierdza ją osobny krok — najlepiej z udziałem człowieka.

Zapisuj ślad przed operacją, nie po. Ta kolejność ma większe znaczenie niż cokolwiek innego w tym mechanizmie: rejestr zapisany po wykonaniu nie chroni przed niczym, bo awaria zdarza się dokładnie pomiędzy.

Profilaktyka

Reguła, od której warto zacząć projektowanie narzędzi: każde narzędzie ze skutkiem ubocznym jest albo idempotentne, albo objęte potwierdzeniem przed wykonaniem. Trzeciej możliwości nie ma.

Oznaczaj narzędzia jawnie w kodzie, żeby pętla mogła automatycznie decydować o ponawianiu i zrównoleglaniu. Nie zrównoleglaj operacji zapisujących. Deduplikuj na wejściu.

Jak zdiagnozować u siebie

Symulacja przyczyny pierwszej — operacja wykonana, odpowiedź zgubiona — i porównanie zachowania bez klucza idempotencji i z nim.

python
import uuid

SYSTEM_DOCELOWY = []          # "baza" po drugiej stronie
WIDZIANE_KLUCZE = {}

class Timeout(Exception): pass

def wyslij_mail(adres, tresc, klucz=None, gubie_odpowiedz=False):
    """Narzędzie po drugiej stronie sieci."""
    if klucz and klucz in WIDZIANE_KLUCZE:
        return WIDZIANE_KLUCZE[klucz]          # rozpoznane powtórzenie

    SYSTEM_DOCELOWY.append({"adres": adres, "tresc": tresc})
    wynik = {"status": "wyslano", "id": len(SYSTEM_DOCELOWY)}
    if klucz:
        WIDZIANE_KLUCZE[klucz] = wynik

    if gubie_odpowiedz:                        # wykonane, ale nie wróciło
        raise Timeout("brak odpowiedzi")
    return wynik

def z_ponowieniami(klucz=None, prob=3):
    for i in range(prob):
        try:
            return wyslij_mail("klient@firma.pl", "Potwierdzenie",
                               klucz=klucz, gubie_odpowiedz=(i < 2))
        except Timeout:
            print(f"  próba {i+1}: timeout — ponawiam")
    return None

print("BEZ klucza idempotencji:")
SYSTEM_DOCELOWY.clear(); WIDZIANE_KLUCZE.clear()
z_ponowieniami()
print(f"  maili w systemie docelowym: {len(SYSTEM_DOCELOWY)}\n")

print("Z kluczem idempotencji:")
SYSTEM_DOCELOWY.clear(); WIDZIANE_KLUCZE.clear()
klucz = f"{uuid.uuid4().hex[:8]}-krok3"
z_ponowieniami(klucz=klucz)
print(f"  maili w systemie docelowym: {len(SYSTEM_DOCELOWY)}")

Pierwszy przebieg wysyła trzy maile, mimo że pętla widzi wyłącznie nieudane próby. Drugi wysyła jednego — bo system docelowy rozpoznaje powtórzenie po kluczu, choć z perspektywy pętli sytuacja jest identyczna.

To jest cała różnica między agentem, który po awarii sieci zawiadamia klienta raz, a takim, który robi to trzy razy. Kod pętli w obu przypadkach ten sam.

Ćwiczenie: policz w swoim systemie trzy liczby za ostatni miesiąc — zadania na wejściu, wywołania narzędzi ze skutkami ubocznymi w logu, operacje w systemie docelowym. Jeśli trzecia jest wyższa od drugiej, masz przyczynę pierwszą albo drugą i wiesz, gdzie szukać.

Powiązane

Klasyfikacja awarii, ponowienia i skutki uboczne: Maszyneria agenta.

Następny syndrom: Agent zrobił coś, o co nikt nie prosił.

Agent kończy, ale zadania nie wykonał

Agent kończy, ale zadania nie wykonał

Objawy Agent odpowiada: „Przygotowałem i wysłałem potwierdzenie do klienta." Potwierdzenie nie zostało wysłane. Albo: „Zaktualizowałem wszystkie rekordy" — zaktualizował trzy z dwunastu. Albo: „Nie znalazłem żadnych zamówień" — bo narzędzie zwróciło błąd, którego nikt...

Agent zrobił coś, o co nikt nie prosił

Agent zrobił coś, o co nikt nie prosił

Objawy Operacja, której nikt nie zlecił. Dane wysłane pod adres, którego nie ma w żadnej konfiguracji. Rekord zmieniony poza zakresem zadania. Odpowiedź, z której wynika, że agent dostał instrukcje od kogoś innego niż ty. Cecha wspólna: technicznie wszystko zadziałało...

Agent gubi wątek w długim zadaniu

Agent gubi wątek w długim zadaniu

Objawy Pierwsze kroki idą wzorowo. Po kilkunastu agent zaczyna się rozjeżdżać. Przestaje przestrzegać reguły z promptu systemowego, której trzymał się na początku. Zmienia format odpowiedzi w połowie zadania. Wraca do czegoś, co już ustalił, i ustala to inaczej....

Agent podaje dane, których nie ma

Agent podaje dane, których nie ma

Objawy Numer zamówienia w idealnym formacie, którego nie ma w bazie. Kwota, której w dokumencie nie ma. Nazwa pola API, które nigdy nie istniało. Cytat z regulaminu, brzmiący dokładnie jak reszta regulaminu i w nim nieobecny. Cecha wspólna wszystkich tych przypadków:...

Działał wczoraj, dziś nie działa

Działał wczoraj, dziś nie działa

Objawy Nic się nie wywala. Nie ma wyjątków, nie ma timeoutów, dziennik wygląda tak samo jak zawsze. Po prostu odpowiedzi są gorsze. Postacie, w jakich to się objawia: Jakość. Agent zaczyna pomijać kroki, które wcześniej wykonywał, albo odpowiada ogólniej. Format....

Agent wybiera złe narzędzie

Agent wybiera złe narzędzie

Objawy Agent odpowiada nie na to pytanie. Pobiera listę zamówień, gdy pytano o jedno konkretne. Odpowiada z pamięci, choć miał sprawdzić w bazie. Albo sięga po właściwe narzędzie i wpisuje w argumenty coś, czego nie da się użyć. Sygnał, który odróżnia ten syndrom od...

Agent kręci się w kółko

Agent kręci się w kółko

Objawy Agent wykonuje kolejne obroty pętli, nie zbliżając się do zakończenia zadania. W dzienniku widać jedną z trzech postaci. Powtórzenie. To samo narzędzie, te same argumenty, raz za razem — czasem dziesiątki razy pod rząd. Oscylacja. Agent wywołuje na przemian dwa...

Co logować w pętli agenta

Co logować w pętli agenta

W drugim artykule padło, że pętla powinna zwracać nie samą odpowiedź, lecz odpowiedź razem z powodem zakończenia, liczbą kroków i zużytym budżetem. To był zalążek. Tu jest pełna wersja. Typowy dziennik agenta zapisuje pytanie i odpowiedź. To akurat najmniej użyteczna...