Zapisanie pętli, którymi kieruje się agent sztucznej inteligencji w aplikacji mobilnej.
Zapisywanie pętli, które uruchamiają się w aplikacjach mobilnych za pomocą agentów AI: cztery pytania, na które każda pętla musi odpowiedzieć, sześć pętli nawarstwionych od kliknięcia do przetwarzania danych oraz sprawdzenia, które niczego nie wykrywają.
Powiedz agentowi tylko, aby sprawdził logowanie i je przejął – on sam w tym momencie zaprojektuje pętlę. Zazwyczaj następuje jedno z dwóch scenariuszy: próbuje ponownie, aż wszystko stanie się „zielone”, włączając testy sprawdzające teraz mniej kryteriów niż wcześniej. Albo czyta swój własny rejestr, nie znajduje błędów i ogłasza zakończenie zadania. Obie te zachowania stanowią pętle, a w obu przypadkach model wybiera projekt na podstawie tego, co akurat przeczytał.
Inżynieria pętli eliminuje tę możliwość wyboru projektu przez model. Osoba decyduje z góry, na piśmie, co ma działać, co ma być czytane w celu podjęcia decyzji, co ma to zakończyć oraz kiedy należy przestać i poprosić człowieka o pomoc. W tym przypadku jest to zastosowane do aplikacji mobilnej.
Ustawienia
Przykład to aplikacja do przesyłania wiadomości głosowych dla Androida i iOS, stworzona w dużej mierze przy użyciu Claude Code. Każda sesja ma oddzielne wyjście z repozytorium Git, nazywane worktree. W jego ramach agent buduje aplikację, instaluje ją na emulatorze, obsługuje interfejs użytkownika za pomocą Maestro – narzędzia do wykonywania testów w formacie YAML, które odczytuje drzewo dostępności ekranu (elementy oznaczone, które system operacyjny udostępnia) – i zapisuje swoje prace. Nic w pętlach nie zależy od tej konkretnej aplikacji. Wystarcza agent, który potrafi działać, a następnie obserwować.
Harness to zestaw narzędzi, plików, urządzeń i reguł, które określają, co agent widzi i robi. Pętle dotyczą zupełnie innego aspektu, a mianowicie tego, jak agent zachowuje się wraz z tym harnessem w miarę upływu czasu.
Cztery pytania
Rozważmy najmniejszy pętlę: naciśnijmy przycisk, odczytajmy drzewo dostępności i sprawdźmy, czy pojawiła się oczekiwana etykieta. Jeśli nie, poczekajmy i odczytajmy ponownie, a po dziesięciu sekundach zakończmy próbę. Niezależnie od wielkości pętli w konfiguracji agenta, aż do pytania „czy ta funkcja została zrealizowana?”, musi ona odpowiedzieć na cztery pytania:
- Co się uruchamia?
- Czego odczytuje, aby podjąć decyzję?
- Co ją zakończa?
- Kiedy przestaje działać i prosi człowieka o pomoc?
Bez odpowiedzi na wszystkie cztery pytania nie ma mowy o pętli – jest tylko skrypt, który uruchamia się raz i ma nadzieję. Odpowiadanie na te pytania dla każdej pętli, którą uruchamia agent, stanowi całą tę dyscyplinę. Reszta to przykłady.
Pętla 3, logowanie, pokazuje te cztery odpowiedzi zaznaczone na rzeczywistym narzędziu pomocniczym. Każdy zależny od stanu przepływ testowy zaczyna się od niej:
- launchApp:
stopApp: true # 1. what runs: a cold launch, every time
- retry:
maxRetries: 3 # 3. what ends it: Home on screen, or three tries
commands:
- runFlow:
when:
visible:
text: "Continue" # 2. what it reads: the Login screen's button
file: login.yaml # -> sign in only if it's showing
- runFlow: assert-home.yaml # -> then prove Home is on screen
# 4. when it asks a human: never. A fourth failure is red, and red is the answer.
Fizyczna przyczyna tłumaczy konieczność ponownej próby. Zaraz po zimnym uruchomieniu ekran powitalny zakrywa zarówno stronę logowania, jak i główną, przez około trzy sekundy, więc przy pierwszym spojrzeniu nic z tego nie będzie widoczne. Limit to trzy próby. Prosta instrukcja „próbuj dalej, aż pojawi się strona główna” nigdy nie zakończyłaby się w przypadku uszkodzonej wersji oprogramowania, natomiast trzy próby pozwolą poradzić sobie z wolnym uruchamianiem i nadal umożliwią wykrycie rzeczywistego błędu poprzez zmianę koloru na czerwony.
Sześć nawarstwionych pętli
Sześć pętli działa od najmniejszej do największej, przy czym każda znajduje się wewnątrz następnej. Pętla nr 1 jest wykonywana dziesiątki razy w ramach każdego testu, każdy test jest uruchamiany w ramach testu na dwóch urządzeniach, każdy taki test z kolei w ramach sprawdzeń powiązanych z danym commitem, a każdy commit w ramach konkretnej części pracy.
Najpierw należy stworzyć najgłębszą pętlę, ponieważ wszystkie pozostałe od niej zależą. Pętle nr 1, 3 i 6 dotyczą interakcji z użytkownikiem, logowania oraz wypożyczania urządzeń. Trzy pozostałe wymagają wyjaśnienia.
Pętla 2 to ograniczona próba ponownego wykonania. W przypadku pustych cache’ów pierwsze tworzenie projektu w nowym worktree może spowodować awarię w kompilatorze Kotlin. Plik pamięci agenta zawiera jednozdaniową zasadę: spróbować raz przed założeniem, że mamy do czynienia z testowaną zmianą. To ograniczenie jest istotne – nieskończone próby aż do uzyskania pozytywnego wyniku mogłyby ukryć prawdziwą awarię, natomiast jedna próba sprawia, że znany problem przestaje mieć znaczenie, a druga nieudana próba staje się istotna.
Pętla 4 to hook pre-commit. W tym repozytorium nie ma CI, więc ten hook jest jedyną pętlą, która sama uruchamia się przy każdej zmianie. Sprawdza ona warunki zamknięte: jeśli brakuje skanera tajemnic gitleaks, komit jest odrzucany. Ponadto uruchamia własne testy przed testami gate’ów, ponieważ gate niezdolny do znalezienia swoich danych po prostu wyświetla komunikat OK. W skrócie brzmi ono tak:
#!/bin/sh
set -u
if ! command -v gitleaks >/dev/null 2>&1; then
echo "pre-commit: gitleaks not found — refusing to commit without a secret scan." >&2
exit 1
fi
gitleaks git --pre-commit --staged --redact --no-banner || exit 1
Druga połowa wygląda tak:
# The gates' own tests run first: a discovery bug produces no answer,
# after which every gate iterates an empty list and prints OK.
python3 ./scripts/test_gates.py >/dev/null 2>&1 || exit 1for gate in check_string_parity audit_hardcoded_strings; do
[ -x "./scripts/$gate.sh" ] && { "./scripts/$gate.sh" || exit 1; }
done
Pętla 5 to zewnętrzna pętla obejmująca każdą jednostkę pracy. Kawałek to mała, gotowa do wysłania część funkcji, która przechodzi przez cztery etapy. Propose tworzy rekord zmiany zawierający opis tego, co się zmienia, dlaczego oraz kryteria akceptacji. Apply realizuje tę zmianę. Verify sprawdza wynik w porównaniu z tym rekordem. Archive synchronizuje specyfikacje i zamyka dany kawałek. Verify to odrębny etap z własnym wynikiem, a nie dodatkowy komentarz od osoby realizującej kod potwierdzający pomyślne testy, dzięki czemu nie tylko agent, który napisał kod, może za niego ręczyć.
Kontrole przeprowadzane przez ludzi znajdują się w pisemnej tabeli pokazującej, które decyzje agent podejmuje samodzielnie, a które wymagają najpierw zadania pytania. Tabelę określa jedno zdanie: najpierw należy zapytać, czy odwrócenie zmiany kosztowałoby więcej niż około jednego dnia pracy nad poprawkami. Agent, który pominie jakiś etap, musi się zatrzymać, to wyjaśnić i uzyskać zatwierdzenie.
Pamięć to to, co przekazuje informacje pomiędzy pętlami oraz między sesjami: zbiór krótkich notatek, które Claude Code ponownie czyta za każdym razem, gdy rozpoczyna się sesja. W tym projekcie znajduje się 52 takie notatki, z których każda stanowi zasadę podobną do tej z pętli nr 2. Bez nich nowa sesja powtarza wcześniejszą diagnozę od zera.
Sprawdzenia, które nie mogą się nie powieść
Wszystkie te pętle kończą się, gdy jakieś sprawdzenie zgłosi „gotowe”. Sprawdzenie, które zgłasza „gotowe”, nie znalazłszy nic, w tajemnicy przerywa pętlę. Zdarzyło się to pięć razy w tym repozytorium.
Brama, która nie mogła zawieść. Brama lokalizacyjna to skrypt, który blokuje dokonanie komitowania, gdy tekst w języku angielskim jest zaimplementowany bezpośrednio w interfejsie użytkownika, a nie pobierany z katalogu ciągów tekstowych. Zawierała filtr o nazwie filter_animation_labels, przeznaczony do odrzucania wyłącznie tych rekordów, które przypominały etykiety animacji. Filtr sprawdzał window ~ tagbuilder wewnątrz awk, ale żaden parametr tagbuilder nie został kiedykolwiek przekazany do awk. Zmienna awk, która nie została ustawiona, to pusta strona, a window ~ „” pasuje do wszystkiego, więc filtr odrzucał każdy znaleziony rekord. Brama ta nigdy razu nie została aktywowana.
Filtr ukrył 49 sztywno zdefiniowanych ciągów znaków w 14 plikach w funkcji awaryjnej aplikacji, w tym angielski contentDescription przycisku SOS obok nieużywanej wersji tłumaczonej. Zdjęcia ekranu wykonane bezpośrednio na urządzeniu ujawniły wszystkie rzeczywiste błędy lokalizacji w tym obszarze, podczas gdy ten mechanizm nie wykrył żadnego. Jako warunek zakończenia pętli nr 4 sprawdzał każdą zmianę przez dwa dni i nic nie znalazł.
Mechanizm, który do niczego nie prowadził. Specyfikacja wymagała, aby proces budowania zakończył się błędem, lecz nic tego nie egzekwowało, ponieważ brakowało systemu CI. Pierwsza wersja tego mechanizmu używała również exec gitleaks, co zastępowało shell i tym samym uniemożliwiało wykonanie kolejnych poleceń.
Hook, który działał na wszystko. Wzorzec if: może ograniczyć hooki w Claude Code do określonych poleceń. Hook przeznaczony dla gh pr create używał jedynie Bash(gh pr create *), co jest zbyt wąskim ograniczeniem. Wzorce oparte wyłącznie na nazwie poleceń zawodzą, gdy napotykają $(), $VAR lub heredocy. Rozwiązaniem było użycie if: Bash(gh *), przy czym sam skrypt filtrował polecenie odczytane z stdin.
zestaw, który nic nie sprawdzał. Przez trzy tygodnie procesy testowe dla iOS zatrzymywały się po pierwszym kliknięciu, a żaden element nie zmieniał koloru na czerwony, ponieważ nic ich nie uruchamiało. Krok mający na celu obsługę przycisku SOS zgłaszał status COMPLETED, mimo że nic nie robił, więc jeden test SOS dawał identyczne wyniki dla kodu poprawnego i uszkodzonego.
Kontrola, która sprawdza złą rzecz. Trzy zielone wyniki dotyczyły APK zainstalowanego w innej sesji. Walidator specyfikacji zatwierdził 182 z 182 specyfikacji, mimo że jedna opisywała ekran usunięty miesiące wcześniej. PATCH otrzymał odpowiedź 200 od serwera backendowego, który nie otrzymał migracji i po cichu odrzucił to pole.
Zanim kontrola zyska zaufanie, należy przywrócić dokładny defekt, który miała wykryć, i sprawdzić, czy pokazuje on kolor czerwony; następnie należy usunąć ten defekt i sprawdzić, czy wynik jest zielony – wszystko to w tej samej sesji, w której została napisana kontrola. Skrypt audytu dokumentacji tutaj postępował zgodnie z tą zasadą: zmieniając status, dodając nieistniejący link lub usuwając dane delta za każdym razem, gdy wynik stawał się czerwony. W notatce projektowej zasada ta jest sformułowana w jednym zdaniu: kontrola, która nie może zawieść, jest jedynie dekoracją. Nie przechowuje ona również żadnego pliku bazowego, ponieważ uciszenie 292 znanych problemów w celu uzyskania wyniku zielonego interpretuje się jako pełne pokrycie.
Gdzie zacząć
Najpierw należy napisać cztery odpowiedzi przed jakimkolwiek kodem, jako komentarz na górze bloku instrukcji, skryptu lub reguły. Brakująca odpowiedź zostanie natychmiast zauważona, a nie w trakcie bezproblemowo przebiegającej sesji.
Następnie przychodzi najprostszy pętla – pętla tap, która ma dwa wymagania. Elementy sterujące muszą mieć etykiety: contentDescription lub testTag dla Androida, accessibilityLabel dla iOS. Ponadto agent potrzebuje stałej reguły dla każdej interakcji z ekranem. Reguła ta znajduje się w pliku instrukcji repozytorium, który Claude Code ładuje na początku każdej sesji, i sprowadza się do następującego:
Driving the emulator:
- Before every tap: `adb shell uiautomator dump`, and find the label you intend to tap.
- Tap the centre of that node's bounds. Never reuse a coordinate from an earlier dump.
- After the tap: dump again and confirm the expected label appeared. Poll up to 10 s,
then stop and report red — do not guess a second tap.
- If the app won't launch: `pm dump <pkg> | grep enabled=` before suspecting the build.
Te cztery zdania stanowią pętlę, którą uruchamia agent. W rzeczywistości wykonywane są operacje dump, tap, dump. Agent czyta strukturę drzewa i zatrzymuje się przy pojawieniu się etykiety lub po upływie 10 sekund. Nigdy nie pyta człowieka, ponieważ odpowiedzią jest kolor czerwony.
Na koniec, zanim zaufasz procedurze sprawdzania, która zamyka pętlę, celowo ją przerwij. Zmień nazwę etykiety i sprawdź, czy pętla zmieni kolor na czerwony. Jeśli pozostanie zielona, oznacza to, że nic nie jest czytane.
Cała konfiguracja składa się z sześciu elementów:
- Pętla typu tap, zapisana jako powyższa zasada w pliku instrukcji agenta. Wszystko inne zakłada, że agent może najpierw działać, a potem sprawdzać wyniki.
- Hook pre-commit, który powoduje niepowodzenie, który jest komitowany do repozytorium i umieszczany w folderze
.git/hooksza pomocą krótkiego skryptu konfiguracyjnego, dzięki czemu przy każdym pobraniu uruchamiana jest komitowana wersja. Bez skanera nie ma możliwości komitowania.
W sumie daje to drzewo projektowe do budowy, konto umożliwiające logowanie, ekran, który agent może odczytać, urządzenie używane podczas działania oraz pętle kończące się po sprawdzeniu, czy niektóre próby zakończyły się niepowodzeniem.