Головна / Статті

Опис циклів, якими керується штучний інтелект-агент у мобільному додатку.

Записування циклів, які виконують штучні інтелектуальні агенти в мобільному додатку: чотири запитання, на які мусить відповісти кожен цикл, шість вкладених циклів від натискання до обробки даних, та перевірки, які нічого не виявляють.

Видавець: REACTAPP.TOP9 хв читання2122 слів

Джерела: Loop Engineering on a Mobile App: What My AI Agent Runs, What It Reads, What Ends It, and When It Asks Me

Якщо сказати агенту лише пройти тест входу, він самостійно та миттєво створює відповідний цикл. Зазвичай настає одне з двох: він намагається знову та знову, поки щось не стане «зеленим», включаючи тест, який тепер перевіряє менше критеріїв, ніж раніше. Або ж він читає власний журнал подій, не бачить помилок та повідомляє про завершення роботи. Обидві форми поведінки є циклами, і в обох випадках модель обирає спосіб роботи на основі того, що їй випадково вдалося прочитати.

Інженерія циклів усуває цей вибір способу роботи з моделі. Людина заздалегідь, у письмовій формі, визначає, що має виконуватися, що вона читає для прийняття рішення, що завершує процес та коли потрібно зупинитися та звернутися до людини. Тут цей підхід застосовується до мобільного додатку.

Налаштування

Приклад, який розглядається, — це додаток для голосових повідомлень для Android та iOS, створений переважно за допомогою Claude Code. Кожна сесія отримує окремий режим git checkout, який називається worktree. У межах цього режиму агент створює додаток, встановлює його на емулятор, керує інтерфейсом через Maestro — виконавця тестів на YAML, який читає дерево доступності екрана (екранизовані елементи керування, які операційна система робить доступними), та фіксує свою роботу. Ніщо у цих циклах не залежить від конкретного додатку. Усе, що їм потрібно, — це агент, який може діяти та потім спостерігати.

Harness — це інструменти, файли, пристрої та правила, які визначають, що бачить та що робить агент. Цикли стосуються іншої справи, а саме того, як агент поводиться з цим harness з плином часу.

Чотири запитання

Розгляньмо найменшу петлю: натисніть кнопку, прочитайте дерево доступності та перевірьте, чи з’явилася очікувана мітка. Якщо ні, почекайте та прочитайте ще раз; у разі відсутності результату через десять секунд — зупиніть процес. Незалежно від розміру петлі в налаштуваннях агента, навіть якщо йдеться про перевірку „Чи виконана ця функція?“, вона повинна дати відповідь на чотири запитання:

  1. Що виконується?
  2. Що читається для прийняття рішення?
  3. Що її завершує?
  4. Коли вона зупиняється та запитує людину?

Без відповідей на всі чотири запитання немає петлі — лише скрипт, який виконується один раз та сподівається на краще. Відповідь на ці запитання для кожної петлі, яку виконує агент, і є суттю цієї дисципліни. Решта — це приклади.

Петля 3, процес входу, демонструє чотири відповіді, позначені на реальному помічнику. Кожен тестовий процес, залежний від стану, починається саме з неї:

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

Фізична причина пояснює необхідність повторної спроби. Відразу після холодного запуску екран завантаження закриває як екран входу, так і головну сторінку приблизно на три секунди, тому під час першого погляду їх можна не побачити. Кількість спроб обмежена трьома. Проста формула „повторювати, поки не з’явиться головна сторінка“ ніколи не завершиться при пошкодженій версії програми, тоді як три спроби дозволяють пережити повільний запуск та все одно виявити справжню проблему, яка змусить екран стати червоним.

Шість вкладених циклів

Шість циклів виконуються від найменшого до найбільшого, причому кожен знаходиться всередині наступного. Цикл 1 виконується десятки разів під час кожного тесту; кожен тест виконується під час тестування на двох пристроях; кожне тестування — під час перевірок коміту; а кожен коміт — під час виконання певної частини роботи.

Спочатку потрібно створити найвнутрішніший цикл, оскільки всі зовнішні залежать від нього. Цикли 1, 3 та 6 стосуються взаємодії з пристроями, процесу входу та оренди пристроїв. Ще три цикли потребують пояснення.

Цикл 2 — це обмежена спроба повторення. У випадку порожніх кешів перша обробка коду в новому робочому дереві може призвести до збою в компіляторі Kotlin. Файл пам’яті агента містить однорядкове правило: спробувати ще раз, перш ніж припускати, що відбувається тестування змін. Цей ліміт має велике значення. Нескінченне повторення спроб до отримання позитивного результату може приховати справжню проблему, тоді як одна спроба перетворює відому нестабільність на щось несуттєве та робить другу невдачу значущою.

Цикл 4 — це хук pre-commit. У цьому репозиторії немає системи CI, тож цей хук є єдиним циклом, який самостійно активується при кожній зміні. Він блокує коміт, якщо відсутній сканер секретів gitleaks; у такому разі коміт відхиляється. Крім того, він запускає власні тести перед перевіркою, оскільки тестовий механізм, який не може знайти свої вхідні дані, просто виводить „OK“. Коротко кажучи, його функція полягає у цьому:

#!/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

Друга половина виглядає так:

# 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

Цикл 5 — це зовнішній цикл, який охоплює кожну одиницю роботи. Шматочок — це невелика частина функції, готова до випуску, і вона проходить через чотири кроки. Крок «Пропонування» включає запис змін із описом того, що саме змінюється, чому це потрібно та критеріями прийняття. Крок «Застосування» здійснює ці зміни. Крок «Перевірка» порівнює результат із записом змін. Крок «Архівування» синхронізує специфікації та завершує роботу над шматочком. Крок «Перевірка» є окремим кроком із власним результатом, а не просто приміткою розробника про те, що тести пройшли, тож не лише агент, який написав код, може підтвердити його якість.

Людські контрольні точки представлені у письмовій таблиці, яка показує, які рішення приймає агент самостійно, а які потребують попереднього запитання. Таблицею керує одне правило: спочатку потрібно запитати, чи не коштуватиме скасування змін більше, ніж приблизно один день на переробку. Агент, який пропускає крок, змушений зупинитися, пояснити це та отримати схвалення.

Пам’ять — це те, що передає інформацію між циклами та протягом сеансів: набір коротких записів, які Claude Code знову читає щоразу при початку сеансу. У цьому проекті є 52 таких записи, кожен з яких — це правило у форматі циклу 2. Без них новий сеанс повторює попередній аналіз з нуля.

Перевірки, які не можуть зазнати невдачі

Усі ці цикли закінчуються, коли якась перевірка повідомляє „готово“. Перевірка, яка повідомляє про готовність, не виявивши нічого, тихо припиняє цикл. Це трапилося п’ять разів у цьому репозиторії.

Брана, яка не могла зазнати невдачі. Брана локалізації — це скрипт, який блокував збереження змін, коли англійський текст був жорстко закодований у інтерфейсі та не читався з каталогу рядків. Він містив фільтр під назвою filter_animation_labels, призначений для відкидання лише тих записів, які нагадували мітки анімації. Фільтр перевіряв window ~ tagbuilder у awk, але жоден параметр tagbuilder ніколи не передавався до awk. Незадана змінна awk є порожньою рядком, і window ~ „“ збігається з усім, тому фільтр відкидав кожен знайдений запис. Ця брана так і не спрацювала жодного разу.

Фільтр знайшов 49 жорстко закодованих рядків у 14 файлах у функції надзвичайних ситуацій додатку, серед яких англійський рядок contentDescription для кнопки SOS поруч із невикористаним перекладом. Скріншоти, зроблені безпосередньо на пристрої, виявили всі справжні дефекти локалізації в цій області, тоді як система перевірки нічого не виявила. Використовуючись як умова завершення циклу 4, вона перевірила кожну зміну протягом двох днів та нічого не виявила.

Система перевірки, яка нікуди не призвела. Технічні вимоги передбачали, що процес будування має зазнати невдачі, проте цього не контролювалося через відсутність системи CI. Перша версія цього механізму також використовувала команду exec gitleaks, яка замінює оболонку та таким чином заважала виконанню будь-яких наступних команд.

Гак, який діяв на все. Шаблон if: дозволяє обмежити гаки Claude Code певними командами. Гак, призначений для gh pr create, використовував лише Bash(gh pr create *), що є занадто вузьким формулюванням. Шаблони, що виходять за межі простої назви команди, при зустрічі з $(), $VAR або хередоками починають працювати без обмежень. Рішенням стало використання if: Bash(gh *), коли сам скрипт фільтрує команду, отриману з stdin.

Набір, який нічого не перевіряв. Протягом трьох тижнів тестування для iOS зупинялося вже після першого натискання, і ніщо не показувало помилки, оскільки жодні процеси не запускалися. Крок, який мав би контролювати кнопку SOS, повідомляв про завершення роботи, хоча насправді нічого не робив, тому один тест SOS давав однакові результати як для правильного, так і для пошкодженого коду.

Перевірка, яка показує хибний результат. Три зелені позначки виникли щодо APK, встановленого іншою сесією. Перевірювач специфікацій схвалив 182 з 182 специфікацій, хоча одна з них описувала екран, який було видалено кілька місяців тому. PATCH отримав код 200 від бекенду, який не отримав міграцію, і тихо проігнорував це поле.

Перш ніж перевірка заслуговуватиме на довіру, необхідно повернути саме той дефект, для виявлення якого вона створена, переконатися, що вона показує червоний статус, потім усунути дефект та переконатися, що статус став зеленим — усе це має відбуватися в тій самій сесії, де була написана перевірка. Скрипт аудиту документації тут слідував цьому алгоритму: змінював статус, додавав недійсні посилання чи видаляв дані при кожному переході на червоний статус. У примітці до його проектування цей принцип сформульований однією реченням: перевірка, яка не може дати хибного результату, є лише декорацією. Він також не зберігає базових файлів, оскільки приховування 292 відомих проблем для досягнення зеленого статусу розцінюється як покриття.

З чого почати

Спочатку запишіть чотири відповіді перед будь-яким кодом у вигляді коментаря нагорі блоку коду, скрипту чи правила. Тоді відсутня відповідь стане очевидною відразу, а не під час безпроблемного виконання.

Далі йде найменший цикл — цикл тапу, який має дві вимоги. Контролери повинні мати мітки: contentDescription або testTag для Android, accessibilityLabel для iOS. Крім того, агенту потрібне постійне правило для кожної взаємодії з екраном. У цьому випадку правило знаходиться у файлі інструкцій репозиторію, який Claude Code завантажує на початку кожної сесії, і полягає у наступному:

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.

Ці чотири речення є циклом, який виконує агент. Він виконує команди dump, tap, dump. Агент читає структуру дерева та зупиняється при з’явленні певної мітки або через 10 секунд. Він ніколи не запитує людину, адже відповіддю є колір червоний.

Нарешті, перш ніж довіряти перевірці, яка закриває цикл, навмисно його порушіть. Змініть назву мітки та подивіться, чи стане цикл червоним. Якщо він залишиться зеленим, це означає, що нічого не читається.

Повна конфігурація складається з шести елементів:

  1. Цикл tap, описаний у вигляді правила вище всередині файлу інструкцій агента. Усе інше передбачає, що агент може діяти, а потім спостерігати.
  2. Функція pre-commit hook that fails closed, яка зберігається у репозиторії та поміщається до папки .git/hooks за допомогою короткого скрипта налаштування, щоб під час кожного завантаження виконувалася збережена версія. Без сканера немає можливості здійснити коміт.
  • Тест на мутацію для кожного етапу, який виконується у день його написання: наявність дефекту дає червоний колір, його відсутність — зелений. Його потрібно виконати протягом тієї самої сесії, інакше це ніколи не відбудеться.
  • Обмежена кількість спроб повторного виконання для відомих проблем, таких як повільна збірка проекту Gradle, запуск симулятора чи екран завантаження; це записується у пам’яті чи файлі інструкцій як один рядок: спробувати один раз, а потім звалити провину на зміни.
  • Крок перевірки окремо від реалізації із письмовим результатом, тобто перелік контрольних пунктів згідно з записом змін, а не просто твердження про те, що тести пройшли.
  • Одноречевий людський контрольний пункт у файлі інструкцій: спочатку потрібно запитати, чи коштуватиме скасування змін більше, ніж приблизно день переробки.
  • У сукупності це створює ієрархічну структуру, яка включає механізм побудови, обліковий запис для входу, екран, який може читати агент, пристрій для виконання завдань та цикли, які припиняються після перевірки невдалих спроб.