use lc_langgraph::{
AgentState, Checkpointer, GraphBuilder, GraphError, SqliteCheckpointer, StateUpdate, END, START,
};
const THREAD_ID: &str = "example-thread-001";
fn build_graph(
db_path: &std::path::Path,
) -> Result<lc_langgraph::CompiledGraph<AgentState>, Box<dyn std::error::Error>> {
let checkpointer = SqliteCheckpointer::<AgentState>::new(db_path, THREAD_ID)?;
let mut builder = GraphBuilder::<AgentState>::new();
for i in 1..=3usize {
let name = format!("n{i}");
builder = builder.add_node_fn(name.clone(), move |state: &AgentState| {
let mut next = state.clone();
next.set_output(format!("reached n{i}"));
Ok(StateUpdate::full(next))
});
if i == 1 {
builder = builder.add_edge(START, name.clone());
}
if i == 3 {
builder = builder.add_edge(name, END);
}
}
for i in 1..3usize {
builder = builder.add_edge(format!("n{i}"), format!("n{}", i + 1));
}
Ok(builder
.compile()?
.with_interrupt_before(vec!["n3".to_string()])
.with_recursion_limit(10)
.with_checkpointer(checkpointer))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_path = std::env::temp_dir().join("lc_durable_checkpoint_example.db");
let _ = std::fs::remove_file(&db_path);
let graph = build_graph(&db_path)?;
match graph
.invoke(AgentState::new("durable payload".to_string()))
.await
{
Err(GraphError::ExecutionInterrupted(node)) => {
println!("phase 1: interrupted before '{node}' — process may exit now");
}
other => panic!("expected an interrupt, got {other:?}"),
}
drop(graph);
let cp = SqliteCheckpointer::<AgentState>::new(&db_path, THREAD_ID)?;
let history = cp.list().await?;
println!(
"phase 2: {} durable checkpoint(s) for thread '{THREAD_ID}'",
history.len()
);
if let Some(first) = history.first() {
let old = cp.load(first).await?;
println!(
"time travel: oldest checkpoint input='{}', output={old:?}",
old.input
);
}
if let Some(target) = history.get(1) {
match cp.update_state(target, &cp.load(target).await?, 1).await {
Ok(version) => println!("update_state: checkpoint now at version {version}"),
Err(GraphError::CheckpointVersionConflict {
expected,
actual,
..
}) => println!("update_state: conflict (expected v{expected}, actual v{actual}) — reload and retry"),
Err(other) => return Err(other.into()),
}
}
let resumed_graph = build_graph(&db_path)?;
let execution = resumed_graph
.create_resume_execution("n3")
.await
.expect("a checkpoint to resume from");
println!(
"phase 3: resuming with {} recursion step(s) already consumed",
execution.recursion_count
);
let result = resumed_graph.resume(execution).await?;
println!(
"phase 3: resumed run finished — output='{}', total steps={}",
result.final_state.output.as_deref().unwrap_or("<none>"),
result.recursion_count
);
Ok(())
}