Anotar los bucles que ejecuta un agente de IA en una aplicación móvil
Anotar los bucles que ejecutan los agentes de IA en una aplicación móvil: cuatro preguntas a las que debe responder cada bucle, seis bucles anidados desde el toque hasta el procesamiento, y verificaciones que no detectan nada.
Dígale a un agente que solo haga que pase la prueba de inicio de sesión, y él diseñará el bucle por sí mismo, en el momento. Suelen ocurrir una de dos cosas: vuelve a intentarlo hasta que algo muestre un resultado positivo, incluso si la prueba verifica menos datos que antes; o bien lee su propio registro, no detecta errores y anuncia que la tarea está completada. Ambos comportamientos son bucles, y en ambos casos el modelo eligió el diseño según lo que leyó.
La ingeniería de bucles elimina esa elección de diseño del modelo. Una persona decide de antemano, por escrito, qué se ejecuta, qué se lee para tomar una decisión, qué lo detiene y cuándo debe parar y solicitar ayuda humana. Aquí se aplica a una aplicación móvil.
La configuración
El ejemplo práctico es una aplicación de mensajes de voz para Android e iOS, desarrollada en gran medida utilizando Claude Code. Cada sesión recibe un checkout separado de git, denominado worktree. Dentro de él, el agente construye la aplicación, la instala en un emulador, opera la interfaz de usuario a través de Maestro, un ejecutor de pruebas en YAML que lee el árbol de accesibilidad de la pantalla (los controles etiquetados que el sistema operativo expone), y guarda sus resultados. Nada en los bucles depende de esta aplicación específica; lo único que necesitan es un agente capaz de actuar y luego observar.
El conjunto de herramientas consiste en los archivos, dispositivos y reglas que determinan qué ve y hace el agente. Los bucles abordan un tema diferente: cómo se comporta el agente con ese conjunto de herramientas a medida que pasa el tiempo.
Las cuatro preguntas
Considere el bucle más pequeño: toque un botón, lea el árbol de accesibilidad y verifique si apareció la etiqueta esperada. Si no, espere y vuelva a leerlo; si después de diez segundos aún no ha funcionado, considérelo un fallo. Independientemente del tamaño de un bucle en la configuración del agente, hasta el punto de “¿está completada esta función?”, este debe responder a cuatro preguntas:
- ¿Qué se ejecuta?
- ¿Qué lee para tomar una decisión?
- ¿Qué lo detiene?
- ¿Cuándo se detiene y pide ayuda a una persona?
Sin las respuestas a estas cuatro preguntas, no hay bucle, solo un script que se ejecuta una vez y espera.Responderlas para cada bucle que ejecute el agente constituye toda la disciplina; lo demás son ejemplos.
El bucle 3, de inicio de sesión, muestra las cuatro respuestas marcadas en un asistente real. Cada flujo de pruebas dependiente del estado comienza con él:
- 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.
Una causa física explica la repetición del intento. Justo después de un arranque en frío, la pantalla de bienvenida oculta tanto la pantalla de inicio de sesión como la página principal durante aproximadamente tres segundos, por lo que al primer vistazo no se puede ver ninguna de ellas. El límite son tres intentos. Un enfoque simple de “reintentar hasta que aparezca la página principal” nunca terminaría en una compilación defectuosa, mientras que tres intentos permiten soportar un arranque lento y aún así hacer que aparezca el mensaje de error en rojo cuando realmente hay un fallo.
Seis bucles anidados
Los seis bucles se ejecutan del más pequeño al más grande, y cada uno está dentro del siguiente. El bucle 1 se ejecuta decenas de veces dentro de cada prueba, cada prueba se lleva a cabo dentro de una ejecución con dos dispositivos, cada ejecución está incluida en las verificaciones de un commit y cada commit forma parte de una sección específica del trabajo.
Construya primero el bucle más interno, ya que todos los demás dependen de él. Los bucles 1, 3 y 6 corresponden al bucle de toque, al inicio de sesión y al arrendamiento de dispositivos. Tres de los otros merecen una explicación adicional.
El bucle 2 es un intento de reintentar limitado. En cachés vacíos, la primera compilación en un nuevo árbol de trabajo puede fallar dentro del compilador Kotlin. El archivo de memoria del agente contiene una regla de una sola línea: reintentar una vez antes de sospechar que se está probando un cambio. Este límite es importante; los reintentos ilimitados hasta obtener un resultado positivo ocultarían una falla real, mientras que un solo reintento convierte un problema conocido en algo insignificante y hace que una segunda falla tenga sentido.
El bucle 4 es el gancho pre-commit. No existe ningún sistema de CI en este repositorio, por lo que este gancho es el único bucle que se activa automáticamente ante cada cambio. Falla de forma definitiva: cuando falta el escáner de secretos gitleaks, el commit se rechaza. También ejecuta las pruebas propias de los controles antes de que estos se activen, ya que un control incapaz de encontrar sus datos simplemente muestra “OK”. En resumen, su funcionamiento es el siguiente:
#!/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
La segunda mitad se ve así:
# 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
El bucle 5 es el bucle exterior que rodea cada unidad de trabajo. Una porción es una pequeña parte listo para enviar de una funcionalidad, y pasa por cuatro pasos. Propose escribe el registro del cambio con lo que se hace, el motivo y los criterios de aceptación. Apply lo lleva a cabo. Verify verifica el resultado en comparación con ese registro. Archive sincroniza las especificaciones y cierra la porción. Verify es un paso independiente con salida separada, no una observación final del implementador indicando que las pruebas pasaron, de modo que el agente que escribió el código no es el único que lo avala.
Los puntos de control humanos se presentan en una tabla escrita que muestra qué decisiones toma el agente por sí solo y cuáles requieren una consulta previa. Una sola frase rige la tabla: primero preguntar si revertir el cambio costaría más de aproximadamente un día de trabajo adicional. Un agente que omite un paso debe detenerse, explicarlo y obtener aprobación.
La memoria es lo que transfiere información entre bucles y a través de sesiones: un conjunto de notas breves que Claude Code vuelve a leer cada vez que comienza una sesión. Este proyecto contiene 52 de ellas, cada una siendo una regla similar a la del bucle 2. Sin ellas, una sesión nueva repite el diagnóstico anterior desde cero.
Verificaciones que no pueden fallar
Todos estos bucles terminan cuando alguna verificación indica “listo”. Una verificación que dice “listo” sin detectar nada interrumpe el bucle en silencio. Eso ocurrió cinco veces en este repositorio.
Una barrera que no podía fallar. Una barrera de localización era un script que bloqueaba una confirmación de cambios cuando el texto en inglés estaba codificado directamente en la interfaz de usuario y no se leía del catálogo de cadenas. Contenía un filtro llamado filter_animation_labels, cuyo propósito era descartar únicamente aquellos resultados que parecían etiquetas de animación. El filtro comprobaba window ~ tagbuilder dentro de awk, pero nunca se pasó ningún valor de tagbuilder a awk. Una variable de awk no definida es una cadena vacía, y window ~ "" coincide con todo, por lo que el filtro descartaba todos los resultados que encontraba. La barrera nunca se activó ni una sola vez.
El filtro encontró 49 cadenas de texto hardcodeadas distribuidas en 14 archivos dentro de la función de emergencia de la aplicación; entre ellas había un contentDescription en inglés para el botón SOS junto a una traducción que no se utilizaba. Las capturas de pantalla realizadas en el propio dispositivo revelaron todos los defectos reales de localización en esa área, pero la herramienta de verificación no detectó ninguno. Al servir como condición final del bucle 4, examinó cada commit durante dos días sin encontrar nada.
Una herramienta de verificación que no sirvió para nada. Las especificaciones exigían que la compilación fallara, pero no había mecanismo alguno que lo impidiera, ya que no existía un sistema de integración continua. La primera versión del script también utilizaba exec gitleaks, lo que sustituía al shell y evitaba así la ejecución de las líneas posteriores.
Un gancho que funcionaba con todo. Un patrón if: puede restringir los ganchos de Claude Code a ciertas comandos. Un gancho destinado a gh pr create solo utilizaba Bash(gh pr create *), lo cual es demasiado específico. Los patrones que van más allá del nombre simple de un comando fallan al encontrarse con $(), $VAR o heredocs. La solución fue if: Bash(gh *), de modo que el propio script filtre el comando leído desde stdin.
Un conjunto que no probaba nada. Durante tres semanas, los flujos de prueba para iOS se detenían en el primer paso y nada se marcaba como error, ya que no se ejecutaba ninguna prueba. El paso que debía activar el botón SOS indicaba COMPLETED sin hacer nada, por lo que una prueba de SOS daba resultados positivos idénticos tanto con código funcional como con código defectuoso.
Una comprobación que resulta verdadera para algo incorrecto. Había tres registros verdes relacionados con un APK instalado en otra sesión. Un validador de especificaciones aprobó 182 de las 182 especificaciones, aunque una describía una pantalla que se había eliminado meses antes. Un PATCH devolvió 200 desde un backend que no había recibido la migración y descartó silenciosamente ese campo.
Antes de que una comprobación gane confianza, hay que recuperar el defecto exacto para el cual fue creada y verificar que se muestre en rojo; luego eliminar ese defecto y verificar que aparezca en verde, todo ello en la misma sesión en la que se escribió la comprobación. El script de auditoría de documentos aquí siguió ese procedimiento: cambiaba el estado, añadía un enlace inválido o eliminaba los cambios cada vez que se mostraba en rojo. Su nota de diseño resume el principio en una sola frase: una comprobación que no puede fallar es solo decorativa. Tampoco guarda ningún archivo de referencia, ya que silenciar 292 hallazgos conocidos para lograr un resultado en verde se considera cobertura.
Por dónde empezar
Comience escribiendo las cuatro respuestas antes de cualquier código, como un comentario en la parte superior del flujo, script o regla. De esta manera, cualquier respuesta faltante se detectará de inmediato y no dentro de una ejecución que pase desapercibida.
A continuación está el bucle más simple, el bucle tap, que tiene dos requisitos. Los controles deben tener etiquetas: contentDescription o testTag para Android, y accessibilityLabel para iOS. Además, el agente necesita una regla permanente para cada interacción con la pantalla. En este caso, dicha regla se encuentra en el archivo de instrucciones del repositorio, aquel que Claude Code carga al inicio de cada sesión, y se resume en lo siguiente:
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.
Esa secuencia de cuatro oraciones constituye un bucle que ejecuta el agente. Lo que realmente se ejecuta es “dump”, “tap” y nuevamente “dump”. El agente lee el árbol estructural; termina cuando aparece la etiqueta correspondiente o después de 10 segundos. Nunca consulta a un humano, ya que el color rojo es la respuesta.
Finalmente, antes de confiar en la verificación que cierra el bucle, rompálo intencionadamente. Cambie el nombre de la etiqueta y observe si el bucle se vuelve rojo. Si permanece verde, significa que no se está leyendo nada.
Toda la configuración se reduce a seis componentes:
- Un bucle de tipo “tap”, definido como la regla mencionada anteriormente dentro del archivo de instrucciones del agente. Todo lo demás supone que el agente puede actuar primero y luego observar.
- Un gancho de tipo “pre-commit que falla”, que se registra en el repositorio y se coloca en
.git/hooksmediante un breve script de configuración, de modo que cada descarga ejecute la versión registrada. Sin un escáner, no hay registro.
En total, eso da como resultado un árbol de trabajo que se construye, una cuenta con la que poder iniciar sesión, una pantalla que el agente puede leer, un dispositivo para ejecutar las tareas y bucles que finalizan cuando se comprueba que ciertas pruebas han fallado.
Lecturas relacionadas
- Bucles vs. Grafos: Dos formas de diseñar agentes de IA — Cómo difieren la ingeniería de bucles y la ingeniería de grafos para los agentes de IA, con arquitecturas, cuándo elegir cada una y una lista de verificación para producción.
- Ingeniería de bucles: Cuatro bucles anidados para agentes de IA auto-mejorables — Cómo cuatro bucles de control anidados (ReAct, reintentos, cola, escalada de colinas) junto con un evaluador, un medidor de presupuesto y una verificación contra manipulaciones hacen que un agente sea fiable, ilustrado en el proceso de aprobación de seguros.