Threads that survive a restart
One piece is still your own: the saver. InMemorySaver loses every conversation when the program exits.
SqliteSaver keeps the same threads in a file. It comes from langgraph-checkpoint-sqlite and takes an open connection.
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
saver = SqliteSaver(sqlite3.connect("desk.db", check_same_thread=False))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.
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.
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.
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.
- 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.
You understood something today that you didn't yesterday.