<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Diego Ferreira Caetano</title>
    <description>The latest articles on DEV Community by Diego Ferreira Caetano (@diego_f_caetano).</description>
    <link>https://dev.to/diego_f_caetano</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4192438%2F802f0e80-08de-49ca-8dcb-88774e3d9e73.png</url>
      <title>DEV Community: Diego Ferreira Caetano</title>
      <link>https://dev.to/diego_f_caetano</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diego_f_caetano"/>
    <language>en</language>
    <item>
      <title>Android Dev Summit 2026: Google vuelve a reunir a los devs después de cuatro años y pone a los agentes en el centro de Android</title>
      <dc:creator>Diego Ferreira Caetano</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:43:47 +0000</pubDate>
      <link>https://dev.to/diego_f_caetano/android-dev-summit-2026-google-vuelve-a-reunir-a-los-devs-despues-de-cuatro-anos-y-pone-a-los-4l9e</link>
      <guid>https://dev.to/diego_f_caetano/android-dev-summit-2026-google-vuelve-a-reunir-a-los-devs-despues-de-cuatro-anos-y-pone-a-los-4l9e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lvwr9lzf4ha5h5oqc5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lvwr9lzf4ha5h5oqc5t.png" alt="Android Dev Summit 2026: Google vuelve a reunir a los devs después de cuatro años y pone a los agentes en el centro de Android" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mountain View, 28 de octubre de 2026&lt;/strong&gt; — Después de cuatro años sin un evento propio, Google volvió a realizar el &lt;strong&gt;Android Dev Summit&lt;/strong&gt;. La última edición había sido en 2022; desde entonces, las novedades de la plataforma compartían espacio con el resto del Google I/O. Esta vez fueron dos días dedicados solo a Android, en el campus Bay View, con el keynote transmitido en YouTube y una edición extendida en Londres.&lt;/p&gt;

&lt;p&gt;El mensaje del keynote fue directo: Android ahora se construye &lt;strong&gt;con&lt;/strong&gt; agentes de IA y &lt;strong&gt;para&lt;/strong&gt; agentes de IA. Android Studio abrió sus puertas a agentes de otras empresas, como Claude y Codex. Android CLI les dio a esos agentes acceso a dispositivos reales. Y las apps ganaron una forma estándar de ofrecer sus funciones a Gemini.&lt;/p&gt;

&lt;p&gt;En este briefing reúno lo que se anunció, en qué etapa está cada novedad y — la parte que el keynote recorre rápido — cómo usar cada una en el día a día, con código.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8Lmv7LGgL4E" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Ficha del evento
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cuándo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28 y 29 de octubre de 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dónde&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Campus Bay View de Google, Mountain View (EE. UU.), con edición extendida en Londres&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contexto&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Primer Android Dev Summit desde 2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI experiences, Android XR, Apps foundation, Tools &amp;amp; performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keynote&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cerca de una hora, con demos en vivo — &lt;a href="https://www.youtube.com/watch?v=8Lmv7LGgL4E" rel="noopener noreferrer"&gt;míralo en YouTube&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Los titulares
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android Studio Rabbit 2 llega a stable&lt;/strong&gt; y acepta cualquier agente de código compatible con ACP — Claude, Codex, Antigravity y más de 40 otros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android CLI suma dispositivos físicos remotos&lt;/strong&gt;, render de Compose Preview en la terminal, un profiler con LeakCanary y plugins oficiales para Claude Code y Codex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checklist de publicación en semáforo&lt;/strong&gt;: la skill &lt;code&gt;play-policy-insights&lt;/code&gt; audita la app antes del envío a Google Play y marca 🟢🟡🔴 lo que falta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migración de XML a Compose con un clic derecho&lt;/strong&gt;, guiada por un agente dentro de Android Studio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AppFunctions pasa a beta&lt;/strong&gt;: las funciones de tu app se vuelven &lt;em&gt;tools&lt;/em&gt; para Gemini y otros agentes, vía &lt;strong&gt;Android MCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2UI (alpha)&lt;/strong&gt;: los agentes ahora pueden responder con pantallas nativas en Compose, hechas con tu design system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini Nano 4 + ML Kit Agentic API (alpha)&lt;/strong&gt;: un agente completo corriendo en el dispositivo, con llamada de funciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jetpack AppState (alpha)&lt;/strong&gt; lleva el modelo de estado de Compose a ViewModels y repositorios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Googlebook, plegables y Android XR&lt;/strong&gt;: un código para muchas pantallas. &lt;strong&gt;XREAL AURA&lt;/strong&gt; debuta como las primeras gafas XR con cable que corren Android XR.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Lo que ya puedes usar (y lo que todavía es laboratorio)
&lt;/h2&gt;

&lt;p&gt;Antes de instalar todo, conviene mirar la madurez de cada anuncio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnf1md9ialznq5lpr93o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnf1md9ialznq5lpr93o4.png" alt="Mapa de madurez de los anuncios" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mi regla práctica: &lt;strong&gt;stable&lt;/strong&gt; va a producción, &lt;strong&gt;beta&lt;/strong&gt; entra detrás de un feature flag, &lt;strong&gt;alpha y preview&lt;/strong&gt; se quedan en POC y estudio.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Android Studio Rabbit 2: trae tu propio agente
&lt;/h2&gt;

&lt;p&gt;Android Studio dejó de ser "el IDE con Gemini". Con &lt;strong&gt;Bring Your Own Agent (BYOA)&lt;/strong&gt;, ahora stable en Rabbit 2, se convierte en el host de cualquier agente compatible con el &lt;strong&gt;Agent Client Protocol (ACP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cómo activarlo:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Abre &lt;strong&gt;Settings &amp;gt; Tools &amp;gt; AI &amp;gt; Agents&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Instala agentes desde el marketplace (Claude Agent, Codex, Antigravity…). Puedes tener varios y alternar.&lt;/li&gt;
&lt;li&gt;El agente pasa a ver lo que solo el IDE sabe: estructura del proyecto y del build, resolución de símbolos, render de Compose Preview, el Emulator y Logcat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Además llegaron tres funciones que cambian el día a día:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change Review&lt;/strong&gt; — revisión agéntica de los cambios que hizo el agente. Inícialo desde los cambios pendientes, la ventana de Git o con &lt;code&gt;/review&lt;/code&gt;. Genera un reporte de problemas y un recorrido guiado para cambios grandes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration Assistant (Canary)&lt;/strong&gt; — clic derecho en un layout XML &amp;gt; &lt;strong&gt;AI › Migrate to Compose&lt;/strong&gt; y el agente migra la pantalla a Compose. Más abajo hay una sección completa sobre esto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Bench 2.0&lt;/strong&gt; — un leaderboard que evalúa modelos y harnesses en tareas Android largas (migrar UI, construir una app desde mocks), midiendo si realmente funciona, además de costo y tiempo. Ideal para elegir el agente del equipo: &lt;a href="https://developer.android.com/bench" rel="noopener noreferrer"&gt;developer.android.com/bench&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ACP vs MCP: no los confundas
&lt;/h3&gt;

&lt;p&gt;Es el punto que más confusión genera. Son dos protocolos con funciones distintas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuoou5gp6b7mmk5qtyv2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuoou5gp6b7mmk5qtyv2b.png" alt="ACP te ayuda a construir; MCP deja que los agentes usen tu app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Android CLI: todo el toolchain en manos del agente
&lt;/h2&gt;

&lt;p&gt;Android CLI es la interfaz "tool-first" para cualquier terminal, agente o pipeline de CI. La idea es simple: el agente deja de gastar tokens adivinando cómo funciona Android y usa herramientas oficiales.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# instala el CLI y configura el agente&lt;/span&gt;
android init

&lt;span class="c"&gt;# un dispositivo físico real, en un device lab de Google (vía Firebase)&lt;/span&gt;
android device remote connect
android device hub            &lt;span class="c"&gt;# ventana para ver y controlar dispositivos y emuladores&lt;/span&gt;

&lt;span class="c"&gt;# Compose Previews sin emulador ni IDE&lt;/span&gt;
android preview

&lt;span class="c"&gt;# rendimiento y memoria&lt;/span&gt;
android profiler systemtrace
android profiler heapdump capture
android profiler leakcanary

&lt;span class="c"&gt;# calidad del binario y señales de producción&lt;/span&gt;
android analyze app-release.aab
android signals

&lt;span class="c"&gt;# skills y documentación&lt;/span&gt;
android skills list
android skills add jetpack-compose-performance
android docs search &lt;span class="s2"&gt;"predictive back"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los números que publicó Google llaman la atención: los agentes que usaron &lt;code&gt;android preview&lt;/code&gt; terminaron tareas de UI &lt;strong&gt;77% más rápido&lt;/strong&gt; con &lt;strong&gt;hasta 3x menos tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins oficiales para Claude Code y Codex
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code (dentro de la sesión)&lt;/span&gt;
/plugin marketplace add android/android-cli-plugin
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;android-cli@android-cli-plugin

&lt;span class="c"&gt;# Claude Code (terminal)&lt;/span&gt;
claude plugin marketplace add android/android-cli-plugin
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;android-cli@android-cli-plugin

&lt;span class="c"&gt;# Codex&lt;/span&gt;
codex plugin marketplace add android/android-cli-plugin
codex plugin add android-cli@android-cli-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El plugin le da al agente lo necesario para crear proyectos, gestionar emuladores, inspeccionar layouts en vivo, ejecutar &lt;strong&gt;Journeys&lt;/strong&gt;, instalar skills oficiales y consultar la Android Knowledge Base.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills oficiales
&lt;/h3&gt;

&lt;p&gt;Son más de 30 skills en más de 20 áreas, cada una respaldada por evaluaciones. Apuntan a donde los modelos suelen fallar: rendimiento de Compose, APIs nuevas de cámara, auditorías de seguridad. Algunas que vale instalar ya:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jetpack-compose-performance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;android-profiler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8-analyzer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;play-policy-insights&lt;/code&gt; (audita código, manifest y SDKs contra las políticas de Play antes del envío)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;android-intent-security&lt;/code&gt; y &lt;code&gt;android-permissions-security&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repositorio: &lt;a href="https://github.com/android/skills" rel="noopener noreferrer"&gt;github.com/android/skills&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Validación: el agente revisa su propio trabajo
&lt;/h2&gt;

&lt;p&gt;El bloque "Validating apps with Android Prototype" del keynote (12:45) insiste en una idea: &lt;strong&gt;un buen agente verifica lo que hizo&lt;/strong&gt;. La demo más memorable fue con Claude Code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhpuyth7plsi9art4xzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhpuyth7plsi9art4xzc.png" alt="Bucle de validación agéntica" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google le dio al agente una lista de recorridos críticos y un único prompt — &lt;em&gt;encontrar y corregir memory leaks&lt;/em&gt;. Claude ejecutó cada recorrido en un dispositivo distinto, en paralelo, usó &lt;code&gt;android profiler leakcanary&lt;/code&gt;, siguió las cadenas de referencias hasta el código, lo corrigió y volvió a ejecutar todo para confirmar.&lt;/p&gt;

&lt;p&gt;Lo que sostiene ese flujo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Journeys&lt;/strong&gt; — flujos de usuario descritos en lenguaje natural. El equipo los escribe; el agente los ejecuta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeakCanary integrado&lt;/strong&gt; — en el CLI y en el Profiler de Studio, con &lt;strong&gt;Fix with AI&lt;/strong&gt; (Canary).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Performance Analyzer (APA)&lt;/strong&gt; — el motor del Profiler ahora también alimenta el CLI. Con la skill &lt;code&gt;android-profiler&lt;/code&gt;, el agente captura un trace, lo analiza, propone una corrección y mide el impacto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;androidx.tracing 2.0&lt;/strong&gt; — traces con archivo, línea y argumentos; las coroutines aparecen como flujos conectados. Empieza con &lt;code&gt;androidx.tracing:tracing-wire&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recomposition tracing (Compose 1.13)&lt;/strong&gt; — muestra &lt;em&gt;por qué&lt;/em&gt; algo se recompuso, hasta la escritura de estado que lo causó.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Del crash en producción a la corrección
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;android signals&lt;/code&gt; trae crashes, ANRs y problemas de memoria a la terminal. Y Crashlytics ahora recolecta &lt;strong&gt;heap dumps de OOMs reales&lt;/strong&gt; y de Memory Limiter Kills de Android 17, desofuscados con el mapping de R8 y abiertos en Perfetto (&lt;a href="https://goo.gle/crashlytics-memory" rel="noopener noreferrer"&gt;early access&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  R8 sin miedo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AGP 9.3: puntaje de tu configuración y ranking de las keep rules más costosas&lt;/span&gt;
./gradlew :app:analyzeReleaseR8Config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combínalo con la skill &lt;code&gt;r8-analyzer&lt;/code&gt; y reemplaza keep rules genéricas por &lt;strong&gt;Jetpack Keep&lt;/strong&gt; (alpha), anotaciones type-safe que lint puede agregar por ti.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Antes del deploy: el checklist en semáforo
&lt;/h2&gt;

&lt;p&gt;Uno de los momentos más prácticos del evento: en lugar de un checklist manual de publicación, le pides al agente que audite la app y te devuelve un reporte en &lt;strong&gt;semáforo&lt;/strong&gt; — 🟢 lo que está bien, 🟡 lo que necesita revisión, 🔴 lo que bloquea el envío.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkaegfjnja445mr9u0z2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkaegfjnja445mr9u0z2d.png" alt="El agente muestra, en semáforo, lo que falta antes del deploy" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  La skill oficial: &lt;code&gt;play-policy-insights&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Ese formato viene de la skill oficial &lt;strong&gt;play-policy-insights&lt;/strong&gt;, que audita tu código, manifest y SDKs contra las políticas de Google Play y lo cruza con tu declaración de Data safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Instala la skill&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;android skills add play-policy-insights

&lt;span class="c"&gt;# alternativa, fuera de Android CLI:&lt;/span&gt;
npx skills add https://github.com/android/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; play-policy-insights
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Ejecútala con un prompt&lt;/strong&gt; (este es el que sugiere Google):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audit this app for Google Play policy compliance before I submit it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Qué revisa&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data safety&lt;/strong&gt; — compara lo que tu código recolecta y transmite con lo que declaraste en Play Console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credenciales de login de demostración&lt;/strong&gt; — si los revisores de Play pueden entrar a la app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permisos restringidos&lt;/strong&gt; — permisos sensibles u obsoletos donde se espera una alternativa con alcance limitado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Cómo leer el reporte&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Estado general&lt;/th&gt;
&lt;th&gt;Significado&lt;/th&gt;
&lt;th&gt;Qué hacer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟢 Compliant&lt;/td&gt;
&lt;td&gt;No se encontraron violaciones activas&lt;/td&gt;
&lt;td&gt;Seguir con el envío&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟡 Needs Review&lt;/td&gt;
&lt;td&gt;Sin violaciones críticas, pero hay declaraciones manuales en Play Console o permisos restringidos para revisar&lt;/td&gt;
&lt;td&gt;Revisar antes de enviar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴 Non-Compliant&lt;/td&gt;
&lt;td&gt;Violaciones o diferencias en Data safety&lt;/td&gt;
&lt;td&gt;Corregir antes del envío&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cada hallazgo tiene severidad — 🔴 &lt;strong&gt;Critical&lt;/strong&gt; (violación confirmada), 🟡 &lt;strong&gt;Important&lt;/strong&gt; (por ejemplo, un permiso obsoleto donde se espera uno con alcance), 🔵 &lt;strong&gt;Suggestion&lt;/strong&gt; (recomendación) — e incluye la política, los archivos afectados, la evidencia, un nivel de confianza, una recomendación y enlaces. El reporte termina con diferencias de Data safety, accesos locales a datos y un &lt;strong&gt;checklist de remediación&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Dos cuidados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El reporte es &lt;strong&gt;consultivo&lt;/strong&gt;: no es asesoría legal ni garantía de aprobación.&lt;/li&gt;
&lt;li&gt;La skill escribe archivos temporales en &lt;code&gt;.scratch/play_policy_insights_&amp;lt;uuid&amp;gt;&lt;/code&gt; en la raíz del proyecto. Agrega &lt;code&gt;.scratch/&lt;/code&gt; al &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En Android Studio, &lt;strong&gt;Play Policy Insights&lt;/strong&gt; también aparece como verificaciones de lint directamente en el código.&lt;/p&gt;

&lt;h3&gt;
  
  
  Más allá: una skill de "release readiness" con todos los gates
&lt;/h3&gt;

&lt;p&gt;La política de Play es solo una de las puertas. Con las herramientas de Android CLI puedes escribir tu propia skill que ejecute &lt;strong&gt;todos&lt;/strong&gt; los gates y devuelva un único semáforo. Este es un ejemplo que escribí para estudio — adapta rutas y umbrales a tu proyecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-readiness&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Úsala antes de publicar una release. Ejecuta los gates de build, tamaño, política de Play, seguridad, estabilidad y recorridos críticos y devuelve un reporte en semáforo.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Release readiness&lt;/span&gt;

Ejecuta todos los gates de abajo, en orden. No te detengas en la primera falla: el reporte debe mostrar todos.

&lt;span class="gu"&gt;## Gates&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Build**&lt;/span&gt; — &lt;span class="sb"&gt;`./gradlew :app:bundleRelease`&lt;/span&gt;. 🔴 si falla.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Tamaño y R8**&lt;/span&gt; — ejecuta &lt;span class="sb"&gt;`android analyze`&lt;/span&gt; sobre el AAB de release y &lt;span class="sb"&gt;`./gradlew :app:analyzeReleaseR8Config`&lt;/span&gt;.
   🟡 si el puntaje de shrinking bajó respecto de &lt;span class="sb"&gt;`docs/release/last-analyze.json`&lt;/span&gt;. 🔴 si la ofuscación está desactivada.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Política de Play**&lt;/span&gt; — ejecuta la skill &lt;span class="sb"&gt;`play-policy-insights`&lt;/span&gt; y copia su estado general.
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Seguridad**&lt;/span&gt; — ejecuta las skills &lt;span class="sb"&gt;`android-permissions-security`&lt;/span&gt; y &lt;span class="sb"&gt;`android-intent-security`&lt;/span&gt;. 🔴 ante cualquier hallazgo Critical.
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Estabilidad**&lt;/span&gt; — &lt;span class="sb"&gt;`android signals`&lt;/span&gt; para la versión actual en producción. 🟡 ante nuevos clusters de crash o ANR.
   🔴 si los usuarios sin crash cayeron por debajo del umbral de &lt;span class="sb"&gt;`docs/release/thresholds.md`&lt;/span&gt;.
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Recorridos críticos**&lt;/span&gt; — ejecuta las Journeys de &lt;span class="sb"&gt;`journeys/`&lt;/span&gt;. 🔴 si alguna falla.

&lt;span class="gu"&gt;## Salida&lt;/span&gt;

Escribe &lt;span class="sb"&gt;`release-report.md`&lt;/span&gt; con:
&lt;span class="p"&gt;-&lt;/span&gt; el estado general (el peor gate define el estado);
&lt;span class="p"&gt;-&lt;/span&gt; una fila por gate: estado, gate, evidencia, próxima acción;
&lt;span class="p"&gt;-&lt;/span&gt; un checklist de remediación.

No publiques, no subas nada y no crees tags. Publicar es una decisión humana.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En Claude Code, guárdala en &lt;code&gt;.claude/skills/release-readiness/SKILL.md&lt;/code&gt; y llámala con &lt;code&gt;/release-readiness&lt;/code&gt;. En otros agentes, usa su carpeta de skills equivalente. La regla "el peor gate define el estado" es lo que convierte seis verificaciones en un semáforo único y honesto.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Compose-first en la práctica: de XML a Compose con un clic derecho
&lt;/h2&gt;

&lt;p&gt;Otro punto destacado del keynote: migrar una pantalla de XML Views a Jetpack Compose dejó de ser un proyecto manual. El &lt;strong&gt;Migration Assistant&lt;/strong&gt; (Android Studio Canary) aparece en el menú contextual del layout y le delega el trabajo a un agente.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwqnxue2tqcq69pvel2l5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwqnxue2tqcq69pvel2l5.png" alt="Flujo del Migration Assistant" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso a paso
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instala el Android Studio Canary más reciente&lt;/strong&gt; — la función está en ese canal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elige el agente.&lt;/strong&gt; Funciona con el agente integrado de Studio o con cualquier agente agregado vía Bring Your Own Agent (Claude, Codex…). También conviene instalar la skill oficial &lt;strong&gt;Migrate to Compose&lt;/strong&gt;, que aparece en &lt;code&gt;android skills list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Haz clic derecho en el layout XML&lt;/strong&gt; (por ejemplo, &lt;code&gt;res/layout/fragment_profile.xml&lt;/code&gt;) y elige &lt;strong&gt;AI › Migrate to Compose&lt;/strong&gt;. Para migrar todo el proyecto, abre la &lt;strong&gt;action palette&lt;/strong&gt; y ejecuta la migración desde ahí.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define el alcance con el agente.&lt;/strong&gt; Te pregunta qué migrar ahora, qué queda en interoperabilidad y cómo tratar las vistas personalizadas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revisa el plan.&lt;/strong&gt; El agente arma un plan por etapas antes de tocar cualquier archivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversión.&lt;/strong&gt; El agente convierte el &lt;strong&gt;layout&lt;/strong&gt;, el &lt;strong&gt;código de view binding&lt;/strong&gt; y la &lt;strong&gt;lógica&lt;/strong&gt; asociada en Compose idiomático.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sigue la timeline.&lt;/strong&gt; La ventana del agente muestra qué etapa está corriendo y desde dónde retomar si se detiene. El progreso se guarda con la conversación: puedes irte y volver después.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valida.&lt;/strong&gt; Revisa los Compose Previews (o &lt;code&gt;android preview&lt;/code&gt; en la terminal) y ejecuta &lt;strong&gt;Change Review&lt;/strong&gt; con &lt;code&gt;/review&lt;/code&gt; antes del commit.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Ejemplo: lo que entra y lo que sale
&lt;/h3&gt;

&lt;p&gt;Un ejemplo ilustrativo de este tipo de conversión — una pantalla de perfil con ViewBinding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Antes (XML + ViewBinding):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- res/layout/fragment_profile.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LinearLayout&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
    &lt;span class="na"&gt;android:orientation=&lt;/span&gt;&lt;span class="s"&gt;"vertical"&lt;/span&gt;
    &lt;span class="na"&gt;android:padding=&lt;/span&gt;&lt;span class="s"&gt;"16dp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/name"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:textAppearance=&lt;/span&gt;&lt;span class="s"&gt;"?attr/textAppearanceHeadlineSmall"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/role"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/follow"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:text=&lt;/span&gt;&lt;span class="s"&gt;"@string/follow"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/LinearLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileFragment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment_profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;viewModels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onViewCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FragmentProfileBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeatOnLifecycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFollowing&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Después (Compose idiomático, con estado elevado):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileUiState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headlineSmall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFollowing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stringResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Preview&lt;/span&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileScreenPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProfileUiState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Android Engineer"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Durante la transición&lt;/strong&gt;, el Fragment puede seguir existiendo y solo alojar Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileFragment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inflater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LayoutInflater&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewGroup&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ComposeView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;requireContext&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setViewCompositionStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ViewCompositionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DisposeOnViewTreeLifecycleDestroyed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist para revisar lo que generó el agente
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] ¿El estado está &lt;strong&gt;elevado&lt;/strong&gt; (Composable sin estado + un Route que recolecta el ViewModel)?&lt;/li&gt;
&lt;li&gt;[ ] ¿Los textos usan &lt;code&gt;stringResource&lt;/code&gt; en lugar de strings fijos?&lt;/li&gt;
&lt;li&gt;[ ] ¿El tema del XML se mapeó a &lt;code&gt;MaterialTheme&lt;/code&gt; (colores, tipografía, formas)?&lt;/li&gt;
&lt;li&gt;[ ] ¿Se preservó la accesibilidad: &lt;code&gt;contentDescription&lt;/code&gt;, tamaños táctiles, orden de lectura?&lt;/li&gt;
&lt;li&gt;[ ] ¿Las pruebas de UI pasaron a &lt;code&gt;composeTestRule&lt;/code&gt; (o screenshot tests)?&lt;/li&gt;
&lt;li&gt;[ ] ¿Sin recomposiciones innecesarias? Usa el &lt;strong&gt;recomposition tracing&lt;/strong&gt; de Compose 1.13 y la skill &lt;code&gt;jetpack-compose-performance&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] ¿Se eliminaron el XML y la clase de binding antiguos una vez estabilizada la pantalla?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Estrategia recomendada:&lt;/strong&gt; migra pantalla por pantalla, empezando por las más simples o las que vas a tocar de todos modos, y usa &lt;code&gt;ComposeView&lt;/code&gt; como puente. Migrar toda la app de una vez genera un diff imposible de revisar — incluso con un agente.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Compose-first: estado fuera de la UI con Jetpack AppState
&lt;/h2&gt;

&lt;p&gt;Si alguna vez peleaste con un &lt;code&gt;combine&lt;/code&gt; de cinco Flows, esto te va a gustar. &lt;strong&gt;Jetpack AppState&lt;/strong&gt; (alpha) usa el runtime de Compose fuera de la UI, en ViewModels, repositorios y servicios. Son dos piezas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transform&lt;/strong&gt; — ejecuta un bloque &lt;code&gt;@Composable&lt;/code&gt; sin UI y devuelve un &lt;code&gt;State&amp;lt;R&amp;gt;&lt;/code&gt; que se recompone solo cuando cambia cualquier estado leído.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StateStore&lt;/strong&gt; — un mapa type-safe de estado compartido entre activities, services y widgets, con persistencia opcional en DataStore.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;appstate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.0.0-alpha01"&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;libraries&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.transform:transform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-transform-testing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.transform:transform-testing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-statestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.statestore:statestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Ojo: todo módulo que llama a &lt;code&gt;transform&lt;/code&gt; necesita &lt;code&gt;buildFeatures { compose = true }&lt;/code&gt; y el plugin del Compose Compiler, aunque no tenga UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Un ViewModel que cambia de fuente según la conectividad:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MediaRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;networkMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NetworkMonitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isOnline&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;networkMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isOnline&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tracks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allTracksFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadedTracksFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@transform&lt;/span&gt; &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;

        &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin &lt;code&gt;flatMapLatest&lt;/code&gt;, sin &lt;code&gt;stateIn&lt;/code&gt;: un &lt;code&gt;if&lt;/code&gt; común decide qué fuente observar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debounce de búsqueda&lt;/strong&gt; con &lt;code&gt;LaunchedEffect&lt;/code&gt;, dentro de &lt;code&gt;transform&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;searchResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBlank&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@LaunchedEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cuándo NO usar &lt;code&gt;transform&lt;/code&gt;:&lt;/strong&gt; representa el &lt;em&gt;valor actual&lt;/em&gt;, no un flujo de eventos. Las actualizaciones rápidas antes de la recomposición se descartan y los valores iguales consecutivos no disparan nada. Para totales acumulados, logs, eventos únicos y backpressure, sigue con &lt;code&gt;Flow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Las pruebas&lt;/strong&gt; son simples con &lt;code&gt;transformTest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testStateUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transformTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;sourceState&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backgroundScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testDispatcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceState&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sourceState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;advance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completan el paquete &lt;strong&gt;Compose 1.13&lt;/strong&gt; (stable pronto: Grid y FlexBox stable, Progressive Blur, Formatted Text) y &lt;strong&gt;Navigation 3 1.2&lt;/strong&gt; (stable: deep links y predictive back más rico). Ejemplos: &lt;a href="https://github.com/android/appstate-recipes" rel="noopener noreferrer"&gt;github.com/android/appstate-recipes&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. A2UI: cuando la respuesta del agente es una pantalla
&lt;/h2&gt;

&lt;p&gt;Un muro de texto en un chat no es buena UX. Con &lt;strong&gt;A2UI (Agent-to-UI)&lt;/strong&gt;, el agente envía JSON declarativo y tu app renderiza &lt;strong&gt;componentes nativos de tu propio catálogo&lt;/strong&gt; — sin ejecutar código arbitrario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faeaek3u0lc81hu0uwixd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faeaek3u0lc81hu0uwixd.png" alt="Flujo de A2UI en Jetpack Compose" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-runtime:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-ui:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;androidTestImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-ui-testing:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ViewModel:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgenticUiViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;parser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;A2uiMessageParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;A2uiMessageProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalogs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomDesignSystemCatalog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;a2uiSurfaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;A2uiSurfaceModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeSurfaces&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// clics y errores vuelven al agente&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CoroutineStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UNDISPATCHED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outboundEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;handleOutboundA2uiEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNetworkMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pantalla:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AgenticUiScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AgenticUiViewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;surfaces&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a2uiSurfaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;surfaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;A2uiSurface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surfaceModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo que más me gustó: las &lt;strong&gt;defensas contra alucinaciones&lt;/strong&gt; vienen incluidas. Un componente desconocido se convierte en un error reportado al agente para que se corrija; el payload se valida contra un schema; los índices de lista absurdos pasan a un mapa disperso para no agotar la memoria.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Gemini Nano 4: un agente completo en el dispositivo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemini Nano 4&lt;/strong&gt; trajo function calling nativo, y ML Kit sumó una &lt;strong&gt;Agentic API&lt;/strong&gt; (alpha) que ejecuta el bucle agéntico completo en el dispositivo, sobre AICore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevlt8wwa0jmoflp75adw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevlt8wwa0jmoflp75adw.png" alt="Gemini Nano 4 y la ML Kit Agentic API" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requisitos:&lt;/strong&gt; Android 14+ inscrito en el AICore Developer Preview, un dispositivo compatible, &lt;code&gt;minSdk 26&lt;/code&gt;, Kotlin 2.3+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.google.mlkit:genai-agent:1.0.0-alpha1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// opcional: tools vía AppFunctions&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appfunctions:appfunctions:1.0.0-alpha13"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;ksp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Assistant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInstructions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You are a helpful on-device assistant."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreateSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat_session"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Can you help me organize my day?"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textTokens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// sin historial: runner.runSingleTurnAsync(prompt)&lt;/span&gt;
&lt;span class="c1"&gt;// al destruir: runner.close()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Límites que debes conocer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;maxTurns&lt;/code&gt; es 10 por defecto, para evitar bucles infinitos y ahorrar batería.&lt;/li&gt;
&lt;li&gt;La inferencia solo corre con la app en primer plano; demasiadas llamadas devuelven &lt;code&gt;ErrorCode.BUSY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;El modelo puede elegir la tool o el argumento equivocado: &lt;strong&gt;la seguridad de las acciones es responsabilidad de tu app&lt;/strong&gt;. Confirma con el usuario las acciones sensibles.&lt;/li&gt;
&lt;li&gt;Para bases de conocimiento enormes, el propio Google recomienda una arquitectura híbrida con modelos en la nube.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Android MCP y AppFunctions: tu app como tool de Gemini
&lt;/h2&gt;

&lt;p&gt;Para mí, este es el cambio más estratégico del evento. &lt;strong&gt;AppFunctions&lt;/strong&gt; es el equivalente móvil de las &lt;em&gt;tools&lt;/em&gt; de MCP: tu app se convierte en un servidor MCP on-device, y Gemini (u otro agente autorizado) descubre y llama a funciones de tu app para cumplir lo que pide el usuario. Está en &lt;strong&gt;beta&lt;/strong&gt; y funciona en Android 16+.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffv860gmiej6p0xguves1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffv860gmiej6p0xguves1.png" alt="Cómo una función de tu app se vuelve una tool de Gemini" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequiresApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@AndroidEntryPoint&lt;/span&gt;
&lt;span class="nd"&gt;@AppFunctionServiceEntryPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;serviceName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TaskAppFunctionService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appFunctionXmlFileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"task_app_function_service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseTaskAppFunctionService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TaskRepository&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@AppFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDescribedByKDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CreateTaskParams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionInvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Title or content should be non-null"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionElementNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task not found for ID = $id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** The parameter to create the task. */&lt;/span&gt;
&lt;span class="nd"&gt;@AppFunctionSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDescribedByKDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;CreateTaskParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="cm"&gt;/** The title of the task. */&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="cm"&gt;/** The content of the task. */&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fíjate en dos detalles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;El KDoc es el prompt.&lt;/strong&gt; Con &lt;code&gt;isDescribedByKDoc = true&lt;/code&gt;, la documentación se convierte en la descripción que lee el agente para decidir cuándo llamar a la función. Escríbela pensando en el modelo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Las excepciones tipadas&lt;/strong&gt; (&lt;code&gt;AppFunctionInvalidArgumentException&lt;/code&gt;, &lt;code&gt;AppFunctionElementNotFoundException&lt;/code&gt;) le dicen al agente &lt;em&gt;por qué&lt;/em&gt; falló, para que intente de otra manera.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Probando sin Gemini:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell cmd app_function list-app-functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AppFunctions vs servidor MCP remoto:&lt;/strong&gt; AppFunctions corre localmente, usa el estado real de la app y no necesita backend. Un MCP remoto es agnóstico de plataforma y depende de la red. Los agentes consideran ambos juntos.&lt;/p&gt;

&lt;p&gt;Para llegar a los usuarios, la &lt;strong&gt;Connected Apps Platform&lt;/strong&gt; (preview) lleva los servicios de tu app a Gemini y a la Búsqueda: construyes con Android MCP o MCP remoto, prototipas en el Gemini dev mode y te registras en Google Connected Console.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Realidad a oct/2026: la integración con Gemini todavía está limitada a apps seleccionadas. Puedes implementarla y probarla hoy; para producción hay un Early Access Program.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. Un código, muchas pantallas: Googlebook, plegables y XR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs6ythawtf50z4ojypqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs6ythawtf50z4ojypqq.png" alt="Un código, muchas pantallas" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Googlebook&lt;/strong&gt; es la nueva categoría de laptops sobre una base Android, con socios como HP, Dell, Lenovo, Acer y Asus. La misma app corre en el teléfono y en la laptop, sin un código separado.&lt;/p&gt;

&lt;p&gt;La &lt;strong&gt;Compose adaptive skill&lt;/strong&gt; se actualizó para que el agente haga el trabajo pesado: atajos de teclado, cursores, hover y tooltips; migración a Grid, FlexBox y &lt;code&gt;mediaQuery&lt;/code&gt;; manejo de estado de punta a punta; y posturas de plegables, como layouts tabletop para pantallas de multimedia y cámara.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 Aviso de Google Play: en 2027 Play revisará de cerca cómo corren las apps y los juegos en plegables. Sigue las &lt;a href="https://developer.android.com/docs/quality-guidelines/adaptive-app-quality" rel="noopener noreferrer"&gt;adaptive app quality guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Android XR y XREAL AURA
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;XREAL AURA&lt;/strong&gt; son las primeras gafas XR con cable que corren Android XR. Pesan menos de 95 g porque el procesamiento va en un "puck" con Snapdragon Reality Elite. Tienen pantalla Sony Micro-OLED 1200p a 120 Hz, ejecutan apps de Google Play y hasta cinco ventanas virtuales. Precio: US$ 1.099 de lanzamiento hasta diciembre (US$ 1.279 después), a la venta en Estados Unidos, Canadá, Japón y Corea del Sur.&lt;/p&gt;

&lt;p&gt;Para los devs, lo más importante: &lt;strong&gt;el núcleo de Jetpack XR ya es stable&lt;/strong&gt; (SceneCore, ARCore for Jetpack XR y XR Runtime), igual que los &lt;strong&gt;emuladores de gafas XR&lt;/strong&gt; en Android Studio y el &lt;strong&gt;Android XR Engine Hub&lt;/strong&gt;. Compose for XR sigue en alpha.&lt;/p&gt;

&lt;p&gt;El primer paso es espacializar una pantalla que ya tienes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Subspace&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SpatialPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;SubspaceModifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;824&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1400&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformingMovable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MyExistingScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// tu pantalla Compose actual&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El contenido dentro de &lt;code&gt;Subspace&lt;/code&gt; solo se muestra cuando la espacialización está activa. En el teléfono se ignora, así que el mismo código sirve para ambos mundos.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Más allá del keynote: 5 laboratorios para esta semana
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Un agente con ojos y manos (stable)&lt;/strong&gt; — instala Android CLI y el plugin en Claude Code o Codex, escribe 3 Journeys de tus flujos críticos y pide: "ejecuta los recorridos, captura un trace de inicio y propón la corrección de mayor impacto". Mide el cold start antes y después.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calidad en CI (stable)&lt;/strong&gt; — ejecuta &lt;code&gt;android analyze&lt;/code&gt; sobre el AAB de release, guarda el JSON como artefacto y haz fallar el build si baja el puntaje de shrinking. Usa &lt;code&gt;analyzeReleaseR8Config&lt;/code&gt; para atacar las 5 keep rules más costosas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un ViewModel sin &lt;code&gt;combine&lt;/code&gt; (alpha)&lt;/strong&gt; — reescribe un ViewModel con 3+ fuentes usando &lt;code&gt;transform&lt;/code&gt; y pruébalo con &lt;code&gt;transformTest&lt;/code&gt;. Compara la legibilidad y verifica si se perdió algún evento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tu app como tool (beta)&lt;/strong&gt; — usa la skill de AppFunctions para encontrar las 3 funciones de mayor valor, implementa una y valídala con &lt;code&gt;adb shell cmd app_function list-app-functions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Una respuesta como UI (alpha)&lt;/strong&gt; — arma un catálogo A2UI con 3 componentes de tu design system y aliméntalo con JSON fijo antes de conectar un modelo real. Prueba el camino de error.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Lo que deja el Dev Summit
&lt;/h2&gt;

&lt;p&gt;El regreso del Android Dev Summit no estuvo marcado por una API aislada. Estuvo marcado por un cambio en el rol de quien desarrolla para Android: &lt;strong&gt;orquestar agentes que construyen, validan y optimizan la app — y diseñar la app para que la usen agentes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si quieres actuar ya, esta es mi lectura:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Para adoptar hoy:&lt;/strong&gt; Rabbit 2 con BYOA, Android CLI con skills, Change Review y el checklist en semáforo en el flujo del equipo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Este trimestre:&lt;/strong&gt; migrar las pantallas XML que más se tocan, definir qué funciones de la app serán AppFunctions y preparar la UI adaptativa para plegables y Googlebook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;En POC:&lt;/strong&gt; A2UI, Gemini Nano 4 y AppState, siguiendo las APIs en su camino a stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿Cuál de estas novedades vas a probar primero? Cuéntame en los comentarios. 👇&lt;/p&gt;




&lt;h3&gt;
  
  
  Fuentes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8Lmv7LGgL4E" rel="noopener noreferrer"&gt;Android Dev Summit 2026 keynote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/blog/posts/build-it-your-way-a-next-era-of-android-development" rel="noopener noreferrer"&gt;Agentic and Flexible: A next era of Android development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/blog/posts/android-dev-summit-26-developer-tools-performance" rel="noopener noreferrer"&gt;What's new in Android developer tools and performance at Android Dev Summit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/tools/agents/android-skills/browse" rel="noopener noreferrer"&gt;Android skills (catálogo)&lt;/a&gt; y &lt;a href="https://www.skills.sh/android/skills/play-policy-insights" rel="noopener noreferrer"&gt;play-policy-insights&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/ai/appfunctions" rel="noopener noreferrer"&gt;Overview of AppFunctions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/agentic/render-surfaces" rel="noopener noreferrer"&gt;Render A2UI surfaces in your app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/ml-kit/genai/agent/android" rel="noopener noreferrer"&gt;Build agentic workflows with Gemini Nano&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/topic/libraries/architecture/appstate" rel="noopener noreferrer"&gt;Jetpack AppState&lt;/a&gt; y &lt;a href="https://developer.android.com/topic/libraries/architecture/appstate/transform" rel="noopener noreferrer"&gt;Transform&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/develop-ui" rel="noopener noreferrer"&gt;Develop UI with Jetpack Compose for XR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcrunch.com/2026/10/28/xreal-aura-is-a-new-wired-headset-powered-by-android-xr/" rel="noopener noreferrer"&gt;TechCrunch — Xreal Aura&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Este artículo también está disponible en portugués e inglés.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>ai</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>Android Dev Summit 2026: Google brings developers back together after four years and puts agents at the center of Android</title>
      <dc:creator>Diego Ferreira Caetano</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:43:04 +0000</pubDate>
      <link>https://dev.to/diego_f_caetano/android-dev-summit-2026-google-brings-developers-back-together-after-four-years-and-puts-agents-at-3jjo</link>
      <guid>https://dev.to/diego_f_caetano/android-dev-summit-2026-google-brings-developers-back-together-after-four-years-and-puts-agents-at-3jjo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8d4e0ljggjy3oabjua8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8d4e0ljggjy3oabjua8.png" alt="Android Dev Summit 2026: Google brings developers back together after four years and puts agents at the center of Android" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mountain View, October 28, 2026&lt;/strong&gt; — After four years without a dedicated event, Google brought back the &lt;strong&gt;Android Dev Summit&lt;/strong&gt;. The last edition was in 2022; since then, platform news had to share the stage with the rest of Google I/O. This time it was two days devoted entirely to Android, at the Bay View campus, with the keynote streamed on YouTube and an extended edition in London.&lt;/p&gt;

&lt;p&gt;The keynote's message was blunt: Android is now built &lt;strong&gt;with&lt;/strong&gt; AI agents and &lt;strong&gt;for&lt;/strong&gt; AI agents. Android Studio opened its doors to agents from other companies, such as Claude and Codex. Android CLI gave those agents access to real devices. And apps gained a standard way to offer their functions to Gemini.&lt;/p&gt;

&lt;p&gt;In this briefing I gather what was announced, how mature each piece is and — the part the keynote moves through quickly — how to use each one day to day, with code.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8Lmv7LGgL4E" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Event fact sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;When&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;October 28–29, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google's Bay View campus, Mountain View (US), plus an extended edition in London&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First Android Dev Summit since 2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI experiences, Android XR, Apps foundation, Tools &amp;amp; performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keynote&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;About one hour, with live demos — &lt;a href="https://www.youtube.com/watch?v=8Lmv7LGgL4E" rel="noopener noreferrer"&gt;watch on YouTube&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The headlines
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android Studio Rabbit 2 reaches stable&lt;/strong&gt; and accepts any ACP-compatible coding agent — Claude, Codex, Antigravity and 40+ others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android CLI gains remote physical devices&lt;/strong&gt;, Compose Preview rendering in the terminal, a LeakCanary-powered profiler and official plugins for Claude Code and Codex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A traffic-light release checklist&lt;/strong&gt;: the &lt;code&gt;play-policy-insights&lt;/code&gt; skill audits your app before you submit to Google Play and flags 🟢🟡🔴 what's missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XML-to-Compose migration with a right-click&lt;/strong&gt;, driven by an agent inside Android Studio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AppFunctions goes beta&lt;/strong&gt;: functions in your app become &lt;em&gt;tools&lt;/em&gt; for Gemini and other agents, through &lt;strong&gt;Android MCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2UI (alpha)&lt;/strong&gt;: agents can now answer with native Compose screens built from your design system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini Nano 4 + ML Kit Agentic API (alpha)&lt;/strong&gt;: a whole agent running on the device, with function calling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jetpack AppState (alpha)&lt;/strong&gt; brings Compose's state model to ViewModels and repositories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Googlebook, foldables and Android XR&lt;/strong&gt;: one codebase for many screens. &lt;strong&gt;XREAL AURA&lt;/strong&gt; debuts as the first wired XR glasses running Android XR.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. What you can ship today (and what is still a lab)
&lt;/h2&gt;

&lt;p&gt;Before installing everything, check how mature each announcement is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiw7znqtdyyi71c71nskc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiw7znqtdyyi71c71nskc.png" alt="Maturity map of the announcements" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My rule of thumb: &lt;strong&gt;stable&lt;/strong&gt; goes to production, &lt;strong&gt;beta&lt;/strong&gt; goes behind a feature flag, &lt;strong&gt;alpha and preview&lt;/strong&gt; stay in POCs and study projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Android Studio Rabbit 2: bring your own agent
&lt;/h2&gt;

&lt;p&gt;Android Studio is no longer "the IDE with Gemini". With &lt;strong&gt;Bring Your Own Agent (BYOA)&lt;/strong&gt;, now stable in Rabbit 2, it hosts any agent that speaks the &lt;strong&gt;Agent Client Protocol (ACP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to turn it on:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings &amp;gt; Tools &amp;gt; AI &amp;gt; Agents&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Install agents from the marketplace (Claude Agent, Codex, Antigravity…). You can keep several and switch.&lt;/li&gt;
&lt;li&gt;The agent now sees what only the IDE knows: project and build structure, symbol resolution, Compose Preview rendering, the Emulator and Logcat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three more features change the daily workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change Review&lt;/strong&gt; — agentic review of the changes your agent made. Start it from pending agent changes, the Git window, or with &lt;code&gt;/review&lt;/code&gt;. You get an issues report and a guided walkthrough for large changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration Assistant (Canary)&lt;/strong&gt; — right-click an XML layout &amp;gt; &lt;strong&gt;AI › Migrate to Compose&lt;/strong&gt; and the agent migrates the screen to Compose. There's a full section on it further down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Bench 2.0&lt;/strong&gt; — a leaderboard that scores models and agent harnesses on long Android tasks (migrating UI, building an app from mocks), measuring whether the result actually works, plus cost and time. Great for picking your team's agent: &lt;a href="https://developer.android.com/bench" rel="noopener noreferrer"&gt;developer.android.com/bench&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ACP vs MCP: don't mix them up
&lt;/h3&gt;

&lt;p&gt;This is the point I see causing the most confusion. Two protocols, two jobs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futr5dvmhk9xf38wcetrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futr5dvmhk9xf38wcetrx.png" alt="ACP helps you build; MCP lets agents use your app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Android CLI: the whole toolchain in your agent's hands
&lt;/h2&gt;

&lt;p&gt;Android CLI is the tool-first interface for any terminal, agent or CI pipeline. The idea is simple: your agent stops burning tokens guessing how Android works and uses official tools instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install the CLI and set up your agent&lt;/span&gt;
android init

&lt;span class="c"&gt;# a real physical device from a Google device lab (via Firebase)&lt;/span&gt;
android device remote connect
android device hub            &lt;span class="c"&gt;# window to view and control devices and emulators&lt;/span&gt;

&lt;span class="c"&gt;# Compose Previews with no emulator and no IDE&lt;/span&gt;
android preview

&lt;span class="c"&gt;# performance and memory&lt;/span&gt;
android profiler systemtrace
android profiler heapdump capture
android profiler leakcanary

&lt;span class="c"&gt;# binary quality and production signals&lt;/span&gt;
android analyze app-release.aab
android signals

&lt;span class="c"&gt;# skills and docs&lt;/span&gt;
android skills list
android skills add jetpack-compose-performance
android docs search &lt;span class="s2"&gt;"predictive back"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google's numbers stand out: agents using &lt;code&gt;android preview&lt;/code&gt; finished UI tasks &lt;strong&gt;77% faster&lt;/strong&gt; with &lt;strong&gt;up to 3x fewer tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Official plugins for Claude Code and Codex
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code (inside a session)&lt;/span&gt;
/plugin marketplace add android/android-cli-plugin
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;android-cli@android-cli-plugin

&lt;span class="c"&gt;# Claude Code (terminal)&lt;/span&gt;
claude plugin marketplace add android/android-cli-plugin
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;android-cli@android-cli-plugin

&lt;span class="c"&gt;# Codex&lt;/span&gt;
codex plugin marketplace add android/android-cli-plugin
codex plugin add android-cli@android-cli-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin gives your agent what it needs to create projects, manage emulators, inspect live layouts, run &lt;strong&gt;Journeys&lt;/strong&gt;, install official skills and search the Android Knowledge Base.&lt;/p&gt;

&lt;h3&gt;
  
  
  Official skills
&lt;/h3&gt;

&lt;p&gt;There are 30+ skills across 20+ areas, each backed by evaluations. They target the areas where models tend to fall short: Compose performance, new camera APIs, security audits. A few worth installing now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jetpack-compose-performance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;android-profiler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8-analyzer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;play-policy-insights&lt;/code&gt; (audits code, manifest and SDKs against Play policies before you submit)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;android-intent-security&lt;/code&gt; and &lt;code&gt;android-permissions-security&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/android/skills" rel="noopener noreferrer"&gt;github.com/android/skills&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Validation: the agent checks its own work
&lt;/h2&gt;

&lt;p&gt;The "Validating apps with Android Prototype" segment of the keynote (12:45) drives home one idea: &lt;strong&gt;a good agent verifies what it did&lt;/strong&gt;. The most memorable demo used Claude Code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ireu1qk2inb395mygs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ireu1qk2inb395mygs8.png" alt="Agentic validation loop" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google gave the agent a list of critical user journeys and a single prompt — &lt;em&gt;find and fix memory leaks&lt;/em&gt;. Claude ran each journey on a separate device in parallel, used &lt;code&gt;android profiler leakcanary&lt;/code&gt;, followed each reference chain back to the code, fixed it and re-ran everything to confirm.&lt;/p&gt;

&lt;p&gt;What powers this workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Journeys&lt;/strong&gt; — user flows written in natural language. Your team writes them; the agent runs them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in LeakCanary&lt;/strong&gt; — in the CLI and in the Studio Profiler, with &lt;strong&gt;Fix with AI&lt;/strong&gt; (Canary).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Performance Analyzer (APA)&lt;/strong&gt; — the Profiler's engine now powers the CLI too. With the &lt;code&gt;android-profiler&lt;/code&gt; skill, the agent captures a trace, analyzes it, proposes a fix and measures the impact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;androidx.tracing 2.0&lt;/strong&gt; — trace sections with file, line and arguments; coroutines show up as connected flows. Start with &lt;code&gt;androidx.tracing:tracing-wire&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recomposition tracing (Compose 1.13)&lt;/strong&gt; — shows &lt;em&gt;why&lt;/em&gt; something recomposed, back to the state write that caused it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  From a production crash to a fix
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;android signals&lt;/code&gt; brings crashes, ANRs and memory issues to the terminal. Crashlytics now collects &lt;strong&gt;heap dumps from real OOMs&lt;/strong&gt; and Android 17 Memory Limiter Kills, deobfuscated with your R8 mapping and opened in Perfetto (&lt;a href="https://goo.gle/crashlytics-memory" rel="noopener noreferrer"&gt;early access&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  R8 without fear
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AGP 9.3: a score for your config and a ranking of the costliest keep rules&lt;/span&gt;
./gradlew :app:analyzeReleaseR8Config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pair it with the &lt;code&gt;r8-analyzer&lt;/code&gt; skill and replace broad keep rules with &lt;strong&gt;Jetpack Keep&lt;/strong&gt; (alpha), type-safe annotations that lint can add for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Before you ship: the traffic-light checklist
&lt;/h2&gt;

&lt;p&gt;One of the most practical moments of the event: instead of a manual release checklist, you ask the agent to audit the app and it returns a &lt;strong&gt;traffic-light&lt;/strong&gt; report — 🟢 what's fine, 🟡 what needs a review, 🔴 what blocks submission.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4s26ajwraj3gk6ogr8re.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4s26ajwraj3gk6ogr8re.png" alt="The agent shows what's still missing before release, traffic-light style" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The official skill: &lt;code&gt;play-policy-insights&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This format comes from the official &lt;strong&gt;play-policy-insights&lt;/strong&gt; skill, which audits your code, manifest and SDKs against Google Play policies and cross-checks your Data safety declaration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install the skill&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;android skills add play-policy-insights

&lt;span class="c"&gt;# alternative, outside Android CLI:&lt;/span&gt;
npx skills add https://github.com/android/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; play-policy-insights
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Run it with a prompt&lt;/strong&gt; (this is the one Google suggests):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audit this app for Google Play policy compliance before I submit it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. What it checks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data safety&lt;/strong&gt; — compares what your code collects and transmits with what you declared in the Play Console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo login credentials&lt;/strong&gt; — whether Play reviewers can actually get into the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restricted permissions&lt;/strong&gt; — sensitive or deprecated permissions where a scoped alternative is expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. How to read the report&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Overall status&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟢 Compliant&lt;/td&gt;
&lt;td&gt;No active violations found&lt;/td&gt;
&lt;td&gt;Go ahead and submit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟡 Needs Review&lt;/td&gt;
&lt;td&gt;No critical violations, but manual Play Console declarations or restricted permissions need attention&lt;/td&gt;
&lt;td&gt;Review before submitting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴 Non-Compliant&lt;/td&gt;
&lt;td&gt;Violations or Data safety mismatches&lt;/td&gt;
&lt;td&gt;Fix before submitting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each finding has a severity — 🔴 &lt;strong&gt;Critical&lt;/strong&gt; (verified violation), 🟡 &lt;strong&gt;Important&lt;/strong&gt; (for example, a deprecated permission where a scoped one is expected), 🔵 &lt;strong&gt;Suggestion&lt;/strong&gt; (advisory) — and lists the policy, affected files, evidence, a confidence level, a recommendation and reference links. The report ends with Data safety discrepancies, local data access and a &lt;strong&gt;remediation checklist&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The report is &lt;strong&gt;advisory&lt;/strong&gt;: it is not legal advice or a guarantee of approval.&lt;/li&gt;
&lt;li&gt;The skill writes scratch files to &lt;code&gt;.scratch/play_policy_insights_&amp;lt;uuid&amp;gt;&lt;/code&gt; at the project root. Add &lt;code&gt;.scratch/&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Android Studio, &lt;strong&gt;Play Policy Insights&lt;/strong&gt; also shows up as lint checks right in your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Going further: a "release readiness" skill with every gate
&lt;/h3&gt;

&lt;p&gt;Play policy is only one gate. With Android CLI's tools you can write your own skill that runs &lt;strong&gt;every&lt;/strong&gt; gate and returns a single traffic light. Here's an example I wrote for study — adapt paths and thresholds to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-readiness&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use before publishing a release. Runs build, size, Play policy, security, stability and critical-journey gates and returns a traffic-light report.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Release readiness&lt;/span&gt;

Run every gate below, in order. Do not stop at the first failure: the report must show all gates.

&lt;span class="gu"&gt;## Gates&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Build**&lt;/span&gt; — &lt;span class="sb"&gt;`./gradlew :app:bundleRelease`&lt;/span&gt;. 🔴 if it fails.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Size and R8**&lt;/span&gt; — run &lt;span class="sb"&gt;`android analyze`&lt;/span&gt; on the release AAB and &lt;span class="sb"&gt;`./gradlew :app:analyzeReleaseR8Config`&lt;/span&gt;.
   🟡 if the shrinking score dropped vs. &lt;span class="sb"&gt;`docs/release/last-analyze.json`&lt;/span&gt;. 🔴 if obfuscation is off.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Play policy**&lt;/span&gt; — run the &lt;span class="sb"&gt;`play-policy-insights`&lt;/span&gt; skill and copy its overall status.
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Security**&lt;/span&gt; — run the &lt;span class="sb"&gt;`android-permissions-security`&lt;/span&gt; and &lt;span class="sb"&gt;`android-intent-security`&lt;/span&gt; skills. 🔴 for any Critical finding.
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Stability**&lt;/span&gt; — &lt;span class="sb"&gt;`android signals`&lt;/span&gt; for the current production version. 🟡 for new crash or ANR clusters.
   🔴 if crash-free users fell below the threshold in &lt;span class="sb"&gt;`docs/release/thresholds.md`&lt;/span&gt;.
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Critical journeys**&lt;/span&gt; — run the Journeys in &lt;span class="sb"&gt;`journeys/`&lt;/span&gt;. 🔴 if any fails.

&lt;span class="gu"&gt;## Output&lt;/span&gt;

Write &lt;span class="sb"&gt;`release-report.md`&lt;/span&gt; with:
&lt;span class="p"&gt;-&lt;/span&gt; the overall status (the worst gate wins);
&lt;span class="p"&gt;-&lt;/span&gt; one row per gate: status, gate, evidence, next action;
&lt;span class="p"&gt;-&lt;/span&gt; a remediation checklist.

Do not publish, upload or tag anything. Shipping is a human decision.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Claude Code, save it as &lt;code&gt;.claude/skills/release-readiness/SKILL.md&lt;/code&gt; and call it with &lt;code&gt;/release-readiness&lt;/code&gt;. In other agents, use their equivalent skills folder. The "worst gate wins" rule is what turns six checks into one honest traffic light.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Compose-first in practice: from XML to Compose with a right-click
&lt;/h2&gt;

&lt;p&gt;Another keynote highlight: migrating a screen from XML Views to Jetpack Compose is no longer a manual project. The &lt;strong&gt;Migration Assistant&lt;/strong&gt; (Android Studio Canary) shows up in the layout's context menu and hands the work to an agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F033rvbf1a0xnumza3fn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F033rvbf1a0xnumza3fn2.png" alt="Migration Assistant flow" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step by step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the latest Android Studio Canary&lt;/strong&gt; — the feature lives in that channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick your agent.&lt;/strong&gt; It works with Studio's built-in agent or with any agent added through Bring Your Own Agent (Claude, Codex…). It's also worth installing the official &lt;strong&gt;Migrate to Compose&lt;/strong&gt; skill, listed in &lt;code&gt;android skills list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right-click the XML layout&lt;/strong&gt; (for example, &lt;code&gt;res/layout/fragment_profile.xml&lt;/code&gt;) and choose &lt;strong&gt;AI › Migrate to Compose&lt;/strong&gt;. To migrate the whole project, open the &lt;strong&gt;action palette&lt;/strong&gt; and run the migration from there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope it with the agent.&lt;/strong&gt; It asks what to migrate now, what stays in interop, and how to handle custom views.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review the plan.&lt;/strong&gt; The agent drafts a step-by-step plan before touching any file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion.&lt;/strong&gt; The agent converts the &lt;strong&gt;layout&lt;/strong&gt;, the &lt;strong&gt;view-binding code&lt;/strong&gt; and the related &lt;strong&gt;logic&lt;/strong&gt; into idiomatic Compose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow the timeline.&lt;/strong&gt; The agent window shows which step is running and where to resume if it stops. Progress is saved with the conversation, so you can step away and come back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate.&lt;/strong&gt; Check the Compose Previews (or &lt;code&gt;android preview&lt;/code&gt; in the terminal) and run &lt;strong&gt;Change Review&lt;/strong&gt; with &lt;code&gt;/review&lt;/code&gt; before committing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example: what goes in and what comes out
&lt;/h3&gt;

&lt;p&gt;An illustrative example of this kind of conversion — a profile screen using ViewBinding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (XML + ViewBinding):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- res/layout/fragment_profile.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LinearLayout&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
    &lt;span class="na"&gt;android:orientation=&lt;/span&gt;&lt;span class="s"&gt;"vertical"&lt;/span&gt;
    &lt;span class="na"&gt;android:padding=&lt;/span&gt;&lt;span class="s"&gt;"16dp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/name"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:textAppearance=&lt;/span&gt;&lt;span class="s"&gt;"?attr/textAppearanceHeadlineSmall"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/role"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/follow"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:text=&lt;/span&gt;&lt;span class="s"&gt;"@string/follow"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/LinearLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileFragment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment_profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;viewModels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onViewCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FragmentProfileBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeatOnLifecycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFollowing&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (idiomatic Compose, with hoisted state):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileUiState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headlineSmall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFollowing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stringResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Preview&lt;/span&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileScreenPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProfileUiState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Android Engineer"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;During the transition&lt;/strong&gt;, the Fragment can stay and simply host Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileFragment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inflater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LayoutInflater&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewGroup&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ComposeView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;requireContext&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setViewCompositionStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ViewCompositionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DisposeOnViewTreeLifecycleDestroyed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist to review what the agent produced
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Is state &lt;strong&gt;hoisted&lt;/strong&gt; (stateless Composable + a Route that collects the ViewModel)?&lt;/li&gt;
&lt;li&gt;[ ] Do texts use &lt;code&gt;stringResource&lt;/code&gt; instead of hardcoded strings?&lt;/li&gt;
&lt;li&gt;[ ] Was the XML theme mapped to &lt;code&gt;MaterialTheme&lt;/code&gt; (colors, typography, shapes)?&lt;/li&gt;
&lt;li&gt;[ ] Is accessibility preserved: &lt;code&gt;contentDescription&lt;/code&gt;, touch target sizes, reading order?&lt;/li&gt;
&lt;li&gt;[ ] Were UI tests moved to &lt;code&gt;composeTestRule&lt;/code&gt; (or screenshot tests)?&lt;/li&gt;
&lt;li&gt;[ ] No needless recompositions? Use Compose 1.13's &lt;strong&gt;recomposition tracing&lt;/strong&gt; and the &lt;code&gt;jetpack-compose-performance&lt;/code&gt; skill.&lt;/li&gt;
&lt;li&gt;[ ] Were the old XML and binding class removed once the screen settled?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended strategy:&lt;/strong&gt; migrate screen by screen, starting with the simplest ones or the ones you're about to touch anyway, and use &lt;code&gt;ComposeView&lt;/code&gt; as the bridge. Migrating the whole app at once produces a diff nobody can review — even with an agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Compose-first: state beyond the UI with Jetpack AppState
&lt;/h2&gt;

&lt;p&gt;If you have ever fought a five-Flow &lt;code&gt;combine&lt;/code&gt;, you will like this one. &lt;strong&gt;Jetpack AppState&lt;/strong&gt; (alpha) runs the Compose runtime outside the UI, in ViewModels, repositories and services. Two pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transform&lt;/strong&gt; — runs a &lt;code&gt;@Composable&lt;/code&gt; block with no UI and returns a &lt;code&gt;State&amp;lt;R&amp;gt;&lt;/code&gt; that recomposes whenever any state it reads changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StateStore&lt;/strong&gt; — a type-safe map of state shared across activities, services and widgets, with optional DataStore persistence.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;appstate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.0.0-alpha01"&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;libraries&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.transform:transform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-transform-testing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.transform:transform-testing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-statestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.statestore:statestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: any module that calls &lt;code&gt;transform&lt;/code&gt; needs &lt;code&gt;buildFeatures { compose = true }&lt;/code&gt; and the Compose Compiler plugin, even without UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A ViewModel that switches sources based on connectivity:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MediaRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;networkMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NetworkMonitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isOnline&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;networkMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isOnline&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tracks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allTracksFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadedTracksFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@transform&lt;/span&gt; &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;

        &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;flatMapLatest&lt;/code&gt;, no &lt;code&gt;stateIn&lt;/code&gt; — a plain &lt;code&gt;if&lt;/code&gt; decides which source to observe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search debounce&lt;/strong&gt; with &lt;code&gt;LaunchedEffect&lt;/code&gt;, inside &lt;code&gt;transform&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;searchResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBlank&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@LaunchedEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When NOT to use &lt;code&gt;transform&lt;/code&gt;:&lt;/strong&gt; it represents the &lt;em&gt;current value&lt;/em&gt;, not a stream of events. Rapid updates before recomposition are dropped, and equal consecutive values trigger nothing. For running totals, logs, one-time events and backpressure, stick with &lt;code&gt;Flow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; is simple with &lt;code&gt;transformTest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testStateUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transformTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;sourceState&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backgroundScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testDispatcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceState&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sourceState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;advance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rounding it out: &lt;strong&gt;Compose 1.13&lt;/strong&gt; (stable soon: Grid and FlexBox stable, Progressive Blur, Formatted Text) and &lt;strong&gt;Navigation 3 1.2&lt;/strong&gt; (stable: deep links and richer predictive back). Examples: &lt;a href="https://github.com/android/appstate-recipes" rel="noopener noreferrer"&gt;github.com/android/appstate-recipes&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. A2UI: when the agent's answer is a screen
&lt;/h2&gt;

&lt;p&gt;A wall of chat text is poor UX. With &lt;strong&gt;A2UI (Agent-to-UI)&lt;/strong&gt;, the agent sends declarative JSON and your app renders &lt;strong&gt;native components from your own catalog&lt;/strong&gt; — no arbitrary code is executed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmhcswi7fj4vifiuq4qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmhcswi7fj4vifiuq4qx.png" alt="A2UI flow in Jetpack Compose" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-runtime:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-ui:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;androidTestImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-ui-testing:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ViewModel:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgenticUiViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;parser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;A2uiMessageParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;A2uiMessageProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalogs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomDesignSystemCatalog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;a2uiSurfaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;A2uiSurfaceModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeSurfaces&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// clicks and errors flow back to the agent&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CoroutineStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UNDISPATCHED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outboundEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;handleOutboundA2uiEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNetworkMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screen:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AgenticUiScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AgenticUiViewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;surfaces&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a2uiSurfaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;surfaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;A2uiSurface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surfaceModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My favorite part: &lt;strong&gt;hallucination defenses&lt;/strong&gt; come built in. An unknown component becomes an error reported back to the agent so it can self-correct; payloads are validated against a schema; absurd list indexes switch to a sparse map so they can't blow up memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Gemini Nano 4: a whole agent on the device
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemini Nano 4&lt;/strong&gt; brings native function calling, and ML Kit gained an &lt;strong&gt;Agentic API&lt;/strong&gt; (alpha) that runs the complete agent loop on the device, on top of AICore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbmotg5v4dfqxatm2eh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbmotg5v4dfqxatm2eh6.png" alt="Gemini Nano 4 and the ML Kit Agentic API" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt; Android 14+ enrolled in the AICore Developer Preview, a supported device, &lt;code&gt;minSdk 26&lt;/code&gt;, Kotlin 2.3+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.google.mlkit:genai-agent:1.0.0-alpha1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// optional: tools via AppFunctions&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appfunctions:appfunctions:1.0.0-alpha13"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;ksp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Assistant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInstructions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You are a helpful on-device assistant."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreateSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat_session"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Can you help me organize my day?"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textTokens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// no history: runner.runSingleTurnAsync(prompt)&lt;/span&gt;
&lt;span class="c1"&gt;// on destroy: runner.close()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Limits you need to know:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;maxTurns&lt;/code&gt; defaults to 10, to prevent runaway loops and save battery.&lt;/li&gt;
&lt;li&gt;Inference only runs while your app is in the foreground; too many calls return &lt;code&gt;ErrorCode.BUSY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The model can pick the wrong tool or argument: &lt;strong&gt;the safety of actions is your app's job&lt;/strong&gt;. Confirm sensitive actions with the user.&lt;/li&gt;
&lt;li&gt;For huge knowledge bases, Google itself recommends a hybrid architecture with cloud models.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Android MCP and AppFunctions: your app as a Gemini tool
&lt;/h2&gt;

&lt;p&gt;To me, this is the most strategic change of the event. &lt;strong&gt;AppFunctions&lt;/strong&gt; is the mobile equivalent of MCP &lt;em&gt;tools&lt;/em&gt;: your app becomes an on-device MCP server, and Gemini (or any authorized agent) discovers and calls your app's functions to fulfill a user's request. It is in &lt;strong&gt;beta&lt;/strong&gt; and runs on Android 16+.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj58ua1shro75enxbvkn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj58ua1shro75enxbvkn6.png" alt="How a function in your app becomes a Gemini tool" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequiresApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@AndroidEntryPoint&lt;/span&gt;
&lt;span class="nd"&gt;@AppFunctionServiceEntryPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;serviceName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TaskAppFunctionService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appFunctionXmlFileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"task_app_function_service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseTaskAppFunctionService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TaskRepository&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@AppFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDescribedByKDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CreateTaskParams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionInvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Title or content should be non-null"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionElementNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task not found for ID = $id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** The parameter to create the task. */&lt;/span&gt;
&lt;span class="nd"&gt;@AppFunctionSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDescribedByKDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;CreateTaskParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="cm"&gt;/** The title of the task. */&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="cm"&gt;/** The content of the task. */&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details worth noticing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The KDoc is the prompt.&lt;/strong&gt; With &lt;code&gt;isDescribedByKDoc = true&lt;/code&gt;, your docs become the description the agent reads to decide when to call the function. Write them for the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed exceptions&lt;/strong&gt; (&lt;code&gt;AppFunctionInvalidArgumentException&lt;/code&gt;, &lt;code&gt;AppFunctionElementNotFoundException&lt;/code&gt;) tell the agent &lt;em&gt;why&lt;/em&gt; a call failed, so it can try another way.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Testing without Gemini:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell cmd app_function list-app-functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AppFunctions vs a remote MCP server:&lt;/strong&gt; AppFunctions runs locally, uses the app's real state and needs no backend. A remote MCP server is platform-agnostic and depends on the network. Agents consider both together.&lt;/p&gt;

&lt;p&gt;To reach users, the &lt;strong&gt;Connected Apps Platform&lt;/strong&gt; (preview) brings your app's services to Gemini and Search: build with Android MCP or remote MCP, prototype in Gemini dev mode, and register through the Google Connected Console.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Reality check (Oct 2026): the Gemini integration is still limited to selected apps. You can implement and test today; for production there is an Early Access Program.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. One codebase, many screens: Googlebook, foldables and XR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm5vzso3bbioqv0dw62i7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm5vzso3bbioqv0dw62i7.png" alt="One codebase, many screens" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Googlebook&lt;/strong&gt; is the new category of laptops built on an Android foundation, with partners such as HP, Dell, Lenovo, Acer and Asus. The same app runs on the phone and the laptop, with no separate codebase.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Compose adaptive skill&lt;/strong&gt; was updated so your agent can do the heavy lifting: keyboard shortcuts, pointer cursors, hover states and tooltips; migration to Grid, FlexBox and &lt;code&gt;mediaQuery&lt;/code&gt;; end-to-end state handling; and foldable postures, such as tabletop layouts for media and camera screens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 Heads-up from Google Play: in 2027 Play will look closely at how apps and games run on foldables. Follow the &lt;a href="https://developer.android.com/docs/quality-guidelines/adaptive-app-quality" rel="noopener noreferrer"&gt;adaptive app quality guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Android XR and XREAL AURA
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;XREAL AURA&lt;/strong&gt; is the first wired XR glasses running Android XR. It stays under 95 g because the compute lives in a puck powered by Snapdragon Reality Elite. It has a Sony Micro-OLED 1200p display at 120 Hz, runs Google Play apps and up to five virtual windows. Price: US$1,099 launch price through December (US$1,279 after), available in the US, Canada, Japan and South Korea.&lt;/p&gt;

&lt;p&gt;For developers, the key news: &lt;strong&gt;the Jetpack XR core is now stable&lt;/strong&gt; (SceneCore, ARCore for Jetpack XR and XR Runtime), and so are the &lt;strong&gt;XR glasses emulators&lt;/strong&gt; in Android Studio and the &lt;strong&gt;Android XR Engine Hub&lt;/strong&gt;. Compose for XR is still alpha.&lt;/p&gt;

&lt;p&gt;The first step is spatializing a screen you already have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Subspace&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SpatialPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;SubspaceModifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;824&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1400&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformingMovable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MyExistingScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// your current Compose screen&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Content inside &lt;code&gt;Subspace&lt;/code&gt; only renders when spatialization is on. On a phone it is ignored, so the same code serves both worlds.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Beyond the keynote: 5 labs to try this week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An agent with eyes and hands (stable)&lt;/strong&gt; — install Android CLI and the plugin in Claude Code or Codex, write 3 Journeys for your critical flows, and ask: "run the journeys, capture a startup trace and propose the highest-impact fix". Measure cold start before and after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality gates in CI (stable)&lt;/strong&gt; — run &lt;code&gt;android analyze&lt;/code&gt; on your release AAB, store the JSON as a pipeline artifact and fail the build if the shrinking score drops. Use &lt;code&gt;analyzeReleaseR8Config&lt;/code&gt; to go after the 5 costliest keep rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A ViewModel without &lt;code&gt;combine&lt;/code&gt; (alpha)&lt;/strong&gt; — rewrite a ViewModel with 3+ sources using &lt;code&gt;transform&lt;/code&gt; and test it with &lt;code&gt;transformTest&lt;/code&gt;. Compare readability and check whether any event got lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your app as a tool (beta)&lt;/strong&gt; — use the AppFunctions skill to find your 3 highest-value functions, implement one and validate it with &lt;code&gt;adb shell cmd app_function list-app-functions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An answer as UI (alpha)&lt;/strong&gt; — build an A2UI catalog with 3 components from your design system and feed it fixed JSON before wiring a real model. Test the error path.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What the Dev Summit leaves behind
&lt;/h2&gt;

&lt;p&gt;The return of the Android Dev Summit was not defined by a single API. It was defined by a shift in the Android developer's role: &lt;strong&gt;orchestrating agents that build, validate and optimize the app — and designing the app to be used by agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you want to act now, here is my read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adopt today:&lt;/strong&gt; Rabbit 2 with BYOA, Android CLI with skills, Change Review and the traffic-light checklist in your team's workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This quarter:&lt;/strong&gt; migrate the XML screens you touch most, map which app functions become AppFunctions, and get the UI adaptive for foldables and Googlebook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As POCs:&lt;/strong&gt; A2UI, Gemini Nano 4 and AppState, following the APIs on their way to stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which of these will you try first? Tell me in the comments. 👇&lt;/p&gt;




&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8Lmv7LGgL4E" rel="noopener noreferrer"&gt;Android Dev Summit 2026 keynote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/blog/posts/build-it-your-way-a-next-era-of-android-development" rel="noopener noreferrer"&gt;Agentic and Flexible: A next era of Android development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/blog/posts/android-dev-summit-26-developer-tools-performance" rel="noopener noreferrer"&gt;What's new in Android developer tools and performance at Android Dev Summit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/tools/agents/android-skills/browse" rel="noopener noreferrer"&gt;Android skills (catalog)&lt;/a&gt; and &lt;a href="https://www.skills.sh/android/skills/play-policy-insights" rel="noopener noreferrer"&gt;play-policy-insights&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/ai/appfunctions" rel="noopener noreferrer"&gt;Overview of AppFunctions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/agentic/render-surfaces" rel="noopener noreferrer"&gt;Render A2UI surfaces in your app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/ml-kit/genai/agent/android" rel="noopener noreferrer"&gt;Build agentic workflows with Gemini Nano&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/topic/libraries/architecture/appstate" rel="noopener noreferrer"&gt;Jetpack AppState&lt;/a&gt; and &lt;a href="https://developer.android.com/topic/libraries/architecture/appstate/transform" rel="noopener noreferrer"&gt;Transform&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/develop-ui" rel="noopener noreferrer"&gt;Develop UI with Jetpack Compose for XR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcrunch.com/2026/10/28/xreal-aura-is-a-new-wired-headset-powered-by-android-xr/" rel="noopener noreferrer"&gt;TechCrunch — Xreal Aura&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article is also available in Portuguese and Spanish.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>ai</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>Android Dev Summit 2026: o Google volta a reunir devs depois de quatro anos e coloca os agentes no centro do Android</title>
      <dc:creator>Diego Ferreira Caetano</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:43:00 +0000</pubDate>
      <link>https://dev.to/diego_f_caetano/android-dev-summit-2026-o-google-volta-a-reunir-devs-depois-de-quatro-anos-e-coloca-os-agentes-no-337h</link>
      <guid>https://dev.to/diego_f_caetano/android-dev-summit-2026-o-google-volta-a-reunir-devs-depois-de-quatro-anos-e-coloca-os-agentes-no-337h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftukygv7s3bz2c7jy2r3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftukygv7s3bz2c7jy2r3x.png" alt="Android Dev Summit 2026: o Google volta a reunir devs depois de quatro anos e coloca os agentes no centro do Android" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mountain View, 28 de outubro de 2026&lt;/strong&gt; — Depois de quatro anos sem um evento próprio, o Google voltou a realizar o &lt;strong&gt;Android Dev Summit&lt;/strong&gt;. A última edição tinha sido em 2022; desde então, as novidades da plataforma dividiam espaço com o resto da programação do Google I/O. Desta vez foram dois dias dedicados só ao Android, no campus Bay View, com keynote transmitido no YouTube e uma edição estendida em Londres.&lt;/p&gt;

&lt;p&gt;O recado do keynote foi direto: o Android passa a ser construído &lt;strong&gt;com&lt;/strong&gt; agentes de IA e &lt;strong&gt;para&lt;/strong&gt; agentes de IA. O Android Studio abriu as portas para agentes de outras empresas, como Claude e Codex. O Android CLI deu a esses agentes acesso a aparelhos reais. E os apps ganharam um jeito padronizado de oferecer suas funções ao Gemini.&lt;/p&gt;

&lt;p&gt;Neste briefing eu reúno o que foi anunciado, em que estágio cada novidade está e — a parte que o keynote passa rápido — como usar cada uma no dia a dia, com código.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8Lmv7LGgL4E" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Ficha do evento
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quando&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28 e 29 de outubro de 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Onde&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Campus Bay View do Google, Mountain View (EUA), com edição estendida em Londres&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contexto&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Primeiro Android Dev Summit desde 2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trilhas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI experiences, Android XR, Apps foundation, Tools &amp;amp; performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keynote&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cerca de 1 hora, com demos ao vivo — &lt;a href="https://www.youtube.com/watch?v=8Lmv7LGgL4E" rel="noopener noreferrer"&gt;assista no YouTube&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  As manchetes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android Studio Rabbit 2 chega ao canal stable&lt;/strong&gt; e aceita qualquer agente de código compatível com ACP — Claude, Codex, Antigravity e mais de 40 outros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android CLI ganha aparelhos físicos remotos&lt;/strong&gt;, renderização de Compose Preview no terminal, profiler com LeakCanary e plugins oficiais para Claude Code e Codex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checklist de publicação em semáforo&lt;/strong&gt;: a skill &lt;code&gt;play-policy-insights&lt;/code&gt; audita o app antes do envio ao Google Play e aponta 🟢🟡🔴 o que falta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migração de XML para Compose com um clique direito&lt;/strong&gt;, conduzida por um agente dentro do Android Studio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AppFunctions entra em beta&lt;/strong&gt;: funções do seu app viram &lt;em&gt;tools&lt;/em&gt; para o Gemini e outros agentes, via &lt;strong&gt;Android MCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2UI (alpha)&lt;/strong&gt;: o agente passa a responder com telas nativas em Compose, montadas com o seu design system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini Nano 4 + ML Kit Agentic API (alpha)&lt;/strong&gt;: um agente inteiro rodando no aparelho, com chamada de funções.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jetpack AppState (alpha)&lt;/strong&gt; leva o modelo de estado do Compose para ViewModels e repositórios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Googlebook, foldables e Android XR&lt;/strong&gt;: um código para muitas telas. O &lt;strong&gt;XREAL AURA&lt;/strong&gt; estreia como o primeiro óculos XR com fio rodando Android XR.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. O que já dá para usar (e o que ainda é laboratório)
&lt;/h2&gt;

&lt;p&gt;Antes de sair instalando tudo, vale olhar a maturidade de cada anúncio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprbgrr65lrsiwijwv91f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprbgrr65lrsiwijwv91f.png" alt="Quadro de maturidade dos anúncios" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Minha regra prática: &lt;strong&gt;stable&lt;/strong&gt; vai para produção, &lt;strong&gt;beta&lt;/strong&gt; entra atrás de feature flag, &lt;strong&gt;alpha e preview&lt;/strong&gt; ficam em POC e estudo.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Android Studio Rabbit 2: traga o seu agente
&lt;/h2&gt;

&lt;p&gt;O Android Studio deixou de ser "o IDE com o Gemini". Com o &lt;strong&gt;Bring Your Own Agent (BYOA)&lt;/strong&gt;, agora stable no Rabbit 2, ele vira o host de qualquer agente compatível com o &lt;strong&gt;Agent Client Protocol (ACP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como ativar:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Abra &lt;strong&gt;Settings &amp;gt; Tools &amp;gt; AI &amp;gt; Agents&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Instale agentes pelo marketplace (Claude Agent, Codex, Antigravity…). Dá para ter vários e alternar.&lt;/li&gt;
&lt;li&gt;O agente passa a enxergar o que só o IDE sabe: estrutura do projeto e do build, resolução de símbolos, render de Compose Preview, Emulator e Logcat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Junto vieram três recursos que mudam o dia a dia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change Review&lt;/strong&gt; — revisão agêntica das mudanças que o agente fez. Inicie pela lista de mudanças pendentes, pela janela de Git ou com &lt;code&gt;/review&lt;/code&gt;. Gera relatório de problemas e um walkthrough guiado para mudanças grandes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration Assistant (Canary)&lt;/strong&gt; — clique direito em um layout XML &amp;gt; &lt;strong&gt;AI › Migrate to Compose&lt;/strong&gt; e o agente migra a tela para Compose. Tem uma seção inteira só sobre isso mais abaixo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Bench 2.0&lt;/strong&gt; — leaderboard que avalia modelos e harnesses em tarefas Android longas (migrar UI, construir app a partir de mocks), medindo se funciona de verdade, custo e tempo. Ótimo para escolher o agente do time: &lt;a href="https://developer.android.com/bench" rel="noopener noreferrer"&gt;developer.android.com/bench&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ACP x MCP: não confunda
&lt;/h3&gt;

&lt;p&gt;Esse foi o ponto que mais vi gerar confusão. São dois protocolos com papéis diferentes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5brlzljzsq6j13dk7ta2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5brlzljzsq6j13dk7ta2.png" alt="ACP ajuda você a construir; MCP deixa agentes usarem o seu app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Android CLI: o toolchain inteiro na mão do agente
&lt;/h2&gt;

&lt;p&gt;O Android CLI é a interface "tool-first" para qualquer terminal, agente ou pipeline de CI. A ideia é simples: o agente para de gastar tokens adivinhando como o Android funciona e passa a usar ferramentas oficiais.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# instale o CLI e configure o agente&lt;/span&gt;
android init

&lt;span class="c"&gt;# aparelho físico real, num device lab do Google (via Firebase)&lt;/span&gt;
android device remote connect
android device hub            &lt;span class="c"&gt;# janela para ver e controlar aparelhos e emuladores&lt;/span&gt;

&lt;span class="c"&gt;# Compose Previews sem emulador e sem IDE&lt;/span&gt;
android preview

&lt;span class="c"&gt;# performance e memória&lt;/span&gt;
android profiler systemtrace
android profiler heapdump capture
android profiler leakcanary

&lt;span class="c"&gt;# qualidade do binário e sinais de produção&lt;/span&gt;
android analyze app-release.aab
android signals

&lt;span class="c"&gt;# skills e documentação&lt;/span&gt;
android skills list
android skills add jetpack-compose-performance
android docs search &lt;span class="s2"&gt;"predictive back"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os números divulgados pelo Google chamam atenção: agentes usando &lt;code&gt;android preview&lt;/code&gt; terminaram tarefas de UI &lt;strong&gt;77% mais rápido&lt;/strong&gt; usando &lt;strong&gt;até 3x menos tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins oficiais para Claude Code e Codex
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code (dentro da sessão)&lt;/span&gt;
/plugin marketplace add android/android-cli-plugin
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;android-cli@android-cli-plugin

&lt;span class="c"&gt;# Claude Code (terminal)&lt;/span&gt;
claude plugin marketplace add android/android-cli-plugin
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;android-cli@android-cli-plugin

&lt;span class="c"&gt;# Codex&lt;/span&gt;
codex plugin marketplace add android/android-cli-plugin
codex plugin add android-cli@android-cli-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O plugin dá ao agente o necessário para criar projetos, gerenciar emuladores, inspecionar layouts ao vivo, rodar &lt;strong&gt;Journeys&lt;/strong&gt;, instalar skills oficiais e consultar a Android Knowledge Base.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills oficiais
&lt;/h3&gt;

&lt;p&gt;São mais de 30 skills em mais de 20 áreas, cada uma com avaliação. Elas focam onde os modelos costumam errar: performance de Compose, APIs novas de câmera, auditoria de segurança. Algumas que valem instalar já:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jetpack-compose-performance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;android-profiler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8-analyzer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;play-policy-insights&lt;/code&gt; (audita código, manifest e SDKs contra as políticas do Play antes do envio)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;android-intent-security&lt;/code&gt; e &lt;code&gt;android-permissions-security&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repositório: &lt;a href="https://github.com/android/skills" rel="noopener noreferrer"&gt;github.com/android/skills&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Validação: o agente confere o próprio trabalho
&lt;/h2&gt;

&lt;p&gt;O trecho "Validating apps with Android Prototype" do keynote (12:45) bate numa tecla: &lt;strong&gt;agente bom é agente que valida o que fez&lt;/strong&gt;. A demo mais marcante foi com o Claude Code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzg0yi4ckdvlgbs1kgvdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzg0yi4ckdvlgbs1kgvdi.png" alt="Loop de validação agêntica" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O Google deu ao agente uma lista de jornadas críticas e um único prompt — &lt;em&gt;encontrar e corrigir memory leaks&lt;/em&gt;. O Claude rodou cada jornada num aparelho separado, em paralelo, usou &lt;code&gt;android profiler leakcanary&lt;/code&gt;, seguiu as cadeias de referência até o código, corrigiu e rodou tudo de novo para confirmar.&lt;/p&gt;

&lt;p&gt;O que sustenta esse fluxo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Journeys&lt;/strong&gt; — fluxos de usuário descritos em linguagem natural. O time escreve, o agente executa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeakCanary integrado&lt;/strong&gt; — no CLI e no Profiler do Studio, com &lt;strong&gt;Fix with AI&lt;/strong&gt; (Canary).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Performance Analyzer (APA)&lt;/strong&gt; — o motor do Profiler agora também alimenta o CLI. Com a skill &lt;code&gt;android-profiler&lt;/code&gt;, o agente captura trace, analisa, propõe correção e mede o impacto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;androidx.tracing 2.0&lt;/strong&gt; — traces com arquivo, linha e argumentos; coroutines aparecem como fluxos conectados. Comece com &lt;code&gt;androidx.tracing:tracing-wire&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recomposition tracing (Compose 1.13)&lt;/strong&gt; — mostra &lt;em&gt;por que&lt;/em&gt; algo recompôs, até a escrita de estado que causou.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Do crash em produção à correção
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;android signals&lt;/code&gt; traz crashes, ANRs e problemas de memória para o terminal. E o Crashlytics agora coleta &lt;strong&gt;heap dumps de OOMs reais&lt;/strong&gt; e de Memory Limiter Kills do Android 17, desofuscados com o mapping do R8 e abertos no Perfetto (em &lt;a href="https://goo.gle/crashlytics-memory" rel="noopener noreferrer"&gt;early access&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  R8 sem medo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AGP 9.3: nota da sua configuração e ranking das keep rules mais caras&lt;/span&gt;
./gradlew :app:analyzeReleaseR8Config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combine com a skill &lt;code&gt;r8-analyzer&lt;/code&gt; e troque keep rules genéricas por &lt;strong&gt;Jetpack Keep&lt;/strong&gt; (alpha), com anotações type-safe que o lint adiciona por você.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Antes do deploy: o checklist em semáforo
&lt;/h2&gt;

&lt;p&gt;Um dos momentos mais práticos do evento: em vez de um checklist manual de publicação, você pede ao agente que audite o app e ele devolve um relatório em &lt;strong&gt;semáforo&lt;/strong&gt; — 🟢 o que está ok, 🟡 o que precisa de revisão, 🔴 o que bloqueia o envio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwwvd7yygs7k9dkyfkez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwwvd7yygs7k9dkyfkez.png" alt="O agente mostra, em semáforo, o que ainda falta antes do deploy" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A skill oficial: &lt;code&gt;play-policy-insights&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Esse formato vem da skill oficial &lt;strong&gt;play-policy-insights&lt;/strong&gt;, que audita código, manifest e SDKs contra as políticas do Google Play e cruza com a sua declaração de Data safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Instale a skill&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;android skills add play-policy-insights

&lt;span class="c"&gt;# alternativa, fora do Android CLI:&lt;/span&gt;
npx skills add https://github.com/android/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; play-policy-insights
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Rode com um prompt&lt;/strong&gt; (o Google sugere este):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audit this app for Google Play policy compliance before I submit it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. O que ela verifica&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data safety&lt;/strong&gt; — compara o que o código coleta e envia com o que você declarou no Play Console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credenciais de login de demonstração&lt;/strong&gt; — se os revisores do Play conseguem entrar no app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissões restritas&lt;/strong&gt; — uso de permissões sensíveis ou obsoletas onde há alternativa com escopo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Como ler o relatório&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status geral&lt;/th&gt;
&lt;th&gt;Significado&lt;/th&gt;
&lt;th&gt;O que fazer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟢 Compliant&lt;/td&gt;
&lt;td&gt;Nenhuma violação ativa encontrada&lt;/td&gt;
&lt;td&gt;Seguir com o envio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟡 Needs Review&lt;/td&gt;
&lt;td&gt;Sem violações críticas, mas há declarações manuais no Play Console ou permissões restritas para revisar&lt;/td&gt;
&lt;td&gt;Revisar antes de enviar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴 Non-Compliant&lt;/td&gt;
&lt;td&gt;Violações ou divergências no Data safety&lt;/td&gt;
&lt;td&gt;Corrigir antes do envio&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cada achado tem severidade — 🔴 &lt;strong&gt;Critical&lt;/strong&gt; (violação confirmada), 🟡 &lt;strong&gt;Important&lt;/strong&gt; (por exemplo, permissão obsoleta onde se espera uma com escopo), 🔵 &lt;strong&gt;Suggestion&lt;/strong&gt; (recomendação) — e vem com política afetada, arquivos, evidência, nível de confiança, recomendação e links. O relatório termina com divergências de Data safety, acessos locais a dados e um &lt;strong&gt;checklist de remediação&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Dois cuidados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O relatório é &lt;strong&gt;consultivo&lt;/strong&gt;: não é parecer jurídico nem garantia de aprovação.&lt;/li&gt;
&lt;li&gt;A skill grava arquivos temporários em &lt;code&gt;.scratch/play_policy_insights_&amp;lt;uuid&amp;gt;&lt;/code&gt; na raiz do projeto. Coloque &lt;code&gt;.scratch/&lt;/code&gt; no &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Android Studio, o &lt;strong&gt;Play Policy Insights&lt;/strong&gt; também aparece como verificações de lint direto no código.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indo além: uma skill de "release readiness" com todos os gates
&lt;/h3&gt;

&lt;p&gt;A política do Play é só um dos portões. Com as ferramentas do Android CLI dá para montar uma skill própria que roda &lt;strong&gt;todos&lt;/strong&gt; os gates e devolve um único semáforo. Este é um exemplo que escrevi para estudo — adapte os caminhos e limites ao seu projeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-readiness&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use antes de publicar uma release. Roda os gates de build, tamanho, política do Play, segurança, estabilidade e jornadas críticas e devolve um relatório em semáforo.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Release readiness&lt;/span&gt;

Rode todos os gates abaixo, em ordem. Não pare na primeira falha: o relatório precisa mostrar todos.

&lt;span class="gu"&gt;## Gates&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Build**&lt;/span&gt; — &lt;span class="sb"&gt;`./gradlew :app:bundleRelease`&lt;/span&gt;. 🔴 se falhar.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Tamanho e R8**&lt;/span&gt; — rode &lt;span class="sb"&gt;`android analyze`&lt;/span&gt; no AAB de release e &lt;span class="sb"&gt;`./gradlew :app:analyzeReleaseR8Config`&lt;/span&gt;.
   🟡 se a nota de shrinking caiu em relação a &lt;span class="sb"&gt;`docs/release/last-analyze.json`&lt;/span&gt;. 🔴 se a ofuscação estiver desligada.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Política do Play**&lt;/span&gt; — rode a skill &lt;span class="sb"&gt;`play-policy-insights`&lt;/span&gt; e copie o status geral dela.
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Segurança**&lt;/span&gt; — rode as skills &lt;span class="sb"&gt;`android-permissions-security`&lt;/span&gt; e &lt;span class="sb"&gt;`android-intent-security`&lt;/span&gt;. 🔴 para qualquer achado Critical.
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Estabilidade**&lt;/span&gt; — &lt;span class="sb"&gt;`android signals`&lt;/span&gt; na versão de produção atual. 🟡 para novos clusters de crash ou ANR.
   🔴 se a taxa de usuários sem crash ficou abaixo do limite em &lt;span class="sb"&gt;`docs/release/thresholds.md`&lt;/span&gt;.
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Jornadas críticas**&lt;/span&gt; — rode as Journeys de &lt;span class="sb"&gt;`journeys/`&lt;/span&gt;. 🔴 se alguma falhar.

&lt;span class="gu"&gt;## Saída&lt;/span&gt;

Escreva &lt;span class="sb"&gt;`release-report.md`&lt;/span&gt; com:
&lt;span class="p"&gt;-&lt;/span&gt; status geral (o pior gate define o status);
&lt;span class="p"&gt;-&lt;/span&gt; uma linha por gate: status, gate, evidência, próxima ação;
&lt;span class="p"&gt;-&lt;/span&gt; checklist de remediação.

Não publique, não faça upload e não crie tags. A decisão de publicar é humana.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Claude Code, salve em &lt;code&gt;.claude/skills/release-readiness/SKILL.md&lt;/code&gt; e chame com &lt;code&gt;/release-readiness&lt;/code&gt;. Em outros agentes, use a pasta de skills equivalente. A regra "o pior gate define o status" é o que transforma seis verificações num semáforo único e honesto.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Compose-first na prática: de XML para Compose com um clique direito
&lt;/h2&gt;

&lt;p&gt;Outro destaque do keynote: migrar uma tela de XML Views para Jetpack Compose deixou de ser um projeto manual. O &lt;strong&gt;Migration Assistant&lt;/strong&gt; (Android Studio Canary) aparece no menu de contexto do layout e entrega o trabalho para um agente.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs30jpmahwowuicq67ee6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs30jpmahwowuicq67ee6.png" alt="Fluxo do Migration Assistant" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Passo a passo
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instale o Android Studio Canary mais recente&lt;/strong&gt; — a feature está nesse canal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escolha o agente.&lt;/strong&gt; Funciona com o agente nativo do Studio ou com qualquer agente adicionado via Bring Your Own Agent (Claude, Codex…). Vale também instalar a skill oficial &lt;strong&gt;Migrate to Compose&lt;/strong&gt;, que aparece em &lt;code&gt;android skills list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clique direito no layout XML&lt;/strong&gt; (por exemplo, &lt;code&gt;res/layout/fragment_profile.xml&lt;/code&gt;) e escolha &lt;strong&gt;AI › Migrate to Compose&lt;/strong&gt;. Para migrar o projeto inteiro, abra a &lt;strong&gt;action palette&lt;/strong&gt; e rode a migração por lá.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defina o escopo junto com o agente.&lt;/strong&gt; Ele pergunta o que migrar agora, o que fica em interoperabilidade e como tratar componentes customizados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revise o plano.&lt;/strong&gt; O agente monta um plano por etapas antes de alterar qualquer arquivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversão.&lt;/strong&gt; O agente converte o &lt;strong&gt;layout&lt;/strong&gt;, o &lt;strong&gt;código de view binding&lt;/strong&gt; e a &lt;strong&gt;lógica&lt;/strong&gt; associada em Compose idiomático.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acompanhe pela timeline.&lt;/strong&gt; A janela do agente mostra qual etapa está rodando e de onde retomar se ele parar. O progresso fica salvo com a conversa: dá para sair e voltar depois.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valide.&lt;/strong&gt; Confira os Compose Previews (ou &lt;code&gt;android preview&lt;/code&gt; no terminal) e rode o &lt;strong&gt;Change Review&lt;/strong&gt; com &lt;code&gt;/review&lt;/code&gt; antes do commit.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Exemplo: o que entra e o que sai
&lt;/h3&gt;

&lt;p&gt;Um exemplo ilustrativo do tipo de conversão — uma tela de perfil com ViewBinding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Antes (XML + ViewBinding):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- res/layout/fragment_profile.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LinearLayout&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
    &lt;span class="na"&gt;android:orientation=&lt;/span&gt;&lt;span class="s"&gt;"vertical"&lt;/span&gt;
    &lt;span class="na"&gt;android:padding=&lt;/span&gt;&lt;span class="s"&gt;"16dp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/name"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:textAppearance=&lt;/span&gt;&lt;span class="s"&gt;"?attr/textAppearanceHeadlineSmall"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/role"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt;
        &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/follow"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
        &lt;span class="na"&gt;android:text=&lt;/span&gt;&lt;span class="s"&gt;"@string/follow"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/LinearLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileFragment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment_profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;viewModels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onViewCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FragmentProfileBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeatOnLifecycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFollowing&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Depois (Compose idiomático, com estado elevado):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileUiState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headlineSmall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFollowing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stringResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Preview&lt;/span&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProfileScreenPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProfileUiState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Android Engineer"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;onFollow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Durante a transição&lt;/strong&gt;, o Fragment pode continuar existindo e só hospedar o Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileFragment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inflater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LayoutInflater&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewGroup&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ComposeView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;requireContext&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setViewCompositionStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ViewCompositionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DisposeOnViewTreeLifecycleDestroyed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist para revisar o que o agente gerou
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] O estado está &lt;strong&gt;elevado&lt;/strong&gt; (Composable sem estado + Route que coleta o ViewModel)?&lt;/li&gt;
&lt;li&gt;[ ] Textos usam &lt;code&gt;stringResource&lt;/code&gt; e não strings fixas?&lt;/li&gt;
&lt;li&gt;[ ] O tema do XML foi mapeado para &lt;code&gt;MaterialTheme&lt;/code&gt; (cores, tipografia, shapes)?&lt;/li&gt;
&lt;li&gt;[ ] Acessibilidade preservada: &lt;code&gt;contentDescription&lt;/code&gt;, tamanhos de toque, ordem de leitura?&lt;/li&gt;
&lt;li&gt;[ ] Testes de UI migrados para &lt;code&gt;composeTestRule&lt;/code&gt; (ou screenshot tests)?&lt;/li&gt;
&lt;li&gt;[ ] Sem recomposições desnecessárias? Use o &lt;strong&gt;recomposition tracing&lt;/strong&gt; do Compose 1.13 e a skill &lt;code&gt;jetpack-compose-performance&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] XML e classe de binding antigos removidos depois que a tela estabilizou?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Estratégia recomendada:&lt;/strong&gt; migre tela a tela, começando pelas mais simples ou pelas que você já vai mexer, e use &lt;code&gt;ComposeView&lt;/code&gt; como ponte. Migrar o app inteiro de uma vez gera um diff impossível de revisar — mesmo com agente.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Compose-first: estado fora da UI com Jetpack AppState
&lt;/h2&gt;

&lt;p&gt;Quem já brigou com &lt;code&gt;combine&lt;/code&gt; de cinco Flows vai gostar dessa. O &lt;strong&gt;Jetpack AppState&lt;/strong&gt; (alpha) usa o runtime do Compose fora da UI, em ViewModels, repositórios e serviços. São duas peças:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transform&lt;/strong&gt; — roda um bloco &lt;code&gt;@Composable&lt;/code&gt; sem UI e devolve um &lt;code&gt;State&amp;lt;R&amp;gt;&lt;/code&gt; que recompõe sozinho quando qualquer estado lido muda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StateStore&lt;/strong&gt; — mapa type-safe de estado compartilhado entre activities, services e widgets, com integração opcional ao DataStore.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;appstate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.0.0-alpha01"&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;libraries&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.transform:transform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-transform-testing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.transform:transform-testing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;androidx-appstate-statestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"androidx.appstate.statestore:statestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"appstate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Atenção: todo módulo que chama &lt;code&gt;transform&lt;/code&gt; precisa de &lt;code&gt;buildFeatures { compose = true }&lt;/code&gt; e do plugin do Compose Compiler, mesmo sem UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Antes e depois.&lt;/strong&gt; Um ViewModel que troca de fonte conforme a conectividade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MediaRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;networkMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NetworkMonitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isOnline&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;networkMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isOnline&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tracks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allTracksFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mediaRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadedTracksFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@transform&lt;/span&gt; &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;

        &lt;span class="nc"&gt;HomeUiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem &lt;code&gt;flatMapLatest&lt;/code&gt;, sem &lt;code&gt;stateIn&lt;/code&gt;, e o &lt;code&gt;if&lt;/code&gt; comum decide qual fonte observar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debounce de busca&lt;/strong&gt; com &lt;code&gt;LaunchedEffect&lt;/code&gt;, dentro do &lt;code&gt;transform&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;searchResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBlank&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@LaunchedEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quando NÃO usar &lt;code&gt;transform&lt;/code&gt;:&lt;/strong&gt; ele representa o &lt;em&gt;valor atual&lt;/em&gt;, não um fluxo de eventos. Atualizações rápidas antes da recomposição são descartadas e valores iguais em sequência não disparam nada. Para totais acumulados, logs, eventos únicos e backpressure, continue com &lt;code&gt;Flow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testes&lt;/strong&gt; ficam simples com &lt;code&gt;transformTest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testStateUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transformTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;sourceState&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backgroundScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testDispatcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceState&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sourceState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;advance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completam o pacote &lt;strong&gt;Compose 1.13&lt;/strong&gt; (stable em breve: Grid e FlexBox stable, Progressive Blur, Formatted Text) e &lt;strong&gt;Navigation 3 1.2&lt;/strong&gt; (stable: deep links e predictive back mais rico). Exemplos: &lt;a href="https://github.com/android/appstate-recipes" rel="noopener noreferrer"&gt;github.com/android/appstate-recipes&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. A2UI: quando a resposta do agente é uma tela
&lt;/h2&gt;

&lt;p&gt;Chat com muro de texto não é boa UX. Com o &lt;strong&gt;A2UI (Agent-to-UI)&lt;/strong&gt;, o agente envia JSON declarativo e o seu app renderiza &lt;strong&gt;componentes nativos do seu catálogo&lt;/strong&gt; — sem executar código arbitrário.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3luc69hn7p1r1dh220aw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3luc69hn7p1r1dh220aw.png" alt="Fluxo do A2UI em Jetpack Compose" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-runtime:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-ui:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;androidTestImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.a2ui.compose:compose-ui-testing:1.0.0-alpha01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ViewModel:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgenticUiViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;parser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;A2uiMessageParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;A2uiMessageProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalogs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomDesignSystemCatalog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;a2uiSurfaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;A2uiSurfaceModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeSurfaces&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// cliques e erros voltam para o agente&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CoroutineStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UNDISPATCHED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outboundEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;handleOutboundA2uiEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNetworkMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tela:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AgenticUiScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AgenticUiViewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;surfaces&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a2uiSurfaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;surfaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;A2uiSurface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surfaceModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que mais gostei: as &lt;strong&gt;defesas contra alucinação&lt;/strong&gt; já vêm prontas. Componente desconhecido vira erro reportado ao agente para ele se corrigir; o payload é validado contra schema; índices de lista absurdos viram mapa esparso para não estourar memória.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Gemini Nano 4: um agente inteiro no aparelho
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;Gemini Nano 4&lt;/strong&gt; trouxe function calling nativo, e o ML Kit ganhou uma &lt;strong&gt;Agentic API&lt;/strong&gt; (alpha) que roda o loop agêntico completo no aparelho, sobre o AICore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frayodjls67w60yo3iund.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frayodjls67w60yo3iund.png" alt="Gemini Nano 4 e a ML Kit Agentic API" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pré-requisitos:&lt;/strong&gt; Android 14+ no AICore Developer Preview, aparelho suportado, &lt;code&gt;minSdk 26&lt;/code&gt;, Kotlin 2.3+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.google.mlkit:genai-agent:1.0.0-alpha1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// opcional: tools via AppFunctions&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appfunctions:appfunctions:1.0.0-alpha13"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;ksp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Assistant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInstructions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You are a helpful on-device assistant."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreateSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat_session"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Can you help me organize my day?"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textTokens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// sem histórico: runner.runSingleTurnAsync(prompt)&lt;/span&gt;
&lt;span class="c1"&gt;// ao destruir: runner.close()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Limites que você precisa saber:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;maxTurns&lt;/code&gt; padrão é 10, para evitar loop infinito e poupar bateria.&lt;/li&gt;
&lt;li&gt;Inferência só com o app em primeiro plano; excesso de chamadas devolve &lt;code&gt;ErrorCode.BUSY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;O modelo pode escolher a tool ou o argumento errado: &lt;strong&gt;a segurança das ações é do seu app&lt;/strong&gt;. Confirme ações sensíveis com o usuário.&lt;/li&gt;
&lt;li&gt;Para bases de conhecimento enormes, o próprio Google recomenda arquitetura híbrida com modelos em nuvem.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Android MCP e AppFunctions: seu app como tool do Gemini
&lt;/h2&gt;

&lt;p&gt;Esta é, para mim, a mudança mais estratégica do evento. &lt;strong&gt;AppFunctions&lt;/strong&gt; é o equivalente mobile das &lt;em&gt;tools&lt;/em&gt; do MCP: seu app vira um servidor MCP on-device e o Gemini (ou outro agente autorizado) descobre e chama funções do app para cumprir o pedido do usuário. Está em &lt;strong&gt;beta&lt;/strong&gt; e roda no Android 16+.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr9uoyn2q1bqzqz3scbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr9uoyn2q1bqzqz3scbv.png" alt="Como uma função do seu app vira uma tool do Gemini" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequiresApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@AndroidEntryPoint&lt;/span&gt;
&lt;span class="nd"&gt;@AppFunctionServiceEntryPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;serviceName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TaskAppFunctionService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appFunctionXmlFileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"task_app_function_service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseTaskAppFunctionService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TaskRepository&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@AppFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDescribedByKDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CreateTaskParams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionInvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Title or content should be non-null"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;createTaskParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;taskRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;AppFunctionElementNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task not found for ID = $id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** The parameter to create the task. */&lt;/span&gt;
&lt;span class="nd"&gt;@AppFunctionSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDescribedByKDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;CreateTaskParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="cm"&gt;/** The title of the task. */&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="cm"&gt;/** The content of the task. */&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare em dois detalhes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;O KDoc é o prompt.&lt;/strong&gt; Com &lt;code&gt;isDescribedByKDoc = true&lt;/code&gt;, a documentação vira a descrição que o agente lê para decidir quando chamar a função. Escreva pensando no modelo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exceções tipadas&lt;/strong&gt; (&lt;code&gt;AppFunctionInvalidArgumentException&lt;/code&gt;, &lt;code&gt;AppFunctionElementNotFoundException&lt;/code&gt;) dizem ao agente &lt;em&gt;por que&lt;/em&gt; falhou, para ele tentar de outro jeito.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Testando sem o Gemini:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell cmd app_function list-app-functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AppFunctions x servidor MCP remoto:&lt;/strong&gt; AppFunctions roda local, usa o estado real do app e não exige backend. MCP remoto é agnóstico de plataforma e depende de rede. Os agentes consideram os dois juntos.&lt;/p&gt;

&lt;p&gt;Para levar isso a usuários, a &lt;strong&gt;Connected Apps Platform&lt;/strong&gt; (preview) leva os serviços do app ao Gemini e à Busca: você constrói com Android MCP ou MCP remoto, prototipa no Gemini dev mode e registra no Google Connected Console.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Realidade em out/2026: a integração com o Gemini ainda está restrita a apps selecionados. Dá para implementar e testar hoje; para produção, há um Early Access Program.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. Um código, muitas telas: Googlebook, foldables e XR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ctg9hjt13z3gy28fuye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ctg9hjt13z3gy28fuye.png" alt="Um código, muitas telas" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Googlebook&lt;/strong&gt; é a nova categoria de laptops sobre base Android, com parceiros como HP, Dell, Lenovo, Acer e Asus. O mesmo app roda no celular e no notebook, sem codebase separado.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Compose adaptive skill&lt;/strong&gt; foi atualizada para o agente fazer o trabalho pesado: atalhos de teclado, cursores, hover e tooltips; migração para Grid, FlexBox e &lt;code&gt;mediaQuery&lt;/code&gt;; estado ponta a ponta; e posturas de foldable, como layout tabletop em telas de mídia e câmera.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 Recado do Google Play: em 2027 o Play vai olhar de perto como apps e jogos rodam em foldables. Siga as &lt;a href="https://developer.android.com/docs/quality-guidelines/adaptive-app-quality" rel="noopener noreferrer"&gt;adaptive quality guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Android XR e XREAL AURA
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;XREAL AURA&lt;/strong&gt; é o primeiro óculos XR com fio rodando Android XR. Fica abaixo de 95 g porque o processamento vai num "puck" com Snapdragon Reality Elite. Tem tela Sony Micro-OLED 1200p a 120 Hz, roda apps do Google Play e até cinco janelas virtuais. Preço: US$ 1.099 promocional até dezembro (US$ 1.279 depois), à venda nos EUA, Canadá, Japão e Coreia do Sul.&lt;/p&gt;

&lt;p&gt;Para devs, o mais importante: &lt;strong&gt;o núcleo do Jetpack XR ficou stable&lt;/strong&gt; (SceneCore, ARCore for Jetpack XR e XR Runtime), assim como os &lt;strong&gt;emuladores de óculos XR&lt;/strong&gt; no Android Studio e o &lt;strong&gt;Android XR Engine Hub&lt;/strong&gt;. O Compose for XR segue em alpha.&lt;/p&gt;

&lt;p&gt;O primeiro passo é espacializar uma tela que você já tem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Subspace&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SpatialPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;SubspaceModifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;824&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1400&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformingMovable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MyExistingScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// sua tela Compose atual&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O conteúdo de &lt;code&gt;Subspace&lt;/code&gt; só aparece quando a espacialização está ativa. No celular ele é ignorado, então o mesmo código serve para os dois mundos.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Indo além do keynote: 5 laboratórios para fazer esta semana
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agente com olhos e mãos (stable)&lt;/strong&gt; — instale o Android CLI e o plugin no Claude Code ou Codex, escreva 3 Journeys dos fluxos críticos e peça: "rode as jornadas, capture o trace de startup e proponha a correção de maior impacto". Meça o cold start antes e depois.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qualidade no CI (stable)&lt;/strong&gt; — rode &lt;code&gt;android analyze&lt;/code&gt; no AAB de release, salve o JSON como artefato e falhe o build se a nota de shrinking cair. Use &lt;code&gt;analyzeReleaseR8Config&lt;/code&gt; para atacar as 5 keep rules mais caras.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ViewModel sem &lt;code&gt;combine&lt;/code&gt; (alpha)&lt;/strong&gt; — reescreva um ViewModel com 3+ fontes usando &lt;code&gt;transform&lt;/code&gt; e teste com &lt;code&gt;transformTest&lt;/code&gt;. Compare legibilidade e verifique se algum evento se perdeu.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seu app como tool (beta)&lt;/strong&gt; — use a skill de AppFunctions para achar as 3 funções de maior valor, implemente uma e valide com &lt;code&gt;adb shell cmd app_function list-app-functions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resposta como UI (alpha)&lt;/strong&gt; — monte um catálogo A2UI com 3 componentes do seu design system e alimente com JSON fixo antes de ligar um modelo real. Teste o caminho de erro.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  O que fica do Dev Summit
&lt;/h2&gt;

&lt;p&gt;O retorno do Android Dev Summit não foi marcado por uma API isolada. Foi marcado por uma mudança no papel de quem desenvolve para Android: &lt;strong&gt;orquestrar agentes que constroem, validam e otimizam o app — e projetar o app para ser usado por agentes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Para quem quer agir agora, minha leitura é esta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Já dá para adotar:&lt;/strong&gt; Rabbit 2 com BYOA, Android CLI com skills, Change Review e o checklist em semáforo no fluxo do time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neste trimestre:&lt;/strong&gt; migrar as telas XML mais mexidas, mapear quais funções do app viram AppFunctions e preparar a UI adaptativa para foldables e Googlebook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Em POC:&lt;/strong&gt; A2UI, Gemini Nano 4 e AppState, acompanhando a evolução das APIs até o stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E você, qual dessas novidades vai testar primeiro? Conta nos comentários. 👇&lt;/p&gt;




&lt;h3&gt;
  
  
  Fontes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8Lmv7LGgL4E" rel="noopener noreferrer"&gt;Android Dev Summit 2026 keynote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/blog/posts/build-it-your-way-a-next-era-of-android-development" rel="noopener noreferrer"&gt;Agentic and Flexible: A next era of Android development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/blog/posts/android-dev-summit-26-developer-tools-performance" rel="noopener noreferrer"&gt;What's new in Android developer tools and performance at Android Dev Summit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/tools/agents/android-skills/browse" rel="noopener noreferrer"&gt;Android skills (catálogo)&lt;/a&gt; e &lt;a href="https://www.skills.sh/android/skills/play-policy-insights" rel="noopener noreferrer"&gt;play-policy-insights&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/ai/appfunctions" rel="noopener noreferrer"&gt;Overview of AppFunctions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/agentic/render-surfaces" rel="noopener noreferrer"&gt;Render A2UI surfaces in your app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/ml-kit/genai/agent/android" rel="noopener noreferrer"&gt;Build agentic workflows with Gemini Nano&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/topic/libraries/architecture/appstate" rel="noopener noreferrer"&gt;Jetpack AppState&lt;/a&gt; e &lt;a href="https://developer.android.com/topic/libraries/architecture/appstate/transform" rel="noopener noreferrer"&gt;Transform&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/develop-ui" rel="noopener noreferrer"&gt;Develop UI with Jetpack Compose for XR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcrunch.com/2026/10/28/xreal-aura-is-a-new-wired-headset-powered-by-android-xr/" rel="noopener noreferrer"&gt;TechCrunch — Xreal Aura&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Este artigo também está disponível em inglês e espanhol.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>ai</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>AI in Practice: RAG, MCP and Multi-Agent Systems in Kotlin, Without Magic Frameworks</title>
      <dc:creator>Diego Ferreira Caetano</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:43:50 +0000</pubDate>
      <link>https://dev.to/diego_f_caetano/ai-in-practice-rag-mcp-and-multi-agent-systems-in-kotlin-without-magic-frameworks-564o</link>
      <guid>https://dev.to/diego_f_caetano/ai-in-practice-rag-mcp-and-multi-agent-systems-in-kotlin-without-magic-frameworks-564o</guid>
      <description>&lt;h1&gt;
  
  
  AI in Practice: Demystifying RAG, MCP and Multi-Agent Systems in Kotlin, Without "Magic" Frameworks
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;❗ &lt;strong&gt;Opening provocation:&lt;/strong&gt; The market was flooded with buzzwords: RAG, MCP, multi-agent architecture, ReAct, guardrails. Every day a new framework promises autonomous agents in two lines of code. But what happens when you put that "demo agent" in front of a real customer support desk? It hallucinates, ignores business rules and leaks data. Enough &lt;em&gt;vibes-based engineering&lt;/em&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article walks through a real project, &lt;strong&gt;DSupport&lt;/strong&gt;, built by hand in Kotlin Multiplatform with no agent framework. Every concept comes with the &lt;strong&gt;real code&lt;/strong&gt; from the repository and an explanation of how it works, line by line.&lt;/p&gt;

&lt;p&gt;📦 Full code: &lt;a href="https://github.com/diegoferreiracaetano/dsupport-kt" rel="noopener noreferrer"&gt;github.com/diegoferreiracaetano/dsupport-kt&lt;/a&gt;. Every stage is a git tag (&lt;code&gt;etapa-1&lt;/code&gt; to &lt;code&gt;etapa-12&lt;/code&gt;), so you can see the exact code at that point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to read the code snippets:&lt;/strong&gt; the numbers ①②③ in the comments show up again in the explanation right below, under &lt;strong&gt;🔍 Reading the code&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ The Big Illusion of "Everything Is an LLM": Deterministic vs. Non-Deterministic
&lt;/h2&gt;

&lt;p&gt;There is an illusion that the language model should make 100% of a software's decisions. But an LLM is a &lt;strong&gt;probabilistic&lt;/strong&gt; engine: the same input can produce different outputs. Asking it, through a prompt, to never show sensitive data is Russian roulette.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------+
|                        HYBRID SYSTEM (DSupport)                        |
+------------------------------------+----------------------------------+
|  Model (non-deterministic)         |  Code (deterministic)            |
+------------------------------------+----------------------------------+
|  - understand natural language     |  - validate types and rules      |
|  - decide which tool to call       |  - hide sensitive data           |
|  - draft the reply                 |  - block what must not pass      |
|  - route between specialists       |  - tests that run offline        |
+------------------------------------+----------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Why does relying on the prompt alone fail in production?&lt;/strong&gt; A prompt is a suggestion, not a guarantee. A prompt injection or a plain stochastic variation breaks the rule. Whatever matters to the business must be enforced &lt;strong&gt;in code&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The contrast: three ways to build it
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it delivers&lt;/th&gt;
&lt;th&gt;Where it breaks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;100% deterministic&lt;/strong&gt; (&lt;code&gt;if/else&lt;/code&gt;, state machines)&lt;/td&gt;
&lt;td&gt;Predictable, fast, zero cost&lt;/td&gt;
&lt;td&gt;It does not understand paraphrase; the rule list grows forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;100% model&lt;/strong&gt; ("magic" framework)&lt;/td&gt;
&lt;td&gt;Flexible, looks smart&lt;/td&gt;
&lt;td&gt;Unpredictable answers, endless loops, runaway cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hybrid (DSupport)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The model understands and chooses; the code validates and blocks&lt;/td&gt;
&lt;td&gt;Costs more than an &lt;code&gt;if&lt;/code&gt;: it needs evals and tracing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  In practice: where the deterministic version fails
&lt;/h3&gt;

&lt;p&gt;This is the first thing anyone would try, and it is a great idea for predictable questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A deterministic router (illustration, not in the repository)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"refund"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lowercase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                     &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;POLICY&lt;/span&gt;   &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="nc"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""order\s*#?\d+"""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;containsMatchIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ORDER&lt;/span&gt;    &lt;span class="c1"&gt;// ②&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;                                                &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OTHER&lt;/span&gt;    &lt;span class="c1"&gt;// ③&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; It looks for the exact word "refund". "I want my money back" does not contain it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; It requires the pattern "order 1042". "Where is the thing I bought Monday?" does not match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; Anything not predicted lands here, silently.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"my package never showed up"           -&amp;gt; OTHER   (wrong)
"where is the thing I bought Monday?"  -&amp;gt; OTHER   (wrong)
"I want my money back"                 -&amp;gt; OTHER   (wrong)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 The deterministic version fails on &lt;strong&gt;language&lt;/strong&gt;, not on logic. The agent fails on &lt;strong&gt;trust&lt;/strong&gt;. The right question is not "AI or code?" but &lt;strong&gt;"what does each one do best?"&lt;/strong&gt;. In DSupport, &lt;strong&gt;5 roles use a model&lt;/strong&gt; and &lt;strong&gt;14 pieces are plain code&lt;/strong&gt; (guardrails, validator, TF-IDF search, tools, MCP transport, tracing, eval scoring and more).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2️⃣ The DSupport Architecture: Clean Separation of Responsibilities
&lt;/h2&gt;

&lt;p&gt;For testability and vendor independence (no &lt;em&gt;vendor lock-in&lt;/em&gt;), DSupport uses &lt;strong&gt;Clean Architecture&lt;/strong&gt; and &lt;strong&gt;Ports &amp;amp; Adapters&lt;/strong&gt; in Kotlin Multiplatform. The golden rule: &lt;strong&gt;dependencies point inward&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fayouji446s053bzcbxur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fayouji446s053bzcbxur.png" alt="Module dependencies" width="799" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧱 How the modules are separated
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;contract&lt;/code&gt;&lt;/strong&gt;: the shared types (&lt;code&gt;SupportAnswer&lt;/code&gt;, &lt;code&gt;AgentEvent&lt;/code&gt;). No external dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;core&lt;/code&gt;&lt;/strong&gt;: the heart. Agent loop, orchestration, guardrails, review and the &lt;strong&gt;ports&lt;/strong&gt; (interfaces). It does not know what HTTP, Anthropic or JSON is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;adapters&lt;/code&gt;&lt;/strong&gt;: implement the ports. Anthropic client, RAG engine, JSON files, MCP client. Swapping Claude for another model means writing &lt;strong&gt;a new adapter&lt;/strong&gt;, without touching &lt;code&gt;core&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mcp-orders&lt;/code&gt; and &lt;code&gt;mcp-tickets&lt;/code&gt;&lt;/strong&gt;: independent servers that speak the MCP protocol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cli&lt;/code&gt;, &lt;code&gt;server&lt;/code&gt;, &lt;code&gt;app&lt;/code&gt;&lt;/strong&gt;: entry points (terminal, Ktor API with SSE, Compose app on desktop and iPhone).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eval&lt;/code&gt;&lt;/strong&gt;: the evaluation suite with a &lt;em&gt;golden dataset&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  A real port: what &lt;code&gt;core&lt;/code&gt; knows about the model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;LlmClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LlmRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;LlmReply&lt;/span&gt;                                      &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;structured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LlmRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OutputSchema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;StructuredReply&lt;/span&gt;   &lt;span class="c1"&gt;// ②&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; &lt;code&gt;next&lt;/code&gt; returns &lt;strong&gt;text&lt;/strong&gt; or &lt;strong&gt;a tool request&lt;/strong&gt; (&lt;code&gt;LlmReply.Text&lt;/code&gt; or &lt;code&gt;LlmReply.ToolCalls&lt;/code&gt;). It is the basis of the agent loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; &lt;code&gt;structured&lt;/code&gt; forces the model to fill in a JSON form (we will see it in section 3).&lt;/li&gt;
&lt;li&gt;There is no provider &lt;code&gt;import&lt;/code&gt; here. &lt;code&gt;AnthropicLlmClient&lt;/code&gt;, in the &lt;code&gt;adapters&lt;/code&gt; module, implements this interface and translates everything to the Anthropic JSON, with 2 retries on 429 and 5xx errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;Why does this matter?&lt;/strong&gt; The project's 380+ tests run &lt;strong&gt;offline&lt;/strong&gt;, with test doubles in place of the model. No test spends a cent on the API.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3️⃣ The Concepts Mapped in Practice (From Theory to Code)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔹 Agent loop: the model decides, the code acts (&lt;code&gt;etapa-1&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In one sentence:&lt;/strong&gt; a &lt;code&gt;for&lt;/code&gt; loop with a limit. The model asks for a tool, the code runs it, the result goes back, and the cycle repeats until there is an answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frbcopiy16pfi0v0j3yr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frbcopiy16pfi0v0j3yr6.png" alt="Agent loop" width="799" height="511"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxTurns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                              &lt;span class="c1"&gt;// ①&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LlmRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;           &lt;span class="c1"&gt;// ②&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;LlmReply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;finalAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;// ③&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;LlmReply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToolCalls&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AssistantToolCalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;coroutineScope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;awaitAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// ④&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// ⑤&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Turn limit of $maxTurns reached. Escalating to a human agent."&lt;/span&gt;          &lt;span class="c1"&gt;// ⑥&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; &lt;code&gt;repeat(maxTurns)&lt;/code&gt;: the loop &lt;strong&gt;always&lt;/strong&gt; has a limit. Without it, a confused model calls tools forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; On every turn the whole history is sent again, together with the list of available tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; If the model answered with &lt;strong&gt;text&lt;/strong&gt;, it is over. Before returning, the answer goes through the guardrails (&lt;code&gt;finalAnswer&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; If it asked for &lt;strong&gt;tools&lt;/strong&gt;, they run &lt;strong&gt;in parallel&lt;/strong&gt; (&lt;code&gt;async&lt;/code&gt; + &lt;code&gt;awaitAll&lt;/code&gt;). Two independent lookups take as long as the slower one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⑤&lt;/strong&gt; Results enter the history &lt;strong&gt;in the same order as the requests&lt;/strong&gt;, because the model API requires that match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⑥&lt;/strong&gt; Limit reached? The code returns an escalation message. The model does not decide that.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;🛑 The model &lt;strong&gt;never executes anything&lt;/strong&gt;. It only &lt;strong&gt;asks&lt;/strong&gt;. Your code executes, validates and decides what comes back.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 Tools (function calling): giving the model hands (&lt;code&gt;etapa-3&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;A tool is a function with a &lt;strong&gt;name&lt;/strong&gt;, a &lt;strong&gt;description&lt;/strong&gt; and an &lt;strong&gt;input schema&lt;/strong&gt; (JSON Schema). The model only sees that; the code does the work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;                                   &lt;span class="c1"&gt;// ① name, description, schema&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt;        &lt;span class="c1"&gt;// ② executes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing_argument:order_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing_argument:email"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOwned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                            &lt;span class="c1"&gt;// ②&lt;/span&gt;
        &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_not_found_or_email_mismatch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;late&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IN_TRANSIT&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eta&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                &lt;span class="c1"&gt;// ③&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;JsonPrimitive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wire&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"late"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;JsonPrimitive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;late&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;// ④&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code (&lt;code&gt;GetOrderStatusTool&lt;/code&gt;, shortened)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; Missing argument? It returns a &lt;strong&gt;readable error result&lt;/strong&gt;, not an exception. The model reads &lt;code&gt;missing_argument:email&lt;/code&gt; and &lt;strong&gt;asks the customer for the email&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; &lt;code&gt;findOwned&lt;/code&gt; only returns the order if the number &lt;strong&gt;and&lt;/strong&gt; the email match. This is the first lock against data leaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; The delay is &lt;strong&gt;computed in code&lt;/strong&gt;, from the estimated date. The model does not "guess" that it is late: it reads &lt;code&gt;late: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; The return is short JSON, already in the right shape, without dumping the whole database into the context.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 The tool &lt;strong&gt;description&lt;/strong&gt; is part of the prompt. A vague description makes the model pick the wrong tool. Write it as if it were instructions for an intern.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 RAG (Retrieval-Augmented Generation): answering with a consultation (&lt;code&gt;etapa-4&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Instead of letting the model invent the return policy, RAG &lt;strong&gt;searches&lt;/strong&gt; the help center and forces it to answer &lt;strong&gt;only from what was found&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2jrwjtx2b56izo2lvsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2jrwjtx2b56izo2lvsw.png" alt="RAG" width="800" height="491"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchKbTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;KnowledgeSearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;minScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                  &lt;span class="c1"&gt;// ①&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing_argument:query"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;relevant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;minScore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// ②&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nothing_relevant_found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// ③&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relevant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;buildJsonObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"doc_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;docId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"snippet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// ④&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"results"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;JsonArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; The &lt;strong&gt;relevance cut-off&lt;/strong&gt; is the most important piece. Without it, the search always returns &lt;em&gt;something&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; Only what is similar enough to the question gets through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; Nothing relevant? An &lt;strong&gt;honest&lt;/strong&gt; answer: "nothing found". The knowledge specialist is instructed to reply &lt;code&gt;NO_ANSWER&lt;/code&gt; in that case, instead of inventing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; Every result carries the &lt;strong&gt;article title&lt;/strong&gt;. That is how the final answer can &lt;strong&gt;cite the source&lt;/strong&gt;, and the validator (below) checks that the cited source really came from the search.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Articles are split by section (&lt;code&gt;MarkdownChunker&lt;/code&gt;) and turned into vectors with &lt;strong&gt;TF-IDF&lt;/strong&gt;, a simple word weighting. There is an optional &lt;code&gt;VoyageEmbedder&lt;/code&gt; for semantic search. Notice: the search itself is &lt;strong&gt;deterministic code&lt;/strong&gt;, and it works without an API key.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ RAG without a relevance cut-off is the number one cause of "confident answers based on a passage that has nothing to do with the question".&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 MCP (Model Context Protocol): tools in separate processes (&lt;code&gt;etapa-5&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;MCP is an open standard: tools live in &lt;strong&gt;other programs&lt;/strong&gt; and speak JSON-RPC. In this project, over &lt;strong&gt;stdio&lt;/strong&gt; with the official Kotlin SDK: the client starts the server as a child process. A session has 3 messages: &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;tools/call&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp-orders/build/install/mcp-orders/bin/mcp-orders"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"DATA_DIR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp-tickets/build/install/mcp-tickets/bin/mcp-tickets"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"DATA_DIR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the server side, &lt;strong&gt;any list of &lt;code&gt;Tool&lt;/code&gt;&lt;/strong&gt; becomes an MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.),&lt;/span&gt;   &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="n"&gt;toolAnnotations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolAnnotations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readOnlyHint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destructiveHint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;// ②&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;emptyMap&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;                                       &lt;span class="c1"&gt;// ③&lt;/span&gt;
    &lt;span class="nc"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// ④&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code (server)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; The same &lt;code&gt;ToolSpec&lt;/code&gt; of the local tools is converted to the protocol schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; The &lt;code&gt;readOnly&lt;/code&gt; and &lt;code&gt;destructive&lt;/code&gt; flags travel along: a client can use them to ask for confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; The tool is the &lt;strong&gt;same class&lt;/strong&gt; as before. Nothing was rewritten to become remote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; The result goes back as text, with the error flag.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the client side, a remote failure &lt;strong&gt;must never&lt;/strong&gt; freeze the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;withTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// ①&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CancellationException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;TimeoutCancellationException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error: ${spec.name} timed out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;   &lt;span class="c1"&gt;// ②&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error: ${e.message}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;// ③&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code (client)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; Every remote call has a &lt;strong&gt;timeout&lt;/strong&gt;. A slow server does not freeze the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; A timeout becomes an &lt;strong&gt;error result&lt;/strong&gt;. A real cancellation (&lt;code&gt;CancellationException&lt;/code&gt;) is rethrown, so coroutines do not break.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; A dead server or an exception also becomes an error the model can read and work around.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;MCP benefit:&lt;/strong&gt; adding the tickets server is &lt;strong&gt;one line in &lt;code&gt;config/mcp-servers.json&lt;/code&gt;&lt;/strong&gt;. The agent code does not change: it does not know whether a tool is local or remote.&lt;/p&gt;

&lt;p&gt;🛑 On an MCP server over stdio, &lt;code&gt;stdout&lt;/code&gt; &lt;strong&gt;is the protocol&lt;/strong&gt;. A stray &lt;code&gt;println&lt;/code&gt; corrupts the conversation. That is why the server redirects &lt;code&gt;System.out&lt;/code&gt; to &lt;code&gt;System.err&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 Multi-agent orchestration: one orchestrator, three specialists (&lt;code&gt;etapa-6&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Instead of one giant "super prompt", an &lt;strong&gt;orchestrator&lt;/strong&gt; with a senior-support-specialist profile delegates to agents with a &lt;strong&gt;short prompt&lt;/strong&gt; and &lt;strong&gt;only the tools they need&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzere5hhgfo2qoka2yp1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzere5hhgfo2qoka2yp1h.png" alt="Multi-agent orchestration" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Who may use what is &lt;strong&gt;configuration&lt;/strong&gt;, not code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"knowledge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"search_kb"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"track_shipment"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"escalation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"create_ticket"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And each specialist is exposed to the orchestrator &lt;strong&gt;as if it were a tool&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubAgentTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                           &lt;span class="c1"&gt;// ①&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;stringSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing_argument:task"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;newAgent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                     &lt;span class="c1"&gt;// ②&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CancellationException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error: specialist '$name' failed: ${e.message}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// ③&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; It receives an agent &lt;strong&gt;factory&lt;/strong&gt;, not an agent. Every call creates a &lt;strong&gt;new&lt;/strong&gt; agent, because an agent keeps its history and two parallel calls must not share it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; Running the specialist is just calling &lt;code&gt;run(task)&lt;/code&gt;. To the orchestrator, this is a tool like any other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; If the specialist fails, the orchestrator receives an error and moves on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The orchestrator has no data tools. Its profile, in the prompt, starts like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a senior customer support specialist and the coordinator of a small team. [...]
You never look up data yourself: you decide who handles each part and always delegate to a specialist.
[...]
- Never promise anything the specialists did not confirm: no dates, refunds or outcomes of your own.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ If you also give the data tools to the orchestrator, it &lt;strong&gt;skips delegation&lt;/strong&gt; and the design collapses. That is why &lt;code&gt;DSupportAssembly&lt;/code&gt; fails to assemble the system if the orchestrator has tools.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 Guardrails: rules the model can neither see nor skip (&lt;code&gt;etapa-7&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Guardrails are &lt;strong&gt;deterministic code&lt;/strong&gt; that runs at 3 fixed points of the loop. Every guardrail implements this interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Hook&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;covers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptySet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                                              &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;beforeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Allow&lt;/span&gt;      &lt;span class="c1"&gt;// ②&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;afterTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;   &lt;span class="c1"&gt;// ③&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;beforeFinal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;            &lt;span class="c1"&gt;// ④&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; Which tools the guardrail protects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; &lt;strong&gt;Before&lt;/strong&gt; the tool: it can &lt;strong&gt;allow&lt;/strong&gt;, &lt;strong&gt;deny&lt;/strong&gt; or &lt;strong&gt;modify&lt;/strong&gt; the arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; &lt;strong&gt;After&lt;/strong&gt; the tool: it can &lt;strong&gt;rewrite the result&lt;/strong&gt; (for example, hide personal data).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; &lt;strong&gt;Before the final answer&lt;/strong&gt;: it can rewrite it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A concrete example: &lt;code&gt;IdentityGate&lt;/code&gt; only lets an order lookup through with a valid number and email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;beforeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;covers&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Allow&lt;/span&gt;                                                                   &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_id is missing or invalid. Ask the customer for the order number."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;// ②&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Patterns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email is missing or invalid. Ask the customer for the email used in the purchase."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Allow&lt;/span&gt;                                                                                   &lt;span class="c1"&gt;// ③&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; Other tools pass straight through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; The denial carries an &lt;strong&gt;instruction for the model&lt;/strong&gt; ("ask the customer for the number"). The model reads the reason and acts, instead of retrying blindly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; Only what passed both checks reaches the tool. The model &lt;strong&gt;cannot get around it&lt;/strong&gt;: the guardrail runs in code, not in the prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the case that proves a prompt is not enough: &lt;strong&gt;two parallel &lt;code&gt;create_ticket&lt;/code&gt; calls&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;beforeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;customerOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Allow&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withLock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                         &lt;span class="c1"&gt;// ①&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@withLock&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A ticket for this customer is already being created. Do not open another."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// ②&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOpenByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@withLock&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ticket ${existing.ticketId} is already open for this customer. [...]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// ③&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;inFlight&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;                                                                          &lt;span class="c1"&gt;// ④&lt;/span&gt;
        &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Allow&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code (&lt;code&gt;TicketDedup&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; A &lt;code&gt;Mutex&lt;/code&gt; guarantees only one call at a time enters this check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; A creation is already &lt;strong&gt;in progress&lt;/strong&gt; for this customer? Deny.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; A ticket is already &lt;strong&gt;open&lt;/strong&gt;? Deny and give the number, so the model can tell the customer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; Before allowing, it &lt;strong&gt;reserves&lt;/strong&gt; the customer. That is what stops two simultaneous calls from both passing.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;🛑 "Please never create duplicate tickets" in the prompt is a &lt;strong&gt;wish&lt;/strong&gt;. The &lt;code&gt;Mutex&lt;/code&gt; is a &lt;strong&gt;guarantee&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How guardrails plug into the loop (a snippet of &lt;code&gt;Agent.guarded&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;decision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Allow&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deny&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blocked by ${hook.name}: ${decision.reason}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// ①&lt;/span&gt;
        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Modify&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;                                                             &lt;span class="c1"&gt;// ②&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; Denied: the tool &lt;strong&gt;does not even run&lt;/strong&gt;, and the model receives the reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; Modified: the tool runs with the corrected arguments.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🔹 Structured output and validation in code (&lt;code&gt;etapa-8.1&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The final answer &lt;strong&gt;is not a paragraph&lt;/strong&gt;: it is a JSON form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;SupportAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"other"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;escalated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a schema only guarantees the &lt;strong&gt;shape&lt;/strong&gt;, not the &lt;strong&gt;truth&lt;/strong&gt;. Code checks the truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SupportAnswer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                      &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;INTENTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid intent: ${answer.intent}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                         &lt;span class="c1"&gt;// ②&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"confidence out of range 0..1: ${answer.confidence}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// ③&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticketId&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ticket $ticket does not appear in the tool results (invented?)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// ④&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ticket $ticket is missing from the customer-facing text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignoreCase&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"source \"$it\" does not appear in the tool results"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                     &lt;span class="c1"&gt;// ⑤&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; &lt;code&gt;evidence&lt;/code&gt; is everything the tools returned in that conversation: the &lt;strong&gt;source of truth&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; The intent must be in the closed list (&lt;code&gt;policy&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt;, &lt;code&gt;return&lt;/code&gt;, &lt;code&gt;complaint&lt;/code&gt;, &lt;code&gt;other&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; The JSON schema cannot express a range, so the confidence range is checked here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; &lt;strong&gt;An invented ticket is rejected:&lt;/strong&gt; the number must have appeared in the tool output, and must also be in the text the customer will read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⑤&lt;/strong&gt; &lt;strong&gt;An invented source is rejected:&lt;/strong&gt; you may only cite what the search really returned.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every problem found is a sentence in English, written for the &lt;strong&gt;model to read and fix&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔹 Producer–reviewer loop, with a limit (&lt;code&gt;etapa-8.2&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;A second model reviews the draft. But &lt;strong&gt;the reviewer is a model too&lt;/strong&gt;, so the code checks first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;SupportAnswer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;maxRevisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                  &lt;span class="c1"&gt;// ①&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;draft&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SupportAnswerSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                               &lt;span class="c1"&gt;// ②&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;problems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ifEmpty&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// ③&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReviewRound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                  &lt;span class="c1"&gt;// ④&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;                                                          &lt;span class="c1"&gt;// ⑤&lt;/span&gt;
        &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;followUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A reviewer rejected your answer. Fix these points and answer the customer again:\n"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"- $it"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;                                                  &lt;span class="c1"&gt;// ⑥&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;escalate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                                 &lt;span class="c1"&gt;// ⑦&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; At most &lt;code&gt;maxRevisions&lt;/code&gt; (2) fixes. This &lt;strong&gt;limit&lt;/strong&gt; is what stops the endless loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; The producer hands in the draft in the structured format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; &lt;strong&gt;Code first&lt;/strong&gt; (cheap and exact). &lt;strong&gt;Only if it passes&lt;/strong&gt;, the reviewer model judges the claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; Every round becomes an event, and shows up in the trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⑤&lt;/strong&gt; Approved: return it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⑥&lt;/strong&gt; Rejected: the problems go back to the producer, with instructions to fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⑦&lt;/strong&gt; Limit reached? &lt;strong&gt;The code&lt;/strong&gt; opens the ticket for a human. The model does not decide to escalate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdesijt7mjjspyqzs30zu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdesijt7mjjspyqzs30zu.png" alt="Producer-reviewer loop" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🔹 Observability: an airplane black box (&lt;code&gt;etapa-9.1&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;With agents running in parallel, "it went wrong" is not enough. Every event carries a &lt;code&gt;runId&lt;/code&gt; and a &lt;code&gt;parentRunId&lt;/code&gt;, and the tree rebuilds itself. The trick: the parent travels in the &lt;strong&gt;coroutine context&lt;/strong&gt;, with no parameter passed at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;parentRunId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AbstractCoroutineContextElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// ①&lt;/span&gt;
    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;parentRunId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coroutineContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;runId&lt;/span&gt;        &lt;span class="c1"&gt;// ②&lt;/span&gt;
    &lt;span class="n"&gt;runId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                    &lt;span class="c1"&gt;// ③&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;inRun&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AgentStarted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parentRunId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// ④&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; &lt;code&gt;RunContext&lt;/code&gt; is a coroutine-context element that stores "who am I and who called me".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; A sub-agent is born &lt;strong&gt;inside&lt;/strong&gt; the parent's tool call, so it finds the parent's &lt;code&gt;RunContext&lt;/code&gt; and reads its id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; Every run gets a unique id (&lt;code&gt;r1&lt;/code&gt;, &lt;code&gt;r2&lt;/code&gt;...), safe for concurrent calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;④&lt;/strong&gt; &lt;code&gt;inRun&lt;/code&gt; installs the new context for everything this agent does next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result, generated only from the list of events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[orchestrator] r1
  model call: 900 in / 80 out tokens, 700 ms
  tool orders: 1300 ms
    [orders] r2
      model call: 400 in / 40 out tokens, 500 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🔹 Server and app: the same stream of events, now on a screen (&lt;code&gt;etapa-11&lt;/code&gt; and &lt;code&gt;etapa-12&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondTextWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EventStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;answerCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerSentEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errorFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// ①&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerSentEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                                    &lt;span class="c1"&gt;// ②&lt;/span&gt;
            &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                                                                 &lt;span class="c1"&gt;// ③&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; A failure midway becomes an &lt;code&gt;event: error&lt;/code&gt; frame, instead of a connection that dies silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; Every &lt;code&gt;AgentEvent&lt;/code&gt; becomes &lt;strong&gt;one SSE frame&lt;/strong&gt; (&lt;code&gt;data: {...}&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; The &lt;code&gt;flush&lt;/code&gt; is what makes the app show the &lt;strong&gt;live trace&lt;/strong&gt;: without it, everything would arrive at once at the end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The API key stays &lt;strong&gt;only on the server&lt;/strong&gt;. The app (Compose, desktop and iPhone) reads the same stream and draws the conversation, the sources, the escalation badge and the trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  4️⃣ Anatomy of a Real Run: The Producer–Reviewer Loop and Guardrails
&lt;/h2&gt;

&lt;p&gt;The customer writes: &lt;code&gt;"My order 1042 has not arrived. My email is ana@example.com"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53ls6vf4j1bmxn36i5sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53ls6vf4j1bmxn36i5sh.png" alt="Where the model is and where code is" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔬 The real terminal trace
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ REFERENCE_DATE=2026-10-15 ./gradlew :cli:run --args="My order 1042 has not arrived. My email is ana@example.com"

[orchestrator] calls orders {"task":"Check the status, tracking and delivery progress..."}
[orders] calls get_order_status {"order_id":"1042","email":"ana@example.com"}
[orders] calls track_shipment   {"order_id":"1042","email":"ana@example.com"}     &amp;lt;- in parallel
[orders] get_order_status finished in 12 ms: {"ok":true,"status":"in_transit","late":true,"days_late":10}
[orchestrator] calls escalation {"task":"Delayed order. Order number: 1042 ..."}
[escalation] create_ticket finished in 17 ms: {"ok":true,"ticket_id":"TCK-1001"}
review round 1: rejected (4 problems)
review round 2: approved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What each line shows&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The two lookups of the &lt;code&gt;orders&lt;/code&gt; specialist leave &lt;strong&gt;together&lt;/strong&gt;, because the loop runs tools in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;late: true, days_late: 10&lt;/code&gt; was &lt;strong&gt;computed by the code&lt;/strong&gt;, not guessed by the model.&lt;/li&gt;
&lt;li&gt;The delay triggered the &lt;code&gt;escalation&lt;/code&gt; specialist, which opened ticket &lt;code&gt;TCK-1001&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The reviewer &lt;strong&gt;rejected the first draft&lt;/strong&gt; and approved the second. That is the producer–reviewer loop at work.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;Efficiency and cost:&lt;/strong&gt; this run involved 12 model calls, about 15 thousand input tokens and 2 thousand output tokens, costing about &lt;strong&gt;US$ 0.0025&lt;/strong&gt; (a quarter of a cent) with &lt;code&gt;claude-haiku-5-5&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5️⃣ Continuous Evaluation (Evals): How to Prove Your Agent Works
&lt;/h2&gt;

&lt;p&gt;If you do not measure the agent, you only have opinions. The &lt;code&gt;eval&lt;/code&gt; module has a &lt;em&gt;golden dataset&lt;/em&gt; of &lt;strong&gt;15 cases&lt;/strong&gt; run &lt;strong&gt;3 times each&lt;/strong&gt;, because the model is not deterministic: with the same code, 3 cases changed outcome between two single runs.&lt;/p&gt;

&lt;p&gt;Each case is just data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"g01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How many days do I have to return an item?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_intents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"return"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"should_escalate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_contain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"30 days"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Returns Policy"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔍 Reading the case&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;expected_intents&lt;/code&gt;: did the orchestrator route correctly?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;should_escalate&lt;/code&gt;: did it hand over to a human (or not) at the right time?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;must_contain&lt;/code&gt;: is the vital fact (30 days) in the answer?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expected_sources&lt;/code&gt;: did the answer cite the right source?&lt;/li&gt;
&lt;li&gt;There is also &lt;code&gt;must_not_contain&lt;/code&gt;, to check for &lt;strong&gt;leaks&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  📊 Real evolution and metrics of the project
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;What it means in practice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The orchestrator sends the question to the right specialist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Groundedness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No answer invents facts outside the sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No leaks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customer data does not show up where it should not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Expected facts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The vital information was delivered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cited sources&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;86%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You can see where the answer came from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Escalation precision&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hands over to a human at the right time (recall: 100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fallback after failed review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Answers refined without hitting the limit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🔎 What the evals discovered (and code review never would)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Two of my own rules contradicted each other.&lt;/strong&gt; &lt;code&gt;confidence&lt;/code&gt; treated "asking the customer for a detail" as uncertainty, and a validator rejected low confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reviewer invented objections.&lt;/strong&gt; It rejected "please send your email" because it did not know the assistant can ask for that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A deterministic routing error.&lt;/strong&gt; "Where do I find my tracking code?" always went to &lt;code&gt;orders&lt;/code&gt;, instead of the help center.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A test case that was just my assumption.&lt;/strong&gt; The case demanded an escalation that the assistant, correctly, avoided.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total cost: about &lt;strong&gt;30 cents of a dollar&lt;/strong&gt;, one afternoon.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Tests and Containerization
&lt;/h2&gt;

&lt;p&gt;One test class per production class, with test doubles at the ports. &lt;strong&gt;Zero API calls.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`given&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;malformed&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;checking&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="nf"&gt;deny`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;runTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;decision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;argsOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"not-an-email"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;// ①&lt;/span&gt;

    &lt;span class="n"&gt;assertIs&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                &lt;span class="c1"&gt;// ②&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; The test name reads like a sentence: &lt;em&gt;given / when / then&lt;/em&gt;. It documents the rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; No model, no network: the guardrail is plain code, so the test runs in milliseconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A multi-stage &lt;code&gt;Dockerfile&lt;/code&gt; for the server (build with the JDK, run with the JRE):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ① build stage, with the full JDK&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:21-jdk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;./gradlew :server:installDist &lt;span class="nt"&gt;--no-daemon&lt;/span&gt;

&lt;span class="c"&gt;# ② final image, much smaller, JRE only&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:21-jre&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /src/server/build/install/server/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; config ./config&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; data ./data&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DATA_DIR=data&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="c"&gt;# ③ starts the server&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["bin/server"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;①&lt;/strong&gt; The full JDK exists only to compile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;②&lt;/strong&gt; The final image carries only the JRE and the build output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;③&lt;/strong&gt; The API key comes in through an environment variable (&lt;code&gt;-e ANTHROPIC_API_KEY&lt;/code&gt;), never inside the image.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew &lt;span class="nb"&gt;test&lt;/span&gt;    &lt;span class="c"&gt;# 380+ tests, offline, without spending 1 cent on the API&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🏁 Provocative Conclusion: The Future of AI in Engineered Software
&lt;/h2&gt;

&lt;p&gt;Software engineering did not change just because we now use AI. Agents without &lt;strong&gt;strong typing&lt;/strong&gt;, &lt;strong&gt;clean architecture&lt;/strong&gt;, &lt;strong&gt;guardrails in code&lt;/strong&gt; and &lt;strong&gt;automated evaluation&lt;/strong&gt; are unpredictable toys.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🛑 &lt;strong&gt;The price of "multi":&lt;/strong&gt; one delayed-order question cost &lt;strong&gt;12 model calls&lt;/strong&gt; and about 10 seconds. Multi-agent is not free. Use one agent with tools when the task is small, and split into specialists only when prompts and toolsets grow and the roles are clearly different.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real innovation is not the newest framework on Twitter. It is knowing &lt;strong&gt;where to use the model's non-determinism&lt;/strong&gt; (understanding language, writing the reply) and &lt;strong&gt;where to use the determinism of code&lt;/strong&gt; (validating, blocking, measuring).&lt;/p&gt;

&lt;h3&gt;
  
  
  💬 Your turn
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Which rule in &lt;strong&gt;your&lt;/strong&gt; project could be code instead of a prompt? Tell me in the comments.&lt;/li&gt;
&lt;li&gt;Do you disagree that most of an agent should be deterministic? I want to hear the counter-example.&lt;/li&gt;
&lt;li&gt;Share this with someone about to build "an AI agent". A star on the repository helps: &lt;a href="https://github.com/diegoferreiracaetano/dsupport-kt" rel="noopener noreferrer"&gt;github.com/diegoferreiracaetano/dsupport-kt&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📖 &lt;em&gt;Leia em português:&lt;/em&gt; &lt;a href="https://github.com/diegoferreiracaetano/dsupport-kt/blob/main/docs/article/artigo-multiagentes-kotlin.md" rel="noopener noreferrer"&gt;docs/article/artigo-multiagentes-kotlin.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kotlin</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
