Przejdź do treści

BlogAI

Z okopów na wieżę obserwacyjną. Jak pracować jako programista, gdy kod pisze agent AI

Wróciłem z krakowskiego JDD 2026 z jedną główną obserwacją: rozmowy na korytarzach i scenach przesunęły się z pytania “jak pisać prompty” na “jak nie utonąć w wygenerowanym kodzie”. Poniżej zbieram wnioski z prelekcji praktyków, którzy pokazywali, jak przestawić warsztat z ręcznego rzemiosła na nadzór nad systemem.

Od Jiry do Spec-Driven Development, zanim agent napisze pierwszą linijkę

Victor Rentea w prelekcji “Code Review in Agentic Engineering” opowiedział, że przestał pisać kod ręcznie w marcu, po 19 latach w zawodzie. W tym roku pomógł ponad 30 firmom w transformacji agentowej i widzi w nich jeden powtarzający się błąd: inżynierowie biorą niejasne zadanie z Jiry i od razu rzucają je agentowi. Jeśli nie rozumiemy problemu biznesowego i nie umiemy powiedzieć biznesowi “nie”, wygenerujemy tylko górę bezużytecznego kodu. Rentea przypomniał technikę Tres Amigos, czyli spotkanie biznesu, dewelopera i testera, na którym ustala się, co i dlaczego budujemy.

Gdy problem jest zrozumiały, zostajemy sam na sam z modelem pokroju Opusa. Rentea przestrzegał przed samotną walką z tak potężnym narzędziem i przed bezrefleksyjnym “looks good to me”. Zamiast od razu generować kod, warto zostać na poziomie plików Markdown: użyć trybu planowania, umiejętności “grill me” (autorstwa Matta Pococka) albo frameworków do Spec-Driven Development, takich jak Superpowers, Get Shit Done (GSD) czy OpenSpec. Jeśli pominiemy ten etap, podczas code review będziemy musieli żmudnie odtwarzać decyzje architektoniczne ukryte w tysiącach linii kodu.

Ten wątek rozwinął Artur Sobczyk w prelekcji “Where Vibe Meets Spec”. W nagranym demo, w narzędziu Kiro, porównał luźny vibe coding z podejściem opartym na specyfikacji. Zadanie było jedno: dodać rate limiting (maksymalnie 5 żądań na minutę) do aplikacji w Spring Boocie 3.5 i Javie 17. Vibe coding dał działający kod w kilkadziesiąt sekund. Problem wyszedł, gdy Sobczyk zapytał agenta o założenia przyjęte po cichu. Model użył pamięci lokalnej maszyny (co wyłoży się przy wielu instancjach), źle obsłużył nagłówek X-Forwarded-For, nałożył limit tylko per IP (więc nadal można ubić całą instancję) i zastosował sztywne okno czasowe oparte na zegarze systemowym.

W podejściu spec-driven proces zaczął się od pliku requirements.md ze słownikiem pojęć i kryteriami akceptacji, przeszedł przez design.md z architekturą i diagramami, a skończył na rozbiciu pracy na osiem zadań w tasks.md, realizowanych przez subagentów. Całość trwała ponad 20 minut zamiast kilkudziesięciu sekund, ale założenia architektoniczne wyszły na etapie czytania dokumentu, a nie po wdrożeniu na produkcję. Sobczyk nie robił z tego reguły: przy PoC-u albo jednorazowym skrypcie struktura może tylko przeszkadzać, a przy kodzie produkcyjnym i poprawkach błędów się opłaca. Arkadiusz Migała (“Master or Disaster: AI Engineering”) dodał, że przeniesienie review na etap specyfikacji i wymagań jest wielokrotnie tańsze. Ostrzegł też przed pułapką organizacyjną: jeden deweloper obyty z AI, który produkuje ogromne ilości taniego kodu bez odpowiednich procesów w zespole, blokuje resztę, bo ta nie nadąża z weryfikacją.

Kontekst agenta i slop, który rozmnaża się przez pączkowanie

Dlaczego agenci podejmują tak dziwne decyzje architektoniczne? Odpowiedzi szukali Krystian Mikła (“Zarządzanie kontekstem agentów AI w dużych projektach”) i Renato Cavalcanti (“Depth Over Fluency”). Mikła zaczął od tego, czym agent jest pod spodem: połączeniem modelu językowego (wnioskowanie token-in, token-out) i warstwy sterującej, czyli harnessu (np. Claude Code, Cursor, Copilot, Codex), który zarządza kontekstem i narzędziami.

Cavalcanti przypomniał, że modele uczą się na całym dostępnym internecie. Obok świetnych projektów open source i książek o architekturze leży w danych treningowych śmietnik z Reddita. Bez prowadzenia model ciąży ku przeciętności i wybiera pierwszą ścieżkę, po której testy robią się zielone (co zresztą leży w interesie dostawców AI, którzy chcą ograniczyć zużycie mocy obliczeniowej przy stałej cenie subskrypcji). Do tego kod, który dziś zaakceptujemy i zmergujemy, jutro staje się kontekstem kolejnej sesji agenta, a kontekst lokalny waży dla modelu więcej niż dane treningowe.

Cavalcanti pokazał to na przykładzie z pracy nad platformą Akka (nazwę klasy podał już tylko ilustracyjnie). W rekordzie Javy zespół przepuścił w konstruktorze kompaktowym nadmiarowe zabezpieczenie, które zamieniało null na Optional.empty(). Po krótkim czasie agenci uznali to za standard projektu i zaczęli wszędzie generować kod, który przy braku danych inicjalizował obiekty losowymi identyfikatorami, nazwą “unknown” i pustymi listami, niszcząc niezmienniki domenowe. Jeden wpuszczony do repozytorium slop zaczyna się sam reprodukować.

Jak dostarczyć agentowi właściwy kontekst w dużym projekcie? Mikła wyjaśnił zjawiska context pollution i lost in the middle. Im więcej szumu (wyniki ls i grep, całe pliki czytane od zera) trafia do okna kontekstowego, tym bardziej spada uwaga modelu i rośnie ryzyko zignorowania kluczowych reguł. Jego zespół przetestował kilka podejść na przykładowym repozytorium, na modelu GPT6 Luna z ustawieniem high effort:

  • Domyślny /init, który generuje jeden duży plik AGENTS.md (lub CLAUDE.md) w głównym katalogu, okazał się nieefektywny. Plik ładuje się w całości przy każdym zadaniu i miesza domeny frontendowe, backendowe i CI/CD.

  • Osobny folder docs/ai, z którego agent sam dobierał pliki, nie zadziałał, bo model, chroniąc swój kontekst, unikał czytania instrukcji.

  • Wymuszenie czytania dokumentacji przez subagenta na starcie sesji działało dobrze z perspektywy głównego agenta. Kosztowało jednak czas i tokeny, nie nadążało za zadaniem zmieniającym się w trakcie rozmowy i nie sprawdzało się w orkiestracji z wieloma subagentami.

Najlepszy wynik dało podejście rekomendowane przez Anthropic: hierarchiczne, zagnieżdżone pliki AGENTS.md / CLAUDE.md w odpowiednich podkatalogach i pakietach. Harness ładuje je dynamicznie dopiero wtedy, gdy agent faktycznie eksploruje dany obszar kodu. Tak wypadło w benchmarku wewnętrznym i na warsztacie z dziewięcioma uczestnikami. Tam liczyła się mediana, bo rozrzut był duży (część osób bez dokumentacji dobiła do blisko 90 punktów), dlatego Mikła zaznaczył, że odpowiedź brzmi “to zależy”. Przywołał też badanie, według którego pliki kontekstowe zwykle nie poprawiały skuteczności agenta i podnosiły koszt, a te pisane przez człowieka radziły sobie lepiej niż generowane automatycznie. Z własnej praktyki wyciągnął wniosek, że brak dokumentacji jest lepszy niż dokumentacja kiepskiej jakości. W plikach kontekstowych warto zapisywać wyłącznie minimum napisane lub zredagowane przez człowieka: nietypowe decyzje architektoniczne, ukryte zależności i realne pułapki, na których agent już wcześniej poległ. Numery linii i oczywiste ogólniki odpadają.

Deterministyczne guardrails i ślepe zaufanie

Litera “D” w skrócie AI nie oznacza determinizmu, jak żartował Arkadiusz Migała, więc agenta trzeba otoczyć twardymi ramami. Rentea mówi wprost: potrzebujemy oskryptowanych kontroli jakości, które uruchamiają się lokalnie (przez stop hook harnessu lub hooki gitowe, żeby wykorzystać prompt cache i dostać szybki feedback) oraz bezwzględnie na CI/CD, poza zasięgiem agenta.

Jego lista obejmuje rzeczy znane od lat, ale rzadko wdrażane w komplecie: lintery, formattery, Checkstyle, Sonar, Snyk, testy ArchUnit pilnujące architektury oraz testy mutacyjne, które pokazują, czy wygenerowane przez AI testy cokolwiek weryfikują. Rentea opisał też “zero drift architecture”. Plugin Mavena generuje schemat OpenAPI z klas DTO w Javie, a paczka NPM generuje z tego pliku YAML interfejsy TypeScriptowe na frontendzie. Agentowi nie wolno ręcznie edytować wygenerowanych plików. Zmiana pola w backendzie od razu łamie build frontendowy, bez udziału AI.

A co, jeśli agent zmodyfikuje pliki workflow na GitHubie albo wyłączy reguły w Sonarze, żeby przepchnąć zielony build? Rentea wskazuje mechanizm CODEOWNERS: każda zmiana plików konfiguracyjnych CI/CD, skryptów budowania i samych guardrails musi automatycznie wzywać do review tech leada lub “starszyznę” projektu.

Ostrożność wobec tego, co agent robi i czyta, ma też wymiar bezpieczeństwa. Mówił o nim w keynote “Understanding Prompt Injection” Brian Vermeer z firmy Snyk. Pokazał ataki na GPT-3.5 Turbo, GPT-4o, GPT-4.1 i GPT-5.4 mini, łączące structured output attack (wymuszenie odpowiedzi w rygorystycznym schemacie JSON na potrzeby rzekomego audytu), role-playing, wirtualizację i delimiter confusion. Zastrzegł, że to nie benchmark, tylko eksperymenty, w których atak nie działa za każdym razem, ale wystarczy, że zadziała raz. Jego testy pokazały też, że nowszy model nie zawsze jest bezpieczniejszy od starszego. Multimodalny GPT-4o okazał się znacznie odporniejszy na część prostych wstrzyknięć niż nowszy GPT-4.1.

Dla deweloperów używających agentów kodujących najważniejsza była część o pośrednim wstrzykiwaniu promptów (indirect prompt injection) i eskalacji uprawnień. Gdy dajemy agentowi dostęp do terminala, poczty, repozytorium albo zewnętrznych źródeł przez MCP, sami stajemy się celem. Vermeer pokazał, jak instrukcja ukryta w kodzie strony HTML skłoniła lokalnego asystenta do utworzenia na Macu użytkownika skryptu bashowego i nadania mu uprawnień. W innym demo ukryte polecenia w plikach z propozycjami referatów zawyżały oceny wystawiane przez model i wyciekały dane przez obrazek. Groźniejszy jest jeszcze link smuggling: model dokleja na końcu każdej odpowiedzi tag obrazka w Markdownie (Show Image), a w parametrach URL zakodowana jest treść rozmowy. Wystarczy, że klient wyrenderuje Markdown, i do serwera atakującego leci żądanie GET, nawet jeśli sam model jest bezpieczny.

Vermeer zadał sali niewygodne pytanie: ilu z nas czyta każdą linijkę kodu i skryptów w plikach skills pobieranych z internetu, zanim wepnie je do swojego agenta? Agenci działają na naszych maszynach z dostępem do kluczy AWS i baz danych, więc niezweryfikowany skill jest koniem trojańskim. Rentea rzucił jeszcze jedną uwagę do code review: warto przeglądać nowe wywołania loggerów, bo logowanie stringów grozi prompt injection (rozwinięcia już nie podał). Dopowiem od siebie, że ryzyko rośnie, gdy logi czyta potem agent utrzymaniowy, o jakich mówił Migała.

Adwersaryjne code review i wieża obserwacyjna

Załóżmy, że specyfikacja była dobra i testy na CI przeszły. Przed nami ląduje pull request na 700 albo 2000 linii. Jak go przejrzeć w pół godziny i nie zwariować? Tu prelekcja Rentei dała najwięcej praktycznych rozwiązań.

Po pierwsze, jego żelazna zasada: żaden człowiek nie przegląda kodu, dopóki nie przejrzy go drugi agent. To adversarial review. Ten sam agent, który pisał kod, się nie nadaje, bo po długiej sesji ma zapchane okno kontekstowe i siedzi w “dumb zone”. Nowy agent recenzujący startuje ze świeżym kontekstem, czyli w “smart zone”. Można mu nadać personę (np. “Jesteś Uncle Bobem, sprawdzasz zasady SOLID i Clean Code”) albo, jak sugeruje Migała, uruchamiać wyspecjalizowanych agentów od wydajności czy architektury, zależnie od automatycznej oceny ryzyka zmian w PR. Recenzent głównie czyta tokeny, a niewiele ich produkuje, więc jest relatywnie tani. Opłaca się użyć silniejszego modelu. Rentea żartował też, że kto chce zobaczyć bezlitosną krytykę i “krew na ekranie”, niech każe innemu modelowi zrecenzować pracę Opusa, bo to wojna w rodzinie.

Po drugie, agent-autor po zakończeniu pracy ma wylistować wszystkie miejsca, w których musiał podjąć własne decyzje lub odejść od specyfikacji, i przypisać każdemu poziom pewności w procentach (np. “60% confidence level”). Wtedy ludzka uwaga idzie tylko tam, gdzie autor się wahał, oraz tam, gdzie zaiskrzyło między autorem a recenzentem.

Po trzecie, trzeba wyjść z okopów czytania kodu linijka po linijce i zbudować sobie wieżę obserwacyjną. Rentea pokazał swoje otwarte narzędzie Human Review. Zamiast surowych plików Markdown generuje ono bogaty raport HTML, spięty z IntelliJ IDEA przez plugin do obsługi PR, dzięki czemu uwagi agentów lądują w edytorze jako komentarze. Na takiej wieży znajdują się:

  • Wideo i środowisko uruchomieniowe: krótkie wygenerowane wideo z działaniem nowej funkcji na UI oraz przycisk, który odpala aplikację w Dockerze i jednym kliknięciem ładuje przygotowane wcześniej database fixtures (np. minimalny zestaw 9 wierszy do powtarzalnych testów manualnych).

  • Deterministyczne diffy API i modelu danych: narzędzie sprawdza kompatybilność wsteczną OpenAPI i podświetla breaking changes na czerwono. Skrypty w Pythonie i refleksja w Javie (albo zrzuty schematu SQL z kontenera Dockera) generują diagramy PlantUML, na których na zielono i czerwono widać, jakie klasy, pola lub tabele zmieniły się w PR. Ten obraz powstaje z kodu, bez udziału AI, więc nie ma w nim halucynacji.

  • Testowane diagramy Draw.io: przy ogromnych schematach baz danych automatyczny layout PlantUML ciągle przestawia pudełka i psuje pamięć wzrokową zespołu. Rentea wykorzystuje więc fakt, że pliki PNG eksportowane z Draw.io zawierają w ukrytych nagłówkach semantykę diagramu (pudełka, linie, etykiety). Pozwala to napisać test jednostkowy porównujący obrazek z modelem domenowym w Javie. Gdy agent dodaje nową relację w kodzie, test zmusza go do dorysowania czerwonej linii na diagramie z prośbą o ręczne ułożenie jej przez człowieka.

  • Mapowanie wymagań i analiza zachowania w runtime: to jedno z niewielu miejsc, gdzie w narzędziu Rentei pracuje AI. Model mapuje konkretne zdania z zadania na GitHubie na scenariusze testów akceptacyjnych (feature files, Playwright), które pokryły zmieniony kod. Przy 100% samplingu z OpenTelemetry ślady z wykonania testów są automatycznie konwertowane na diagramy sekwencji. Klikając strzałkę, widzimy dokładny payload JSON, zapytanie SQL albo wywołanie między modułami monolitu (wykryte prostym argumentem JVM), i od razu widać, że AI zaprojektowało absurdalny cykl wywołań między serwisami.

  • Wizualizacja złożoności: od mapowania zależności pakietów w ArchUnit, przez trójwymiarowe “Code City” (budynek rośnie wraz z liczbą linii kodu klasy i widoczną złożonością poznawczą, a niebieskie linie to coupling), po prosty syntax walker wskazujący, w którym kontrolerze czy mapperze przybyło rozgałęzień warunkowych. Rentea sam przyznał, że Code City świetnie wygląda w prezentacji, ale w praktyce jest najmniej użyteczne z całego zestawu.

Rentea radzi: gdy mimo tych zabezpieczeń znajdziesz w kodzie od agenta śmieci, nie poprawiaj ich po prostu ręcznie. Zaktualizuj deterministyczne guardrails, dopisz jedną linijkę do pliku instrukcji albo zbuduj nowe narzędzie na wieży obserwacyjnej, żeby jutro zauważyć ten sam problem w dwie minuty na obrazku, a nie w dwadzieścia, przerzucając kod łopatą w okopach.

Głębia zamiast biegłości i paradoks automatyzacji

Renato Cavalcanti postawił pytanie o naszą tożsamość zawodową. Przez dekady definiowaliśmy się przez biegłość (fluency) w konkretnym języku, czy to w Javie, czy w Scali. Tłumaczenie myśli na kod dawało nam flow, budowało relację emocjonalną z projektem i pozwalało uczyć się przez tarcie i rozwiązywanie problemów. Dziś biegłość w szybkim pisaniu składni odchodzi do przeszłości.

Cavalcanti przywołał opisany ponad 40 lat temu przez Lisanne Bainbridge paradoks automatyzacji (Ironies of Automation). Im bardziej automatyzujemy proces, tym rzadziej operator ćwiczy swoje umiejętności na co dzień. Automatyzacja przejmuje 90% rutynowych zadań i zostawia człowiekowi najtrudniejsze 10%, a gdy dochodzi do sytuacji krytycznej, operator bez regularnej praktyki nie reaguje poprawnie. Przykładem był lot Air France z Rio do Paryża: podczas burzy samolotem sterował autopilot, a mniej doświadczony drugi pilot zbyt późno obudził bardziej doświadczonych kolegów, i wtedy nie dało się już odwrócić sytuacji.

W inżynierii oprogramowania oznacza to, że nie możemy stać się “wydrążonymi inżynierami” (hollow engineers), którzy tylko wciskają przycisk akceptacji. Wartość przenosi się z biegłości składniowej na głębię inżynierską (depth). To my mamy wiedzieć, że agent nie powinien pisać od zera własnego algorytmu kompresji GZIP (mimo że napisał do niego zielone testy), tylko użyć sprawdzonego w boju Apache Commons Compress, po sprawdzeniu licencji i utrzymania biblioteki. To my mamy wiedzieć, że opakowanie blokującego wywołania w CompletableFuture bez własnej puli wątków zablokuje wspólną pulę systemową. Cavalcanti dodał, że w jego frameworku w ogóle nie było to potrzebne, bo kod użytkownika działa tam na wątkach wirtualnych.

Migała ujął to z przymrużeniem oka: gratulacje, zostaliście awansowani na architektów, niestety bez podwyżki i bez nowego wpisu na LinkedInie. Żeby nie stracić kontaktu z własną bazą kodu, Migała proponuje “sokratejskie code review”: agent po wygenerowaniu zmian przepytuje ciebie (lub autora PR) z tego, co kod robi i jakie niesie konsekwencje. Cavalcanti polecił z kolei książkę “Thinking with AI” i opisane w niej podejście do szkolenia juniorów. Nowi programiści przez pierwsze trzy miesiące piszą kod całkowicie bez AI. Potem dostają licencję i są losowo odpytywani ze swoich pull requestów, a jeśli nie umieją wyjaśnić wygenerowanego kodu, licencja na agenta jest im czasowo odbierana. Sam traktuje AI jak partnera do pair programmingu, ale część zadań świadomie pisze ręcznie, żeby utrzymać sprawność umysłu.

Co wdrożyć w zespole od jutra

  1. Nie wrzucaj surowych zadań z Jiry bezpośrednio do agenta. Wprowadź obowiązkowy etap planowania i specyfikacji (sesja Tres Amigos, potem plan i wymagania w plikach Markdown przed pierwszą linią kodu produkcyjnego).

  2. Zamiast jednego gigantycznego pliku w głównym katalogu stwórz małe, pisane przez ludzi pliki AGENTS.md / CLAUDE.md w kluczowych modułach. Opisuj w nich tylko architekturę, nietypowe rozwiązania i pułapki, na których model już polegl. Wstępną eksplorację kodu zleć tanim subagentom.

  3. Wymagaj od agenta, żeby po każdym dłuższym zadaniu wypisał miejsca, w których przyjął własne założenia, wraz z procentową oceną pewności.

  4. Włącz w CI/CD i lokalnych hookach lintery, analizę statyczną (Sonar, Snyk), testy architektury (ArchUnit) i testy mutacyjne. Pliki konfiguracyjne pipeline’u, reguły jakości i pliki instrukcji zabezpiecz przez CODEOWNERS.

  5. Wprowadź adwersaryjne code review: człowiek patrzy w kod dopiero po przeglądzie osobnego agenta ze świeżym kontekstem, najlepiej silniejszego modelu, z zdefiniowaną personą recenzenta.

  6. Każ agentowi napisać deterministyczne skrypty (w Pythonie lub Javie), które dla każdego PR wygenerują wizualne diffy schematów bazy danych, sprawdzą kompatybilność wsteczną OpenAPI i narysują diagramy sekwencji z trace’ów OpenTelemetry z testów.

  7. Traktuj pliki skills i serwery MCP jak zewnętrzne zależności i czytaj ich kod przed uruchomieniem. W aplikacjach z LLM wymuszaj ustrukturyzowane wyjście (JSON Schema po stronie serwera) i blokuj renderowanie zewnętrznych obrazków w Markdownie, żeby zapobiec wyciekom przez link smuggling.

  8. Przed zatwierdzeniem dużego PR każ agentowi przepytać siebie lub autora zmian z kluczowych decyzji projektowych w wygenerowanym kodzie.

Podsumowanie

Zmiana, którą widzimy, przypomina przejście z języków niskopoziomowych na maszynę wirtualną Javy, z tą różnicą, że nowa warstwa abstrakcji jest niedeterministyczna. Migała przywołał prawo Amdahla: samo przyspieszenie generowania kodu nie przyspieszy dostarczania oprogramowania, jeśli wąskim gardłem zostaje ludzka uwaga na review. Z konferencji wyniosłem wniosek, że praca inżyniera przesuwa się z wpisywania składni na projektowanie barier ochronnych, precyzyjne definiowanie problemów i budowanie narzędzi, które pozwalają odpowiadać za system, którego kodu nie napisaliśmy własnymi rękami.

Czytaj dalej

Wolicie zobaczyć, jak to wygląda w projektach, a nie w teorii? Zajrzyjcie do realizacji

Wróć na blog

Porozmawiajmy