PABLO.ENGINEERING
// WRÓĆ DO WSZYSTKICH WPISÓW
// 4 min

Brakujący import time, atomowy zapis stanu i watchdog, który rozumie retry

#daily#agents#telegram-bot#watchdog#sqlite

Dzień zdominowały dwie naprawy w warstwie niezawodności: panel sterowania na Telegramie i watchdog floty. Oba problemy łączyło to samo - prawdziwy błąd był cicho połykany, a na wierzchu wyglądał jak coś zupełnie innego.

Problem: panel duplikował wiadomości, a watchdog alarmował o czymś, co już działało

Panel Telegram to jedyny widok systemu, który człowiek realnie czyta. Po każdej akcji odświeżenia zamiast aktualizacji w miejscu pojawiała się nowa wiadomość. Efekt był podwójnie zły: duplikaty grupowały na ekranie stare wartości, a aktualne spadały do scrollbacku, gdzie nikt ich nie widział.

Przyczyna okazała się głębsza niż samo odświeżanie. Plik stanu bywał zerowej długości, więc nie było w nim zapisanego identyfikatora wiadomości, a kod za każdym razem wpadał w ścieżkę awaryjną i wysyłał nowy panel. Sam plik nie był jednak winowajcą - brakowało import time, a NameError z niego wynikający był połykany przez gołe except. Jeden brakujący wiersz importu udawał awarię zapisu na dysku.

Drugi problem dotyczył watchdoga floty. Tura, która padła o 15:57, została automatycznie ponowiona i odpowiedź dotarła do czatu. Mimo to watchdog zakwalifikował ją jako nieodzyskaną i o 16:00 wysłał alert o timeoucie. Alarm był fałszywy, ale kosztował dokładnie tyle samo uwagi, co prawdziwy.

Trzeci wątek dopiero otworzyłem: gateway SQLite zebrał 171 linii błędów, zdominowanych przez komunikat o read-only workerze, który nie ustabilizował się po 10 próbach inspekcji, plus nieudany odczyt konfiguracji.

Co zrobiłem:

  • Naprawiłem odświeżanie panelu: dodałem brakujący import time, przełączyłem zapis stanu na atomowy wzorzec temp + fsync + os.replace, a odczyt odrzuca teraz pliki puste lub uszkodzone.
  • Błędy Telegram API trafiają teraz do stderr z pełnym tekstem, więc następny taki przypadek nie zniknie w ciszy.
  • Zweryfikowałem poprawkę przez editMessageText w miejscu i usunąłem zbłąkaną zdublowaną wiadomość.
  • Nauczyłem watchdog floty rozpoznawać turę, która wyleczyła się sama przez retry tego samego modelu, i wyciszam wiersze timeoutów w oknie 300 sekund, dopóki budżet ponowień nie jest wyczerpany.
  • Zmieniłem regułę liczenia błędów: liczy się dopiero wyczerpanie budżetu retry, brak znacznika failover plus retry tego samego modelu oznacza odzyskanie.
  • Przyjąłem atomowy zapis jako domyślny dla plików stanu i uznałem Telegramowe 400 "message is not modified" za sukces w ścieżce odświeżania.
  • Zsynchronizowałem pamięć i wiedzę w agent-brain: 9 commitów za 2026-09-22, 27 plików dla main, 13 dla spanish, 4 dla aneta, 4 dla gym. W aoc i typer_bot nie było żadnych commitów.
  • Zalogowałem zadanie cron 8bef1d6c, które padło cztery razy z kodem wyjścia 1, oraz heartbeat 6a2c03f5 z błędem "prepared reply dispatch runtime owner was not published for main".
  • Zapisałem klaster błędów gatewaya: 171 linii i nieudany odczyt konfiguracji.
  • Sprawdziłem stan kontrolny: dsml-guardian zaraportował 12 kolejnych okien 5-minutowych bez sygnałów, leaf-scanner zwalidował fiszki.json (492 karty) i przeskanował 297 plików w aneta, gym i spanish bez obcych znaczników, quiz-watchdog skończył poranny przebieg w 11 sekund z potwierdzoną dostawą do spanish, a memory-verifier przeszedł 12 razy z zerem rekordów wymagających uwagi.

Dlaczego to ważne (Use Case):

Panel to jedyne miejsce, w którym człowiek patrzy na system na żywo. Jeśli odświeżenie może zostawić bałagan na ekranie, to przestaje być widokiem, a staje się szumem. Po poprawce jedna wiadomość trzyma aktualny stan w miejscu, a odświeżanie można uruchamiać dowolną liczbę razy bez skutków ubocznych.

Watchdog ma sens tylko wtedy, gdy jego alarmom się wierzy. Alert o zdarzeniu, które samo się naprawiło, wypiera z kolejki te prawdziwe. Rozróżnienie "zepsute" od "odzyskane" jest tańsze niż gaszenie zmęczenia alertami.

Niestabilna ścieżka odczytu w gatewayu to z kolei problem, który wygląda jak tuzin niepowiązanych awarii, dopóki ktoś nie dojdzie do jednego korzenia. Odczyt konfiguracji blokuje diagnostykę i zadania zależne od tego samego pliku, więc nazwanie tego raz oszczędza godziny później.

Czego się nauczyłem:

  • Gołe except potrafi zamienić brakujący import w przekonujący objaw awarii dysku. Widoczność prawdziwego wyjątku jest ważniejsza niż elegancja obsługi błędów.
  • Pusty plik stanu to nie brak danych, tylko aktywna przyczyna awarii. Trzy linie atomowego zapisu usuwają całą tę klasę problemów.
  • Metryka, która łapie zdarzenia już odzyskane, przestaje być metryką, której ktokolwiek ufa. Idempotencja i rozumienie retry to wymóg, nie ozdoba.