LangChain Tutorial (Python): persisting agent state for advanced developers

By Cyprian AaronsUpdated 2026-04-21
langchainpersisting-agent-state-for-advanced-developerspython

This tutorial shows how to persist LangChain agent state in Python so an agent can stop, restart, and continue with the same conversation and tool history. You need this when your agent is handling real user workflows like claims intake, underwriting review, or support triage, where losing context on restart is not acceptable.

What You'll Need

  • Python 3.10+
  • langchain
  • langchain-openai
  • langgraph
  • An OpenAI API key in OPENAI_API_KEY
  • Basic familiarity with LangChain chat models and tools
  • A local environment where you can run a small Python script

Install the packages:

pip install langchain langchain-openai langgraph

Step-by-Step

  1. Start by defining a model, a tool, and a persistent checkpointer. The checkpointer is what stores state between runs, and for local development SqliteSaver is the simplest reliable option.
import os
import sqlite3
from typing import Annotated

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from typing_extensions import TypedDict

os.environ["OPENAI_API_KEY"] = os.environ["OPENAI_API_KEY"]

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]

@tool
def policy_lookup(policy_id: str) -> str:
    """Look up a mock policy record by ID."""
    return f"Policy {policy_id}: active, premium paid through 2026-01-01"

llm = ChatOpenAI(model="gpt-4o-mini")
tools = [policy_lookup]
llm_with_tools = llm.bind_tools(tools)
  1. Build a small graph that reads messages from state and writes the model response back into the same state. This is the key difference from a one-shot chain: the graph has memory hooks built in.
def call_model(state: AgentState):
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_edge(START, "agent")
app = graph.compile(checkpointer=None)
  1. Add persistence with SQLite and compile the graph again. The thread_id becomes your session key; use one per user conversation, case file, or claim number.
conn = sqlite3.connect("agent_state.db", check_same_thread=False)
checkpointer = SqliteSaver(conn)

persisted_app = graph.compile(checkpointer=checkpointer)

config = {
    "configurable": {
        "thread_id": "claim-84721"
    }
}
  1. Run the agent once, then run it again with the same thread_id. The second call sees the previous messages because they were written to SQLite after the first invocation.
result_1 = persisted_app.invoke(
    {"messages": [HumanMessage(content="My policy ID is POL123. Check it.")]},
    config=config,
)

print(result_1["messages"][-1].content)

result_2 = persisted_app.invoke(
    {"messages": [HumanMessage(content="What did you find about my policy?")]},
    config=config,
)

print(result_2["messages"][-1].content)
  1. If you want to inspect stored state directly, load it back through the graph using the same config. This is useful for debugging production sessions or resuming work after a crash.
snapshot = persisted_app.get_state(config)

print(snapshot.values["messages"][-1].content)

history = list(persisted_app.get_state_history(config))
print(f"Stored checkpoints: {len(history)}")
for item in history[:3]:
    print(item.values["messages"][-1].content)

Testing It

Run the script twice without deleting agent_state.db. On the first run, ask about a policy ID; on the second run, ask a follow-up question using the same thread_id, and verify that the agent answers as if it remembers prior context.

If you change thread_id to a new value, you should get a fresh conversation with no prior memory. That’s the behavior you want in production when each customer session must stay isolated.

A good sanity check is to print get_state(config) after each turn and confirm that messages accumulate instead of resetting. If they do not, your checkpointer or thread configuration is wrong.

Next Steps

  • Add tool execution loops so the agent can call policy_lookup automatically when needed.
  • Replace SQLite with Postgres-backed persistence for multi-instance deployments.
  • Store structured business state alongside messages, such as claim IDs, underwriting status, or escalation flags.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides