The graph runtime survives transient network failures and makes a hard failure restartable, using two opt-in mechanisms plus the existing checkpoint/resume machinery.
CompiledGraph::with_node_retry(RetryPolicy) wraps every node handler. When a
handler fails with a retryable error — TinyAgentsError::Model or
TinyAgentsError::Tool, the transient class classified by
harness::retry::is_retryable — the executor re-runs the node from its start
up to the policy's max_attempts, emitting GraphEvent::NodeRetryScheduled
before each retry. Because a node is never suspended mid-flight, a retry rebuilds
the handler future and context from scratch, matching the durable-execution
model.
Backoff between attempts is computed from the RetryPolicy (exponential, with
optional jitter) but only slept on when the policy opts in via
RetryPolicy::with_backoff_sleep(true). The default is sleep-free so unit tests
stay deterministic; production callers enable it so retries wait a real, growing
delay. The same opt-in switch governs the harness model-call retry loop and
RetryMiddleware.
Both the sequential and parallel runners drive handlers through one shared
run_node_with_retry helper, which also applies the per-node timeout — so a
retried attempt is still bounded by node_timeout.
When a handler fails beyond the retry budget, or with a non-retryable error, the run is not lost. On a checkpointed thread the executor:
- folds the updates of the branches that completed before the failing one into committed state (partial parallel progress is preserved, not discarded);
- persists a failure-boundary checkpoint whose
next_nodesschedule the failed node (and the not-yet-run tail of the step) for re-run, records the successful nodes ascompleted_tasks, and stamps the renderederrorandfailed_nodeinto the checkpoint metadata; - records a
Failedrun status carrying that checkpoint id; - returns the error to the caller.
Without a checkpointer/thread the run aborts immediately with the error, exactly as before — the failure checkpoint is a no-op.
Because resume/resume_from replay from a checkpoint's next_nodes, a failed
run resumes exactly like an interrupted one:
CompiledGraph::retry(thread)— shorthand forresumewith an empty command; re-runs the failed node and the not-yet-run tail from the failure boundary.- Continue on user feedback — inspect committed state with
get_state, edit it withupdate_state(attributed through the reducers), thenretry/resume. The re-run sees the edited state.
See the resilient_graph example (cargo run --example resilient_graph) for
both mechanisms end to end.
TinyAgentsError distinguishes structural/config errors (non-resumable) from
node failures (resumable on a checkpointed thread):
- structural / not retried, not resumable: missing start, missing node, missing edge target, missing route, invalid command/parent/send target, recursion limit, node-visit limit, sub-agent depth, checkpoint required/missing, resume mismatch, reducer conflict, invalid concurrent update, serialization failure, checkpoint backend failure;
- transient / retryable and resumable:
Model,Tool(andTimeoutaborts the node but leaves a resumable boundary like any other node failure).
- Route node errors to a node-specific or default error-handler node.
- Cooperative drain/shutdown with a drain reason.
- Populate the checkpoint
pending_writeslist explicitly (today partial progress is folded into committed state instead).