Главная / Статьи

Описание циклов, которые выполняет искусственный интеллект-агент в мобильном приложении.

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

Издатель: 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, называемый 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, отображал статус COMPLETED, не выполняя никаких действий; поэтому один тест 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, возвращающий ошибку, который сохраняется в репозитории и помещается в папку .git/hooks с помощью простого скрипта настройки, так что при каждом скачивании выполняется сохраненная версия. Без сканера коммитов не будет.
  • Тест на мутации для каждого шага, выполняемый в день его написания: наличие дефекта приводит к красному цвету, отсутствие дефекта — к зеленому. Это должно происходить в той же сессии, иначе тест не будет выполнен.
  • Ограниченное количество попыток повтора для известных проблем, таких как медленная сборка в Gradle, запуск симулятора или загрузочный экран; информация о них сохраняется в виде одной строки в памяти или файле инструкций: одна попытка повтора, затем выявление причины сбоя.
  • Шаг проверки, отдельный от реализации, с письменным выводом, то есть список проверок по записи изменений, а не просто фраза о том, что тесты прошли.
  • Проверка человеком в виде одного предложения в файле инструкций: сначала необходимо уточнить, стоит ли откат изменений больше, чем примерно день работы над исправлением.
  • В сумме это даёт структуру рабочего дерева для построения, учётную запись для входа, экран, который может просматривать агент, устройство для выполнения операций и циклы, завершающиеся при обнаружении неудачных проверок.