LangChainLangChain 1.4 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
43 small wins to finish your path

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.

Examplethe only new lines
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.

Exampledesk.py
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.

Examplethreads.py
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")
Example
python threads.py
python threads.py

Four 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.

Example
pytest -q -p no:warnings test_desk.py

That 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.

The finished desk, and what each piece becomes
DeskModelyou wrote itBaseChatModelthe interfaceinit_chat_modelyou install itWordEmbeddingscounts wordsEmbeddingsthe interfaceOpenAIEmbeddingslangchain-openaiInMemoryVectorStorea list in memoryVectorStorethe interfaceChromalangchain-chroma, on diskInMemorySavera dict in memoryBaseCheckpointSaverthe interfaceSqliteSaverdesk.db, or Postgres
Hover or tap a piece to see what it is and which lesson built it.
Swap by swap

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.

Try it yourself
  • Delete desk.db and run threads.py twice 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.