deerflow / backend /checkpointer.py
pjpjq's picture
Persist state across restarts and avoid readability js crash loop
82dbc68
"""LangGraph checkpointer bootstrap for persistent thread state.
Resolution order:
1. `LANGGRAPH_CHECKPOINT_POSTGRES_URI` -> Postgres checkpointer
2. `LANGGRAPH_CHECKPOINT_SQLITE_PATH` -> SQLite checkpointer
3. Fallback to `${DEER_FLOW_HOME}/checkpoints.sqlite`
"""
import atexit
import os
from pathlib import Path
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.checkpoint.sqlite import SqliteSaver
_ctx = None
def _default_sqlite_path() -> str:
deer_flow_home = os.getenv("DEER_FLOW_HOME")
if deer_flow_home:
base = Path(deer_flow_home)
else:
base = Path.cwd() / ".deer-flow"
base.mkdir(parents=True, exist_ok=True)
return str((base / "checkpoints.sqlite").resolve())
def _init_checkpointer():
global _ctx
postgres_uri = os.getenv("LANGGRAPH_CHECKPOINT_POSTGRES_URI", "").strip()
if postgres_uri:
_ctx = PostgresSaver.from_conn_string(postgres_uri)
saver = _ctx.__enter__()
saver.setup()
return saver
sqlite_path = os.getenv("LANGGRAPH_CHECKPOINT_SQLITE_PATH", "").strip() or _default_sqlite_path()
sqlite_file = Path(sqlite_path).expanduser().resolve()
sqlite_file.parent.mkdir(parents=True, exist_ok=True)
_ctx = SqliteSaver.from_conn_string(str(sqlite_file))
saver = _ctx.__enter__()
saver.setup()
return saver
def _close_context() -> None:
global _ctx
if _ctx is not None:
try:
_ctx.__exit__(None, None, None)
finally:
_ctx = None
checkpointer = _init_checkpointer()
atexit.register(_close_context)