Saltar al contenido principal

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​

  1. 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 en START manda el mensaje a ese nodo.
  2. 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.
  3. 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 modo resume empieza con un clasificador.
  4. Nodo "Esperar respuesta" (wait_input) con interrupt(). Pausa hasta el siguiente mensaje, que entra como Command({ resume }) (resolveTurnInput). La validación sale por una rama invalid, nunca con un bucle while dentro del nodo: la doc de LangGraph lo desaconseja porque cada reanudación repite todas las vueltas.
  5. Qué olvida el punto de reanudación:
    • derivar a humano;
    • un Fin con "reiniciar conversación".
  6. flowRevision es 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.
  7. El thread_id pasa 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).