Puntos clave
- Trata la sesión de autenticación, el estado de la interfaz y la conversación del agente como tres relojes separados.
- Gestiona la lista de conversaciones en tu API; deja fuera la basura del playground y del portal.
- Guarda un id de conversación duradero antes de abrir el stream.
- Define en un ADR qué pasa al recargar, salir, reciclar y renovar tokens.
- Muestra las ejecuciones incompletas a partir de la verdad del servidor, no de si el EventSource sigue vivo.
- En soporte, separa la duración de la autenticación de la duración de la conversación.
Los usuarios esperan las reglas de ChatGPT: cierras la pestaña, vuelves y el hilo sigue ahí. Los servicios de agentes de las plataformas suelen tener su propio almacén de conversaciones. Tu shell es una tercera cosa.
Si esas tres no coinciden, acabas con hilos sin título que vienen del portal, agentes que parecen "parados" tras recargar y chats incompletos que desaparecen después de volver a iniciar sesión.
Modelo con nombre: tres relojes
- Sesión de autenticación: caché de MSAL, cookies, duración de los tokens. Demuestra quién llama. No decide qué hilo abrir.
- Estado de la interfaz: agente seleccionado, paneles, borradores, scroll. Normalmente desechable. Perderlo debería sentirse como cerrar un cajón.
- Conversación del agente: mensajes, ejecuciones, llamadas a herramientas, citas. Vive en la plataforma o en tu backend, asociada al usuario.
- Contrato: solo el reloj (3) tiene un significado duradero entre pestañas. Seguir con la sesión iniciada no es seguir en el mismo chat.
Figura 1. La autenticación, el estado de la interfaz y la conversación del agente son tres relojes. Solo el reloj de la conversación debería sobrevivir a salir y recargar como historial del producto.
Contexto y restricción
Los fallos que parecen del modelo suelen resultar ser límites de sesión. La ejecución fue bien. La interfaz perdió el id. O la interfaz mostró un hilo del portal que el usuario nunca empezó en la app. Recargar y salir son el contrato del producto, no casos extremos.
- Sesión de autenticación. Caché de MSAL, cookies, duración de los tokens. Sobrevive a la recarga si la diseñas así. Demuestra quién llama. No decide qué hilo abrir.
- Estado de la interfaz. Agente seleccionado, paneles abiertos, borradores locales, posición del scroll. Normalmente desechable. Perderlo debería sentirse como cerrar un cajón, no como perder trabajo.
- Conversación del agente. Mensajes, ejecuciones, llamadas a herramientas, citas. Debería vivir en la plataforma o en tu backend, asociada al usuario, no a una pestaña pasajera del navegador.
El error recurrente es suponer que (3) es automático porque (1) funciona. Que el inicio de sesión sobreviva a la recarga no significa que el grafo de la conversación haya sobrevivido, ni que tu shell sepa qué conversación volver a cargar. "Sigo con la sesión iniciada" y "sigo en el mismo chat" son frases distintas.
La figura 2 es el planteamiento de Learn para la autenticación de la SPA contra la API: el shell obtiene tokens y llama a las API. La durabilidad de la conversación es un diseño aparte.

Figura 2. El shell obtiene tokens y llama a las API. La durabilidad de la conversación es un diseño aparte. Fuente: Microsoft Learn: authentication flows and app scenarios.
Qué significa de verdad "sigue funcionando en la plataforma"
Los almacenes de la plataforma pueden conservar las ejecuciones después de que muera el navegador. Es útil. No es una promesa de producto que puedas pegar en unas FAQ de soporte sin tests.
Esa durabilidad no significa que tu shell deba:
- listar todas las conversaciones del proyecto, incluidos los experimentos del playground y de la API
- reanudar una ejecución sin un id de conversación duradero en tu propio almacén
- fingir que un stream SSE que murió a mitad de un token sigue siendo interactivo sin un protocolo de reconexión
- tratar los hilos sin título creados en el portal como historial de primera clase del producto
Trata la durabilidad de la plataforma como una capacidad que hay que verificar con tests de salir y recargar en una build parecida a producción. Los reciclados, la renovación de tokens y dos pestañas abiertas muestran el camino real.
Diseño
1. Gestiona la lista de conversaciones en tu API
Muestra solo los hilos creados a través del shell. Los usuarios no deberían heredar chats sin título del playground ni experimentos vecinos del mismo proyecto de la plataforma. Si los operadores necesitan ver el portal, dales el portal. Filtrar al renderizar no basta; el endpoint de la lista ya debería ser honesto.
2. Guarda antes de hacer streaming
Crea primero el registro de la conversación. Luego haz el streaming. Si el backend se recicla y la interfaz pierde la conexión SSE, sigues teniendo un id para recargar el historial o mostrar un estado claro de "ejecución terminada". Los id solo locales hasta el primer token son el origen de los fallos de pantalla en blanco al recargar. La figura 3 es ese orden de responsabilidades.
Figura 3. Crea primero el registro. Luego haz el streaming. Los id solo locales hasta el primer token son el origen de los fallos de pantalla en blanco al recargar.
3. Define en un ADR qué pasa al recargar
Opciones razonables:
- recargar el historial de ese id de conversación
- detectar una ejecución en curso y ofrecer continuar o esperar
- fail closed con un mensaje explícito
Elige una por superficie del producto. Documéntala. Pruébala en una build real saliendo, recargando y renovando tokens. No dejes el comportamiento de F5 como conocimiento tribal.
4. Separa la duración de la autenticación de la duración de la conversación
Un usuario puede seguir con la sesión iniciada y aun así perder una ejecución. Un usuario puede renovar los tokens y aun así abrir el hilo de ayer. Mezclar esos relojes genera tickets de soporte que suenan a misterio: "el agente se ha parado" cuando murió el stream, o "he perdido mi chat" cuando la interfaz nunca guardó el id asociado al principal.
5. Representa las ejecuciones incompletas como estado de primera clase en la interfaz
Las llamadas largas a herramientas sobreviven a una pestaña. Muestra "en curso", "interrumpida" o "terminada sin respuesta" a partir de la verdad del servidor, no de si el EventSource sigue abierto. La figura 4 es el contrato de la interfaz.
Figura 4. En curso, interrumpida y terminada sin respuesta son estados del producto. Dedúcelos del servidor, no de si el EventSource sigue abierto.
La figura 5 es el planteamiento de Learn para las redes privadas. La configuración de red del shell es independiente del almacenamiento de las conversaciones. Los private endpoints no inventan semántica de sesión.

Figura 5. La configuración de red del shell es independiente del almacenamiento de las conversaciones. Fuente: Microsoft Learn: App Service private endpoints.
Errores comunes
Lista de toda la plataforma en el shell. La interfaz muestra chats de toda la plataforma mezclados con los de la app. Confunde y puede exponer chats que el usuario nunca empezó. La basura del playground acaba junto al trabajo real.
Recargar sin id de conversación. La recarga mata la ejecución y la interfaz no tiene id de conversación. El usuario ve una pantalla en blanco y da por hecho que ha perdido datos. A menudo la plataforma aún tiene la ejecución; tu shell tiró el puntero.
Id nunca asociados al principal. Al volver a iniciar sesión no se pueden abrir los chats incompletos. Los id nunca se guardaron asociados al principal del usuario, o solo vivían en sessionStorage asociados a una pestaña.
Solo streams en memoria. Un reinicio del backend descarta los streams en memoria sin una transcripción duradera. Funciona en una sola máquina hasta el primer reciclado o escalado horizontal.
Interfaz optimista sin confirmación del servidor. Parece rápida hasta que dos pestañas no coinciden o una reconexión repite medio turno.
Reconectar es esperar lo mejor. Reconectar consiste en "recargar la página y cruzar los dedos". Para agentes que llaman a herramientas durante minutos, cruzar los dedos no es un protocolo.
Úsalo tal cual o adáptalo
Úsalo tal cual
- La fuente de verdad de los mensajes (almacén de la plataforma, tu base de datos o ambos).
- Qué chats aparecen en la lista del shell y cuáles se excluyen (playground, otras apps, eliminados).
- El comportamiento al recargar, salir, reciclar el backend y renovar tokens.
- Cómo se representan en la interfaz las ejecuciones incompletas e interrumpidas.
- Retención y eliminación ligadas a la identidad del usuario.
- Expectativas de reconexión para las llamadas largas a herramientas, o un "no soportado" explícito con una UX acorde.
Adáptalo antes de usarlo
Gestionar los metadatos de las conversaciones en tu backend añade trabajo de almacenamiento y eliminación (RGPD, baja de cuentas, "borra mi historial"). Depender solo del almacén de la plataforma puede ser más sencillo, hasta que el filtrado de la lista y la UX del producto necesitan campos que la plataforma no te da de forma limpia.
Las llamadas a herramientas de larga duración necesitan una solución de reconexión. Eso es más difícil que en un simple chat de juguete. Resérvale presupuesto si tus agentes llaman a herramientas que tardan minutos.
Las redes privadas y las listas de IP permitidas protegen el plano. No arreglan la semántica de sesión. Sigues necesitando el ADR.
Referencias principales: Microsoft Learn: authentication flows and app scenarios, App Service private endpoints, y las API de conversación de tu plataforma para el runtime de agentes con el que trabajes.
La sesión de autenticación, el estado de la interfaz y el estado de la conversación son tres relojes. Dibuja los tres, o tu shell seguirá mintiendo con educación a los usuarios.