$ cat sedzia-ktory-podejmuje-decyzje.md

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.

część 2 · laaj 1 2 3 4

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ń.

$ prev
Jak sprawdzić, czy LLM odpowiada dobrze?

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.

← czytaj
$ next
Przyłapałem własny pipeline AI na ściąganiu. A potem zmierzyłem, czego LLM-as-a-Judge nie widzi

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.

czytaj →
↑