Sędzia, który podejmuje decyzje
Tam zbudowaliśmy sędziego. Tu dajemy mu władzę — i patrzymy, co może pójść nie tak.
Kontynuacja artykułu Jak sprawdzić, czy LLM odpowiada dobrze?. Tam zbudowaliśmy sędziego. Tu dajemy mu władzę — i patrzymy, co może pójść nie tak.
W poprzednim tekście zostawiłem Cię z sędzią-LLM, który potrafi porównać dwie odpowiedzi i uzasadnić, która jest lepsza. Brzmi jak koniec historii. Jest początkiem, bo szybko odkryłem coś niewygodnego: sama ocena nie wnosi prawie żadnej wartości.
Wyobraź sobie naiwną, pierwszą wersję takiego systemu. Dwa agenty AI, każdy w osobnym git worktree, niezależnie implementują ten sam task. Sędzia dostaje oba diffy, wystawia oceny: wersja A — 7/10, wersja B — 6/10. Wygrywa A.
Uważny czytelnik poprzedniego tekstu już protestuje: przecież ustaliliśmy, że skala 0/1 bije skalę 1–10! Racja — i to jest pierwszy grzech tego sędziego. Ale jest też drugi, głębszy, i to on jest tematem tego artykułu.
I co z tego, że wygrało A?
Siódemka to nie jest kod, który chcesz mieć na produkcji. To kod, który jest mniej zły od konkurenta. Sędzia porównał dwóch kulawych biegaczy i uroczyście ogłosił, który kuleje mniej. Konkurs się odbył, medal wręczony, a problem — kod nie jest gotowy — stoi nietknięty.
Ocena, za którą nie idzie żadna akcja, jest kosztownym rytuałem. Wartość pojawia się dopiero wtedy, gdy werdykt zaczyna kierować ruchem: ten kod idzie do merge, ten wraca do poprawki, a ten ląduje na biurku człowieka. Sędzia przestaje być jurorem, a staje się routerem.
Najpierw „czy gotowe”, dopiero potem „które lepsze”
Błąd w scenie z siódemką jest strukturalny: zadaliśmy pytanie porównawcze, zanim padło pytanie zerojedynkowe. Naprawiony przepływ ma dwa etapy.
Etap pierwszy: readiness check. Każdy branch osobno przechodzi kontrolę gotowości — nie „jak dobry jest ten kod”, tylko „czy spełnia każde wymaganie z taska”. Binarnie, wymaganie po wymaganiu:
Evaluate whether the implementation satisfies EVERY requirement.
Check each requirement separately.
Task requirements:
{requirements}
Diff:
{diff}
CI results:
{ci_summary}
Return JSON:
{
"requirements_check": [
{ "requirement": "...", "met": true | false }
],
"issues": [
"one concrete, fixable instruction per unmet requirement or defect"
],
"rationale": "1-2 sentences",
"ready": true only if every requirement is met
}
Zwróć uwagę na pole issues. To nie jest ozdobnik — to jest cały sens tego etapu, o czym za chwilę.
Jest tu jedna pułapka, którą odkryłem boleśnie: sędzia proszony o sprawdzenie wymagań ma tendencję do ich dopisywania. Poproszony o weryfikację endpointu potrafi uznać, że „brak rate limitingu” to niespełnione wymaganie — którego nigdy w tasku nie było. Remedium: listę wymagań generuje się raz, przed startem agentów, z opisu taska, i tę samą zamrożoną listę dostają wszyscy — agenty jako specyfikację, sędzia jako checklistę. Sędzia odhacza. Nie wymyśla.
Co przenosi cały ciężar na jakość opisu taska — bo checklista wygenerowana ze śmieciowego opisu będzie odhaczać nie to, co trzeba, tylko szybciej i pewniej. W dobrze napisanym zadaniu ta lista w ogóle nie wymaga generowania: już tam jest i nazywa się kryteriami akceptacji — punktami sprawdzalnymi odpowiedzią tak/nie, spisanymi przez autora taska. O tym, jak pisać zadania, z których taka checklista wyjmuje się sama, mam osobny szablon jak pisać zadania. Napisałem go dla ludzi zlecających mi pracę, zanim jeszcze zaczęły ją brać agenty — i to jest może najciekawsze odkrycie z całej tej zabawy: zadanie napisane tak, żeby człowiek nie musiał zgadywać, jest dokładnie tym samym zadaniem, przy którym nie zgaduje agent.
Etap drugi: porównanie parami — ale tylko między gotowymi. Dopiero gdy branch przechodzi readiness check, trafia do konkursu. Jeśli oba są ready, sędzia porównuje (podwójny przebieg, A/B i B/A — stronniczość pozycji z poprzedniego artykułu nigdzie nie zniknęła). Jeśli tylko jeden — wygrywa walkowerem i porównanie w ogóle się nie odbywa. Jeśli żaden — sprawa idzie do człowieka.
Porównujemy kandydatów do merge’a, nie kandydatów do poprawki. Siódemka kontra szóstka nie ma prawa się wydarzyć.
Reroll to loteria. Feedback to pętla.
No dobrze — a co z branchem, który readiness checku nie przeszedł? Pierwszy odruch: wygenerować od nowa. Może tym razem się uda.
To jest loteria za pełną cenę losu. Nowa generacja od zera nie wie, co było nie tak z poprzednią — startuje z tego samego promptu, z tą samą szansą na te same błędy, a Ty płacisz za cały przebieg jeszcze raz i modlisz się o inny wynik.
Dlatego pole issues z readiness checku jest tak ważne. Sędzia, który zwraca listę konkretnych braków, przestaje być bramkarzem odbijającym piłkę i staje się reviewerem robiącym „request changes”. A agent nie generuje od nowa — dostaje kontynuację swojej własnej sesji z doklejoną instrukcją: „napraw dokładnie te trzy rzeczy, niczego więcej nie ruszaj”. Ma pełen kontekst tego, co już zrobił i dlaczego. Poprawka kosztuje ułamek pełnej generacji i — co ważniejsze — jest zbieżna: celuje w nazwane braki, zamiast losować nowe rozdanie.
Po poprawce nie robimy pełnego review od zera. Re-check sprawdza tylko checklistę. Odhaczone → ready. Pętla, nie ruletka.
„Najlepsza wersja” nie istnieje
I tu dochodzimy do najbardziej podstępnej pułapki całego układu. Skoro mamy pętlę naprawczą, to kusi, żeby kręcić nią do skutku: niech sędzia poprawia, aż będzie naprawdę dobrze. Aż będzie najlepiej.
Problem w tym, że sędzia zawsze znajdzie coś do poprawy. Zawsze. Nazwa zmiennej mogłaby być czytelniejsza. Ten warunek dałoby się uprościć. Przydałby się jeszcze jeden test. Model poproszony o krytykę dostarczy krytykę — to jego praca. „Najlepsza wersja” jako kryterium stopu to przepis na nieskończoną pętlę doskonalenia, w której każdy obrót kosztuje realne pieniądze, a kod asymptotycznie zbliża się do ideału, nigdy nie trafiając na produkcję.
Definicja „done” musi być binarna i zamknięta: wszystkie wymagania z zamrożonej listy spełnione + testy zielone = merge. Koniec. To jest zresztą stara zasada z poprzedniego artykułu — skala 0/1 bije skalę 1–10 — tylko w nowym, droższym wydaniu: tam w niuansach grzązł model, tu grzęźnie cały system, i to za Twoje tokeny.
Do tego dwa twarde bezpieczniki, bo zasady bez egzekucji to pobożne życzenia: maksymalnie dwie rundy naprawcze na branch (potem eskalacja do człowieka, nie trzecia próba) i budżet na task w dolarach, po którego przekroczeniu system failuje głośno. Agent, który utknął, ma się zatrzymać i zapytać — nie brnąć naprzód na założeniach, spalając limit.
Dziennik, czyli zaufanie na piśmie
Został ostatni element, najmniej efektowny i być może najważniejszy. System, który sam ocenia, sam poprawia i sam wybiera, podejmuje w Twoim imieniu kilkanaście decyzji na task. Jeśli nie potrafisz odtworzyć, dlaczego podjął każdą z nich — nie masz podstaw, żeby mu ufać.
Rozwiązanie jest nudne i skuteczne: dziennik zdarzeń, wyłącznie dopisywany (append-only). Każdy krok to jeden wiersz: co się stało, kiedy, kto (który agent, który przebieg sędziego), z jakim werdyktem, uzasadnieniem i kosztem. Readiness check brancha A: not ready, dwa issues. Runda naprawcza pierwsza. Re-check: ready. Pairwise, przebieg 1: wygrywa A. Przebieg 2 (odwrócona kolejność): wygrywa A. Merge.
Z takiego dziennika na końcu generuje się podsumowanie — i tu subtelność: podsumowanie wynika z zapisanych zdarzeń, nie jest pisane przez sędziego „z głowy”. Model streszczający własne decyzje po fakcie potrafi je uładzić. Dziennik nie potrafi.
Bonus, który wraca z nawiązką: każde Twoje nadpisanie werdyktu — „sędzia wybrał A, ja bym wybrał B, bo…” — to gotowy, oznaczony przykład do kalibracji sędziego metodą few-shot z poprzedniego artykułu. System, którego decyzje kwestionujesz, produkuje materiał do własnej poprawy. O ile prowadzi dziennik.
Co dalej
Cały ten tekst jest teorią zbudowaną na małej próbce — system, który opisuję, raczkuje na moich własnych taskach. Zanim dopiszę mu kolejne piętra (kolejka zadań z Trello, panel do rozstrzygania eskalacji, weryfikacja w przeglądarce), przepuszczam przez niego dwadzieścia prawdziwych tasków i przy każdym werdykcie zapisuję własną decyzję: zgadzam się albo nadpisuję.
Ten jeden wskaźnik — zgodność sędziego-routera z człowiekiem — rozstrzygnie, czy warto budować dalej, czy najpierw trzeba kalibrować. W następnej części pokażę liczby: ile werdyktów się broniło, ile tasków wymagało rund naprawczych, ile to wszystko kosztowało per task.
Bo to jest sedno różnicy między jedynką a dwójką tej serii. Tam pytaliśmy, czy model odpowiada dobrze. Tu pytamy, czy można pozwolić modelowi decydować — a na to pytanie nie odpowiada się przekonaniem, tylko dziennikiem i licznikiem nadpisań.
Wyobraź sobie, że masz ocenić pracownika, który jednego dnia pisze wiersz, drugiego rozwiązuje równanie, a trzeciego tłumaczy z japońskiego. Jedną skalą? Powodzenia.
Osiem przebiegów, dwóch agentów, sędzia z podwójnym przejściem i człowiek na końcu. Wszystkie testy zielone. Po drodze: worker kopiujący rozwiązania z sąsiednich zadań, trzy sprzeczne interpretacje jednego zdania specu i licznik decyzji, których nie podjął nikt.