¿Está trabajando o no? El indicador de actividad
«¿Está trabajando o no?» — el indicador y las promesas incumplidas
Nace de un caso real: JMAX escribió «…estoy extrayéndolos ahora. Un momento más y te confirmo», el turno cerró, y el botón de enviar volvió a azul. El usuario leyó una promesa de que seguía y vio un botón que decía lo contrario.
Dos problemas, no uno
La ventana decía la verdad. Azul, sin ■, panel «en reposo», estrellas de calificación: no había nada corriendo. El que mintió fue el modelo: prometió seguir y paró.
Pero el indicador se podía perder de vista. El latido «Razonando · 42s» vive dentro de la burbuja del turno, y se va con el hilo al hacer scroll. Quien subía a releer lo perdía y ya no sabía si seguía vivo.
1. Un turno no se cierra con una promesa
jmax/core/promesas.py. Es la otra mitad de la regla 5 del arnés:
harness.promesa (ya existía) | promesa_al_cierre (nueva) | |
|---|---|---|
| Cuándo mira | arranque del texto | final (últimos 220 caracteres) |
| Con trabajo hecho | se calla | sigue vigilando |
| Caso típico | «voy a consultar tu CRM» y nada | 8 min, 14 herramientas, «un momento más» |
La vieja no se tocó: funciona y tiene sus tests. Mezclarlas habría roto lo que ya cazaba bien.
No salta si el turno lanzó un subagente (entonces «te confirmo cuando termine» es verdad), ni si tras la promesa viene una conclusión («…te confirmo. Listo: aquí están los tres enlaces»), ni con una promesa en mitad de un texto que acaba terminado. Los seis idiomas, y también si el modelo contesta en otro idioma distinto al de la app.
Qué pasa cuando salta
- Primera vez — se le exige, una sola vez: o terminas AHORA en este
turno, o delegas con
tarea_lanzary das nombre y número. El usuario ve una nota ámbar mientras tanto. - Reincidencia — el agente delega él mismo lo que quedaba a un subagente llamado «Relevo», con el encargo original más el punto donde se cortó. La nota va dentro de la respuesta final, no solo como aviso de la ventana: por WhatsApp o Telegram no hay avisos, solo texto.
- Si no se puede delegar (cola desbordada) se entrega lo que hay sin inventar otra promesa, y queda en el registro.
El subagente es durable: sobrevive a cerrar JMAX y al reabrir retoma donde iba.
Y la regla queda escrita en el prompt base, junto a «TAREAS EN PARALELO».
2. Cuando se atasca de verdad, para y cuenta
Si la tarea acaba sin verificar y sin confirmar, el subagente se detiene y espera instrucciones. El aviso de fin ya no dice «falló» a secas: trae qué intentó, por qué no llegó y qué quedó.
🧭 Relevo (tarea #42) se atascó y espera instrucciones · Intentó: web_fetch → pc_powershell → pc_powershell · No llegó porque: no dejó el archivo · Quedó: parcial, 2 enlaces · Dime cómo seguir: otro camino, otra fuente, o lo dejamos aquí.
«Falló» no deja decidir nada; esto sí.
3. El indicador no se puede perder de vista
Una píldora fija en el compositor, pegada al ■ rojo: ● Razonando · 42s.
Punto rojo que parpadea, qué está haciendo, y el reloj.
Misma fuente de texto que el latido de la burbuja — una sola verdad, cero
estado nuevo. setBusy y pintarDetener no se tocaron: ya hacían lo
correcto (ocultar el azul, mostrar ■ y ⏸).
Con las animaciones desactivadas en el sistema, el punto no parpadea.
Comprobado en la ventana real
| Momento | Píldora | ■ rojo | Azul |
|---|---|---|---|
| En reposo | oculta | oculto | visible |
| Trabajando | ● Repasando lo que recuerdo · 3s | visible | oculto |
| Tras subir a releer | sigue en pantalla | visible | oculto |
| Al parar | oculta | oculto | visible |
Probarlo
python -m pytest tests/test_promesa_al_cierre.py -q
19 casos: el texto real de la captura, los seis idiomas, los negativos que no deben saltar, que la regla vieja sigue intacta, que la reincidencia delega de verdad, y el resumen de atasco.