Saving threads with a SqliteSaver checkpointer
SqliteSaver is a checkpointer that keeps every thread in a file. A conversation is still there after the program exits and starts again.
Last updated: 27 Sep, 2026 · LangChain 1.4
SqliteSaver keeps the same threads in a file. It comes from langgraph-checkpoint-sqlite and takes an open connection.
The SqliteSaver connection
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
saver = SqliteSaver(sqlite3.connect("desk.db", check_same_thread=False))The imports for the sqlite saver
check_same_thread=False lets the agent read the connection from whichever thread it runs on. In desk.py the saver goes where InMemorySaver() was.
In desk.py the imports gain sqlite3 and the sqlite saver, in place of the in-memory one.
import sqlite3
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelCallLimitMiddleware, PIIMiddleware
from langgraph.checkpoint.sqlite import SqliteSaver
from desk_model import DeskModel
from guard import no_passwords
from search import search_policies
from tools import Customer, lookup_order, refund_orderThe file-backed saver argument
The last argument to create_agent becomes the file-backed saver, and the rest of the file stays as it is.
checkpointer=SqliteSaver(sqlite3.connect("desk.db", check_same_thread=False)),The desk.py it changes
The whole file, with the two changed lines in place.
import sqlite3
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelCallLimitMiddleware, PIIMiddleware
from langgraph.checkpoint.sqlite import SqliteSaver
from desk_model import DeskModel
from guard import no_passwords
from search import search_policies
from tools import Customer, lookup_order, refund_order
agent = create_agent(
DeskModel(),
tools=[lookup_order, refund_order, search_policies],
context_schema=Customer,
middleware=[
no_passwords,
PIIMiddleware("credit_card", strategy="mask"),
ModelCallLimitMiddleware(run_limit=6),
HumanInTheLoopMiddleware(interrupt_on={"refund_order": True}),
],
checkpointer=SqliteSaver(sqlite3.connect("desk.db", check_same_thread=False)),
)An import, a connection, and the argument that was already there. Everything the desk does with threads was written against the checkpointer, so the rest of the file is untouched.
Ask once, then run it again
The program below asks one question and counts what the thread holds. Running it twice is two separate processes, which is what a restart is.
from chat import say
from desk import agent
thread = {"configurable": {"thread_id": "ravi-1"}}
say("ravi", "Where is A17?", "ravi-1")
print(len(agent.get_state(thread).values["messages"]), "messages saved")python threads.py
python threads.pyFour messages after the first run, eight after the second: the second process found Ravi's thread already there and carried on. With InMemorySaver both runs would have printed four.
Why the second run found the thread
- Four then eight. The first run saved four messages; the second process found Ravi's thread already on disk and continued, reaching eight.
- A restart is two processes. Running the file twice is what a restart is, and the file is what carries the thread across.
InMemorySaverwould print four both times. It holds threads in memory only, so each run would start empty.
The tests, against the real pieces
The five tests from the project were written against the desk, not against the stand-ins. The store is Chroma now and the saver writes to a file, and nothing in the test file changes.
pytest -q -p no:warnings test_desk.pyThat is what those tests were for. Two pieces of the desk were replaced by packages it had never run against, and the owner check, the policy answer, the refusal and the guardrail all still hold.
Pick one to watch it run, step by step.
The desk as it now stands, and what each piece it was built on becomes. The middle column is the only reason the swaps are one line each: everything around them was written against the interface, never against your class.
When threads must survive a restart
- Any desk that must remember a customer between visits, or across a deploy.
- A service on more than one machine, where
PostgresSavertakes a connection string instead of a file.
check_same_thread=False on the connection, or the agent raises when it reads the thread from a different thread than the one that opened the file.The file is the only difference. For a service with more than one machine talking to it, PostgresSaver from langgraph-checkpoint-postgres takes a connection string instead, and the desk around it stays as it is.
Related
- Previous: A persistent vector store with Chroma
- Reference: Persistence and checkpointers
- Delete
desk.dband runthreads.pytwice again. - Ask as a second customer on another thread id and look at the file's size.
- Read
agent.get_state_history(thread)after three questions.
Every expert started right here.