use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tinyagents::harness::ids::ExecutionStatus;
use tinyagents::harness::retry::RetryPolicy;
use tinyagents::{
GraphBuilder, InMemoryCheckpointer, NodeContext, NodeResult, Result, TinyAgentsError,
};
#[derive(Clone, Debug, Default)]
struct Pipeline {
fetched: bool,
committed: bool,
log: Vec<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let checkpointer = Arc::new(InMemoryCheckpointer::<Pipeline>::new());
let fetch_attempts = Arc::new(AtomicUsize::new(0));
let outage = Arc::new(AtomicUsize::new(0));
let fa = fetch_attempts.clone();
let og = outage.clone();
let graph = GraphBuilder::<Pipeline, Pipeline>::overwrite()
.add_node("fetch", move |mut state: Pipeline, _c: NodeContext| {
let fa = fa.clone();
async move {
let n = fa.fetch_add(1, Ordering::SeqCst);
if n < 2 {
return Err(TinyAgentsError::Model(format!(
"connection reset (fetch attempt {})",
n + 1
)));
}
state.fetched = true;
state.log.push(format!("fetched on attempt {}", n + 1));
Ok(NodeResult::Update(state))
}
})
.add_node("commit", move |mut state: Pipeline, _c: NodeContext| {
let og = og.clone();
async move {
if og.load(Ordering::SeqCst) == 0 {
return Err(TinyAgentsError::Tool(
"commit endpoint unreachable (outage)".into(),
));
}
state.committed = true;
state.log.push("committed".into());
Ok(NodeResult::Update(state))
}
})
.set_entry("fetch")
.add_edge("fetch", "commit")
.set_finish("commit")
.compile()
.unwrap()
.with_node_retry(RetryPolicy::default().with_max_attempts(4))
.with_checkpointer(checkpointer.clone());
let thread = "orders-4711";
let err = graph
.run_with_thread(thread, Pipeline::default())
.await
.expect_err("commit is down, so the first run fails");
println!("first run failed as expected: {err}");
println!(
" fetch took {} attempts",
fetch_attempts.load(Ordering::SeqCst)
);
let snapshot = graph
.get_state(thread, None)
.await?
.expect("a failure-boundary checkpoint exists");
println!(
" durable state: fetched={}, committed={}",
snapshot.values.fetched, snapshot.values.committed
);
println!(" will re-run on resume: {:?}", snapshot.next_nodes);
println!(" checkpoints on thread: {}", checkpointer.count(thread));
outage.store(1, Ordering::SeqCst);
let resumed = graph.retry(thread).await?;
assert_eq!(resumed.status.status, ExecutionStatus::Completed);
println!("\nrestarted to completion:");
println!(
" fetched={}, committed={}",
resumed.state.fetched, resumed.state.committed
);
for line in &resumed.state.log {
println!(" - {line}");
}
Ok(())
}