0006 — Los flujos continúan donde quedaron
Estado: aceptada · Fecha: 2026-09-19
Contexto
En un flujo Inicio → Saludo → IA, el bot saludaba en cada mensaje.
No era un fallo del nodo de saludo, sino de cómo usábamos LangGraph. Cada mensaje hace
graph.invoke(entrada, { thread_id }). Si la ejecución anterior del hilo terminó, LangGraph trata
una entrada normal como una ejecución nueva y recorre el grafo desde START. Conserva el estado
(mensajes, variables), pero no el recorrido. Lo comprobamos en el código instalado
(@langchain/langgraph 1.2, pregel/loop.js, _first): la entrada se escribe en START y las
tareas pendientes se descartan.
El motor no tenía ninguno de los dos mecanismos que LangGraph ofrece para esto:
interrupt(), para pausar un nodo hasta la siguiente respuesta.- Entrada condicional desde
START, para decidir por dónde entra cada mensaje.
Decisión
- Por defecto, continuar donde quedó, en todos los flujos, incluidos los existentes. Cada
nodo IA que responde en el canal deja un punto de reanudación en el estado (
resume):{ nodeId, at, revision }. Un router enSTARTmanda el mensaje a ese nodo. - El router vuelve a Inicio en tres casos:
- el punto de reanudación es de otra revisión del flujo;
- pasaron más de
resume_ttl_hours(24 por defecto) desde el último turno; - no hay punto de reanudación.
- Ajuste por flujo
settings.on_new_message.'restart'recupera el comportamiento anterior, para los flujos que clasifican cada mensaje justo después de Inicio. El editor avisa (START_CLASSIFIER_WITH_RESUME) cuando un flujo en modoresumeempieza con un clasificador. - Nodo "Esperar respuesta" (
wait_input) coninterrupt(). Pausa hasta el siguiente mensaje, que entra comoCommand({ resume })(resolveTurnInput). La validación sale por una ramainvalid, nunca con un buclewhiledentro del nodo: la doc de LangGraph lo desaconseja porque cada reanudación repite todas las vueltas. - Qué olvida el punto de reanudación:
- derivar a humano;
- un Fin con "reiniciar conversación".
flowRevisiones la huella de lo que el flujo hace: tipos, datos, conexiones y ajustes. No incluye posición ni etiqueta, así que mover un nodo no reinicia conversaciones.- El
thread_idpasa a ser<botId>:<conversationId>, pensando en el checkpointer compartido.
Alternativas descartadas
- Solo el nodo "Esperar respuesta", sin cambiar el comportamiento por defecto. No arreglaba los flujos existentes: cada tenant tendría que redibujar el suyo para dejar de saludar en cada mensaje.
- Marcar los mensajes "ya enviados" para no repetirlos. Tapa el síntoma del saludo, pero el flujo seguiría recorriéndose entero en cada mensaje: llamadas a API y búsquedas repetidas.
- Reiniciar el checkpointer al editar el flujo, que era lo que se hacía antes. Perdía el historial de mensajes de todas las conversaciones. Ahora el historial se conserva y solo se descarta el recorrido, gracias a la revisión.
Consecuencias
- Un flujo que clasificaba cada mensaje justo después de Inicio cambia de comportamiento: solo clasifica el primero. El editor lo avisa, y el arreglo es un clic en "Ajustes del flujo".
- Activar una versión con cambios en nodos o conexiones manda a Inicio las conversaciones en curso. El asistente de flujos lo avisa antes de activar.
- Mientras el checkpointer sea
MemorySaver, un reinicio del backend también manda todo a Inicio. Lo resuelve el checkpointer en Postgres (fase 4 del plan del motor).