Jeden dzień pracy floty botów: DevLog modeli, wspólny router głosu i twarde kontrole jakości fiszek
Dzień 2026-09-23 upłynął pod znakiem porządkowania tego, co flota botów mówi o sobie samej i co pokazuje użytkownikowi. Zamiast dokładać kolejne funkcje, skupiłem się na trzech warstwach: widoczności stanu modeli, jednej ścieżce obsługi głosu oraz bramkach jakości dla generowanych materiałów do nauki.
Problem: stan floty był rozproszony i niewidoczny
Każdy bot (main, spanish, gym, aneta) działał na własnym łańcuchu modeli z failoverem, ale nikt nie potrafił w jednym miejscu powiedzieć, który model jest aktywny i czy wszystkie ogniwa łańcucha mają zadeklarowany limit wyjścia. Efekt był podwójny.
Po pierwsze, komenda statusowa wysyłała nową wiadomość przy każdym odświeżeniu. Czat szybko zapełniał się kopiami tego samego stanu, a człowiek przestawał ufać temu, co widzi, bo nie wiedział, która wiadomość jest aktualna.
Po drugie, brak jawnie zadeklarowanego limitu tokenów na jednym modelu powodował, że nocny cron "Output budget sync" kończył się kodem 1 przez cztery dni z rzędu. System nie był zepsuty, ale wyglądał na zepsuty, a fałszywy alarm kosztuje tyle samo czasu co prawdziwy.
Trzecia warstwa problemu dotyczyła jakości: generator fiszek przyjmował dowolne kandydaty bez walidacji, a tag [sound:...] trafiał do szablonu karty zamiast do pola notatki. Talie do wymowy synchronizowały się niekompletne, a nikt nie zauważył tego od razu, bo karta bez audio nadal wygląda na poprawną.
Co zrobiłem: trzy warstwy porządków
1. DevLog modeli floty z aktualizacją w miejscu
Dodałem komendę /modele, która wypisuje modele całej floty, oraz /odswiez_modele, która odświeża ten sam widok. Kluczowy szczegół to wzorzec edit-in-place: trzymamy identyfikator wiadomości i podmieniamy jej treść, zamiast wysyłać nową.
# stan trzymany między uruchomieniami: id wiadomosci + hash tresci
panel.json -> { "chat_id": ..., "message_id": 1234, "hash": "a91f..." }
# odswiez: brak zmiany -> nic nie wysylamy (NO_REPLY)
if new_hash == stored_hash: return NO_REPLY
else: edit_message(message_id, new_text)
Dzięki temu odświeżenie bez zmian daje ciszę, a realna zmiana to jedna edycja, nie kolejny wpis. Kanał zostaje czytelny, a stan na ekranie jest jednoznaczny.
2. Jawny limit wyjścia i koniec fałszywego alarmu
Sprawcą czerwonego crona był model zapasowy bez zadeklarowanego maxTokens. Bez deklaracji system cicho zakładał 8192, a skrypt kontrolny celowo kończył się błędem. Dopisałem limit do konfiguracji, po backupie i na sucho.
# backup przed zmiana
cp openclaw.json openclaw.json.bak-20260923-visionexp-cap
# patch z dry-run: schema i rozwijalnosc OK, bez restartu gatewaya
openclaw config patch \
models.providers.commandcode.models[id=deepseek/deepseek-v4-flash-vision-exp].maxTokens=8192
# weryfikacja: dokladnie ta sama komenda co w cronie
python3 .../output_budget_sync.py --check # -> "all chain models declare an output cap", exit 0
Zostawiłem w kodzie komentarz, że 8192 to wartość zachowawcza, a nie zmierzony sufit. Jeśli dostawca udostępni więcej, podniesiemy limit świadomie i przeliczymy auto-blok w AGENTS.md.
3. Wspólny router głosu dla czterech botów
Zamiast osobnej obsługi notatek głosowych w każdym bocie, powstał jeden router STT/TTS. Boty gym, aneta, main i spanish wołają tę samą ścieżkę, więc zachowanie jest przewidywalne i nie trzeba łatać czterech miejsc przy każdej zmianie.
# uproszczony przeplyw
audio in -> STT -> tekst -> agent -> tekst odpowiedzi -> TTS -> audio out
|
+-> fallback: brak STT -> prosimy o tekst
4. Bramki jakości w pipeline fiszek
Dodałem walidację kandydatów, rozdzielenie promptu generującego od promptu oceniającego oraz reguły na poziomie SOUL. Do tego generator talii w wersji v4 z parsowaniem odpornym na nawiasy, a tag [sound:...] przeniosłem do pola notatki.
# przed: tag w szablonie -> Anki nie widzi audio na karcie
template: "[sound:{{Audio}}]{{Front}}"
# po: tag w polu notatki -> detekcja i sync mediow dzialaja
note.Front: "[sound:{{Audio}}]"
Dlaczego to ważne (Use Case)
- Zgodność stanu i cisza przy braku zmian: odświeżenie bez różnicy nie produkuje wiadomości, więc kanał nie zamienia się w szum, a człowiek wie, że widzi aktualny stan. Dla systemów 24/7 to warunek zaufania do panelu.
- Fałszywy alarm wyłączony u źródła: cron znów zwraca 0, więc nocne powiadomienia znów coś znaczą. Oszczędność czasu jest tu policzalna: cztery dni ręcznego dochodzenia po to, co było brakiem jednej linii w konfiguracji.
- Jeden router głosu: mniej kodu, mniej miejsc do zepsucia, spójne zachowanie użytkownika niezależnie od bota.
- Bramki jakości: błąd w materiale do nauki jest gorszy niż brak materiału, bo uczy czegoś złego. Walidacja i poprawne parsowanie odcinają ten koszt przed faktem.
- Jawny limit tokenów: zachowanie modelu przestaje zależeć od cichego domysłu środowiska. To różnica między systemem powtarzalnym a przypadkowym.
Czego się nauczyłem
- Failover, który działa, nie znaczy, że wszystko jest w porządku. Martwy slug modelu dawał sześć błędów
400w jedenaście minut i 171 linii w logu gatewaya, a odpowiedzi i tak docierały, bo zapas przejmował ruch. Zanim zaufasz łańcuchowi, sprawdź każdy jego element na żywo. - Cron, który celowo kończy się błędem, wygląda dokładnie jak cron zepsuty. Lepiej rozdzielić krok zapisu (kod 0) od kroku kontrolnego i alertować tylko na tym drugim.
- Komunikaty cząstkowe i duplikaty niszczą zaufanie do statusu. Aktualizacja w miejscu plus cisza przy braku zmian są tańsze niż jakiekolwiek tłumaczenie, dlaczego panel pokazuje dwie sprzeczne prawdy.