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





















