← Todo el material
Curso Intermedio 20 de agosto de 2026

LangGraph — Módulo 6: persistencia y memoria de corto plazo

Sexto módulo del curso de LangGraph: checkpointers y threads para que el agente recuerde entre turnos. InMemorySaver, PostgresSaver, thread_id, get_state y get_state_history.

☰ Índice del contenido

Sexto módulo del Curso completo de LangGraph. El agente del Módulo 5 funciona, pero tiene un problema serio: es amnésico. Cada invoke empieza con el estado en blanco. Si le dices tu nombre y luego preguntas cuál es, no tiene idea. Hoy lo arreglamos.

La tesis del módulo

La memoria de un agente no es una feature del modelo: es persistencia de estado. Un checkpointer guarda el estado del grafo después de cada paso. Un thread identifica a qué conversación pertenece ese estado. Con esas dos piezas, el mismo grafo que ya escribiste recuerda — sin cambiar un solo nodo.

El problema, en concreto

agente.invoke({"messages": [{"role": "user", "content": "Me llamo Felipe."}]})
agente.invoke({"messages": [{"role": "user", "content": "¿Cómo me llamo?"}]})
# → "No tengo esa información."

El segundo invoke creó un estado nuevo desde cero. El historial del primero se perdió al terminar la ejecución. El reducer add_messages acumula dentro de una ejecución, no entre ejecuciones.

La solución: el checkpointer

Un checkpointer es un componente que guarda una foto del estado (un checkpoint) después de cada paso del grafo. Se lo pasas al compilar:

from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()
agente = builder.compile(checkpointer=checkpointer)

InMemorySaver guarda en memoria RAM: perfecto para desarrollo y pruebas, inútil al reiniciar el proceso. Más abajo lo cambiamos por Postgres.

La segunda pieza: el thread

El checkpointer guarda estados, pero necesita saber de quién. Ese identificador es el thread_id, y va en la configuración de cada invocación:

config = {"configurable": {"thread_id": "felipe-001"}}

agente.invoke(
    {"messages": [{"role": "user", "content": "Me llamo Felipe."}]},
    config,
)

agente.invoke(
    {"messages": [{"role": "user", "content": "¿Cómo me llamo?"}]},
    config,   # ← el mismo thread
)
# → "Te llamas Felipe."

Eso es todo. Mismos nodos, mismas aristas, misma tool. Lo único que cambió fue compilar con checkpointer y pasar un thread_id.

 thread "felipe-001"
 ┌────────────────────────────────────────────────────┐
 │ invoke 1 ──► checkpoint ──► checkpoint ──► guarda  │
 │                                            │       │
 │                                            ▼       │
 │ invoke 2 ──► carga estado previo ──► continúa      │
 └────────────────────────────────────────────────────┘

 thread "ana-002" ──► estado completamente separado

Un thread_id distinto es una conversación distinta, aislada. En una app real suele ser el id de la sesión de chat, o usuario_id + conversación_id.

Detalle clave: en el segundo invoke pasaste solo el mensaje nuevo, no todo el historial. LangGraph carga el estado guardado, le aplica tu actualización con el reducer y sigue. No armes el historial a mano: ese es justamente el trabajo que te está ahorrando.

Inspeccionar el estado guardado

Como el estado está persistido, puedes leerlo cuando quieras — sin ejecutar el grafo:

snapshot = agente.get_state(config)

snapshot.values     # el estado completo (incluye messages)
snapshot.next       # nodos pendientes; () significa que terminó
snapshot.metadata   # step, source, writes

Y el historial completo de checkpoints, del más reciente al más antiguo:

for snap in agente.get_state_history(config):
    print(snap.metadata["step"], snap.next, len(snap.values["messages"]))

Esto no es solo curiosidad: es tu herramienta de debugging. Cuando un agente se comporta raro, get_state_history te muestra paso a paso qué escribió cada nodo. Y es la base del time travel que veremos en el bloque avanzado.

También puedes modificar el estado guardado, como si un nodo lo hubiera escrito:

agente.update_state(config, {"messages": [{"role": "user", "content": "Corrijo: me llamo Felipe B."}]})

update_state crea un checkpoint nuevo (no pisa el anterior) y aplica los reducers normalmente.

De desarrollo a producción: Postgres

InMemorySaver desaparece al reiniciar. En producción quieres una base de datos real:

from langgraph.checkpoint.postgres import PostgresSaver

DB_URI = "postgresql://usuario:clave@host:5432/basedatos"

with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
    checkpointer.setup()          # crea las tablas e índices (solo la 1ª vez)
    agente = builder.compile(checkpointer=checkpointer)
    agente.invoke(entrada, config)

Requiere el paquete langgraph-checkpoint-postgres. Hay también SqliteSaver para persistencia local en un archivo, y variantes Async* si tu app es asíncrona.

Con esto, un usuario puede cerrar el navegador, volver mañana y retomar la conversación exactamente donde quedó. El servidor puede reiniciarse en medio y no se pierde nada.

CheckpointerCuándo
InMemorySaverDesarrollo, notebooks, tests
SqliteSaverApps locales, un solo proceso
PostgresSaverProducción, múltiples instancias

Memoria de corto plazo vs largo plazo

Lo de este módulo es memoria de corto plazo: vive dentro de un thread. Si el mismo usuario abre una conversación nueva (thread_id distinto), el agente no recuerda nada.

Para que recuerde a través de conversaciones —“este usuario prefiere respuestas breves”, “es alérgico a los mariscos”— se necesita el Store, la memoria de largo plazo. Es el Módulo 10.

 Corto plazo (checkpointer) → dentro de un thread → el historial del chat
 Largo plazo (store)        → entre threads       → lo que sabes del usuario

Un aviso sobre el historial largo

El checkpointer acumula todo. Una conversación de 200 turnos son 200 turnos que se le mandan al modelo en cada llamada: caro y, pasado el límite de contexto, imposible. Las estrategias habituales son recortar los mensajes antiguos antes de invocar el modelo, o resumirlos periódicamente en un mensaje de sistema. No confundas “el estado lo guarda todo” con “hay que enviárselo todo al modelo”.

Errores comunes

  • Compilar con checkpointer pero olvidar el thread_id. Sin config, LangGraph no sabe dónde guardar y falla.
  • Reenviar todo el historial en cada invoke. Duplica mensajes. Envía solo el turno nuevo.
  • Usar InMemorySaver en producción. Funciona en tus pruebas y pierde todo en el primer deploy.
  • Reutilizar el mismo thread_id para usuarios distintos. Les mezclas las conversaciones. Es un problema de privacidad, no solo un bug.
  • Olvidar checkpointer.setup() la primera vez con Postgres: las tablas no existen.

La frase para llevarse

“El agente no recuerda porque el modelo sea listo. Recuerda porque tú guardaste el estado y le pusiste nombre a la conversación.”

Qué viene

En el Módulo 7 aprovechamos que el estado ahora se puede pausar y retomar para meter a un humano en el ciclo: human-in-the-loop. El agente se detiene antes de una acción sensible, espera tu aprobación y continúa. Sin checkpointer esto sería imposible — por eso venía después.

Material relacionado

LangGraphPythonMemoriaCheckpointerPersistencia

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.