Pętla jako miejsce kontroli

przez Łukasz | sie 24, 2026

Model nie ma rąk. Nie wykonuje niczego, nie sięga nigdzie, nie zmienia stanu świata. Może wyłącznie zaproponowaćwywołanie — a między propozycją a skutkiem stoi kod, który napisałeś.

To brzmi jak ograniczenie modelu. Jest twoją jedyną przewagą: skoro wszystko przechodzi przez pętlę, wszystko da się w niej zatrzymać.

Cztery bramki w jednym obrocie

W każdym obrocie są dokładnie cztery momenty, w których pętla może powiedzieć „nie”. Kolejność jest istotna, bo każdy kolejny jest droższy od poprzedniego.

Bramka pierwsza: co wpada do kontekstu. Treść pobrana ze strony, wiadomość przychodząca, dokument z zewnątrz. To dane, nie polecenia — nawet jeśli zawierają zdania sformułowane jak polecenia. Filtrowanie na tym poziomie jest najtańsze, bo nie zdążyło jeszcze nic kosztować.

Bramka druga: czy to narzędzie w ogóle jest dostępne. Zanim sprawdzisz argumenty, sprawdź, czy narzędzie powinno być widoczne w tym kroku, dla tego użytkownika, w tym kontekście. Najprostsza forma to nieudostępnianie go modelowi w ogóle — narzędzie, którego nie ma w katalogu, nie zostanie wywołane.

Bramka trzecia: argumenty. Tu dzieje się właściwa walidacja i ma trzy poziomy, o których za chwilę.

Bramka czwarta: samo wykonanie. Potwierdzenie przy operacjach nieodwracalnych, zapis śladu przed działaniem, obsługa idempotencji.

Trzy poziomy walidacji argumentów

Format. Czy typy się zgadzają, czy pola wymagane są obecne, czy wartości mają dopuszczalny kształt. To wyłapuje halucynację strukturalną — zmyślone pole, parametr w złym formacie. Sprawdzenie jest darmowe i powinno być pierwsze.

Istnienie. Czy wskazany zasób naprawdę jest. Numer zamówienia wygenerowany przez model wygląda dokładnie tak samo wiarygodnie jak prawdziwy — a sprawdzenie w źródle prawdy jest najskuteczniejszym pojedynczym zabezpieczeniem przed halucynacją w całym stosie.

Uprawnienia i granice. Czy ten agent, w tym kontekście, ma prawo wykonać tę operację na tym zasobie. I czy mieści się ona w limitach — kwotowych, ilościowych, czasowych.

Uprawnienia agenta to nie uprawnienia użytkownika

Tu jest rozróżnienie, które w kodzie widuje się rzadko, a rozstrzyga o klasie zagrożenia.

Agent działa „w imieniu” użytkownika, więc naturalnym odruchem jest danie mu uprawnień tego użytkownika. To jednak oznacza, że każda treść, którą agent przetwarza, dostaje pośredni dostęp do wszystkiego, do czego dostęp ma człowiek.

Agent powinien mieć podzbiór uprawnień użytkownika, ograniczony do zadania — i nigdy więcej. Agent czytający pocztę, żeby ją streścić, nie potrzebuje prawa wysyłania. Agent sprawdzający status zamówienia nie potrzebuje prawa jego anulowania.

Bez tego rozdzielenia powstaje układ, w którym atakujący nie musi zdobywać niczyich poświadczeń — wystarczy, że wyśle treść, którą agent przeczyta. To jest ta sama mechanika, którą opisywaliśmy w artykule o zatrutym kontekście, tylko widziana od strony uprawnień.

Dlaczego prompt nie jest bramką

Instrukcja „nigdy nie wysyłaj maili poza domenę firmy” umieszczona w prompcie systemowym przesuwa rozkład prawdopodobieństwa. Nie gwarantuje niczego.

Model może ją zignorować, źle zrozumieć albo zostać do jej złamania nakłonionym treścią pobraną z zewnątrz. Kod sprawdzający domenę adresata nie podlega perswazji. Ta różnica jest całą treścią pojęcia bariery ochronnej — i jedynym powodem, dla którego agentom można cokolwiek powierzyć.

Prompt jest dobry do kształtowania zachowania w przypadkach typowych. Bramka jest do przypadków, w których zachowanie zawiodło.

Odrzucenie musi wrócić do modelu

Zablokowanie wywołania to połowa roboty. Druga połowa to powiedzenie modelowi, co się stało.

Ciche odrzucenie wygląda z perspektywy modelu tak, jakby nic się nie wydarzyło — więc spróbuje ponownie, i jeszcze raz, aż do wyczerpania limitu. To jest dokładnie ten mechanizm, który w drugim artykule prowadził do zapętlenia.

Komunikat powinien mówić, co zostało odrzucone i dlaczego, ale bez ujawniania szczegółów samego mechanizmu kontroli. „Adres poza dozwoloną domeną — wysyłka wymaga zatwierdzenia przez człowieka” jest dobre. Wypisanie całej listy dozwolonych domen — nie.

Limit operacji nieodwracalnych na przebieg

Zabezpieczenie proste, rzadko spotykane i nieproporcjonalnie skuteczne.

Poza limitem kroków i budżetu warto ustawić limit operacji ze skutkiem ubocznym w jednym przebiegu. Agent obsługujący zgłoszenie ma wysłać najwyżej jedną odpowiedź. Agent porządkujący dane ma prawo usunąć najwyżej dziesięć rekordów, zanim zapyta.

Powód jest taki, że wykolejenia rzadko polegają na wykonaniu jednej złej operacji. Polegają na wykonaniu tej samej operacji sto razy — a licznik operacji nieodwracalnych zatrzymuje to na drugiej, niezależnie od tego, co model uważa za sensowne.

Co logować

Dzienniki agentów zwykle zapisują, co zostało wykonane. Warto zapisywać także to, co zostało odrzucone i dlaczego.

Odrzucenia są najlepszym dostępnym sygnałem diagnostycznym. Rosnąca liczba odrzuceń tego samego typu oznacza jedną z trzech rzeczy: definicja narzędzia jest myląca, walidacja jest zbyt ostra, albo ktoś próbuje czegoś, czego nie powinien. Wszystkie trzy warto wiedzieć wcześnie.

Jak to sprawdzić u siebie

Jedna funkcja kontrolna z czterema poziomami, zwracająca decyzję razem z powodem.

python
import json, re

DOZWOLONE_DOMENY = {"firma.pl", "klient.pl"}
ZNANE_ZAMOWIENIA = {"ZAM-4471", "ZAM-1234"}
LIMIT_NIEODWRACALNYCH = 1

stan = {"nieodwracalne": 0, "odrzucenia": []}

NARZEDZIA_META = {
    "status_zamowienia": {"skutek": False, "role": {"czyta"}},
    "wyslij_mail":       {"skutek": True,  "role": {"pisze"}},
}

def sprawdz(nazwa, argumenty, role_agenta):
    meta = NARZEDZIA_META.get(nazwa)
    if not meta:
        return False, f"Narzędzie {nazwa} nie istnieje."

    # poziom 2 — czy w ogóle wolno
    if not meta["role"] & role_agenta:
        return False, f"Brak uprawnień do {nazwa}. Wybierz inną drogę."

    # poziom 3a — format
    if nazwa == "wyslij_mail":
        adres = argumenty.get("adres", "")
        if not re.match(r"[^@]+@[^@]+\.[^@]+", adres):
            return False, "Adres e-mail ma niepoprawny format."
        if adres.split("@")[1] not in DOZWOLONE_DOMENY:
            return False, ("Adresat poza dozwoloną domeną. "
                           "Wysyłka wymaga zatwierdzenia przez człowieka.")

    # poziom 3b — istnienie
    if nazwa == "status_zamowienia":
        numer = argumenty.get("numer_zamowienia")
        if numer not in ZNANE_ZAMOWIENIA:
            return False, (f"Zamówienie {numer} nie istnieje w bazie. "
                           f"Zweryfikuj numer, nie ponawiaj z tym samym.")

    # poziom 4 — limit operacji nieodwracalnych
    if meta["skutek"] and stan["nieodwracalne"] >= LIMIT_NIEODWRACALNYCH:
        return False, "Limit operacji nieodwracalnych w tym przebiegu wyczerpany."

    return True, None

proby = [
    ("status_zamowienia", {"numer_zamowienia": "ZAM-4471"}, {"czyta"}),
    ("status_zamowienia", {"numer_zamowienia": "ZAM-9999"}, {"czyta"}),
    ("wyslij_mail", {"adres": "kontakt@firma.pl"},          {"czyta", "pisze"}),
    ("wyslij_mail", {"adres": "ktos@obcy.example"},         {"czyta", "pisze"}),
    ("wyslij_mail", {"adres": "kontakt@klient.pl"},         {"czyta", "pisze"}),
    ("wyslij_mail", {"adres": "kontakt@firma.pl"},          {"czyta"}),
]

for nazwa, arg, role in proby:
    ok, powod = sprawdz(nazwa, arg, role)
    if ok and NARZEDZIA_META[nazwa]["skutek"]:
        stan["nieodwracalne"] += 1
    if not ok:
        stan["odrzucenia"].append((nazwa, powod))
    print(f"{'OK ' if ok else 'NIE'}  {nazwa:18} {json.dumps(arg, ensure_ascii=False)}")
    if powod:
        print(f"      → {powod}")

print(f"\nodrzuceń: {len(stan['odrzucenia'])}")

Zwróć uwagę na trzecie i piąte wywołanie: oba mają poprawny adres w dozwolonej domenie, ale drugie zostaje zablokowane, bo limit operacji nieodwracalnych już się wyczerpał. To zabezpieczenie działa niezależnie od tego, jak sensowna wydaje się prośba.

Ostatnie wywołanie pokazuje z kolei bramkę uprawnień: to samo narzędzie, ten sam poprawny adres, ale agent w roli tylko odczytującej.

Ćwiczenie: podłącz sprawdz do pętli z pierwszego artykułu i zwracaj powód odrzucenia modelowi jako wynik narzędzia. Potem spróbuj bez zwracania powodu — zobaczysz, jak agent zaczyna powtarzać to samo wywołanie aż do wyczerpania limitu kroków.

Co dalej

Cała ta seria opisywała jedną pętlę. Zostaje pytanie, kiedy warto ją rozbić na kilka — bo architektury wieloagentowe rozwiązują realne problemy i tworzą przy tym nowe, o których mówi się znacznie rzadziej.

O tym jest artykuł dziewiąty: Jeden agent czy kilku.

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

Jeden agent czy kilku

Jeden agent czy kilku?

Gdy agent robi się skomplikowany, podział na kilku wyspecjalizowanych wydaje się naturalnym następnym krokiem. Każdy robi swoje, całość jest czytelniejsza. Czasem tak jest. Częściej okazuje się, że problem, który miał rozwiązać podział, dało się rozwiązać taniej — a w...

Stan poza kontekstem

Stan poza kontekstem

W pierwszym artykule padło zdanie, że agent nie kontynuuje pracy — zaczyna od zera z coraz dłuższą notatką. Notatką jest kontekst, a jego rosnąca długość to główny składnik rachunku. Pytanie, które z tego wynika, brzmi: co właściwie musi być w tej notatce, a co...

Kiedy narzędzie zawodzi

Kiedy narzędzie zawodzi

Wszystkie dotychczasowe przykłady zakładały, że narzędzie działa. W produkcji to założenie jest fałszywe częściej, niż się zakłada przy projektowaniu: API mają awarie, limity, przeciążenia i czasy oczekiwania. To, co pętla zrobi w takim momencie — i co powie o tym...

Ile narzędzi to za dużo

Ile narzędzi to za dużo

Dodanie narzędzia wygląda na czynność addytywną. Agent umie o jedną rzecz więcej, reszta zostaje jak była. Nie zostaje. Każde kolejne narzędzie pogarsza wybór wszystkich pozostałych i podnosi cenę każdego obrotu pętli — a oba efekty są niewidoczne, dopóki ktoś ich nie...

Anatomia definicji narzędzia

Anatomia definicji narzędzia

Kiedy agent wybiera niewłaściwe narzędzie, pierwszym odruchem jest poprawianie promptu systemowego. Drugim — zmiana modelu na mocniejszy. Zwykle problem leży gdzie indziej i naprawia się go bez dotykania jednego i drugiego. Model nie widzi kodu To warto powiedzieć...