Thread memory and resume

A conversation spans many HTTP requests. Each request is stateless by default, so something has to remember what happened last turn. LangGraph ships a checkpointer abstraction (MemorySaver for dev, SqliteSaver or PostgresSaver for production). The workshop uses an in-process dictionary for clarity, and the pattern is identical.

booking_graph.py
python
# Simple in-process memory. For production use a LangGraph checkpointer
# like MemorySaver, SqliteSaver, or PostgresSaver.
_THREADS: Dict[str, BookingState] = {}

def get_thread_state(thread_id: str) -> BookingState:
    state = _THREADS.get(thread_id)
    if state is None:
        state = BookingState(
            thread_id=thread_id,
            history=[],
            slot_options=[],
            events=[],
            status="gathering",
            assistant_reply="",
        )
        _THREADS[thread_id] = state
    return state

def reset_thread(thread_id: str) -> None:
    _THREADS.pop(thread_id, None)

The thread store maps thread_id to the last-seen state. First turn bootstraps an empty state with status = gathering. Subsequent turns receive the persisted state and build on it. Reset is an explicit action: pop the key and start fresh.

Resume flow across turns

Thread memory lets turn N+1 pick up exactly where turn N paused.

One more naming note. The thread store exposes reset_thread. The router calls it through a service function named reset_conversation, so the endpoint reads cleanly while the actual pop still happens in reset_thread.

router.py
python
@router.post("/reset", response_model=BookingResponse)
async def reset_endpoint(request: ChatRequest):
    if not request.thread_id:
        raise HTTPException(status_code=400, detail="thread_id is required to reset")
    reset_conversation(request.thread_id)
    return BookingResponse(
        thread_id=request.thread_id,
        status="reset",
        message="Conversation cleared. Start a new booking when you're ready.",
    )

A reset endpoint lets the UI offer a "start over" button without stranding state in memory. The node-writer convention says nothing else can clear state. Reset is explicit, initiated by the user, and visible.

The interface is the same (get + save by thread_id), the durability changes. MemorySaver keeps state in process like the dict, so restarts wipe it. SqliteSaver writes to a local file and survives restarts on a single host. PostgresSaver writes to a shared database so a fleet of FastAPI workers can all read and write the same thread. You pick the checkpointer based on your deployment shape without touching graph code.

Checkpoint: Final checkpoint

Loading practice…