.agents/skills/adk-architecture/references/architecture-workflow-resumability.md
How a Workflow node preserves and restores execution state across a
human-in-the-loop pause, how that compares to peer agent frameworks, and where
it is heading. This is the whole-workflow view; the interrupt/resume lifecycle
of a single node is covered separately.
The first thing to be clear about: a Workflow reconstructs its progress from
the session event stream on every run, so it resumes whether or not resumability
is configured. The is_resumable flag does not switch resume on or off. What it
switches on is durability — persisting loadable checkpoints and letting an
invocation be continued across separate runner calls.
On every run, the workflow scans the current invocation's session events and rebuilds its in-memory loop state (which nodes completed, their outputs, which are still waiting on interrupts). Completed nodes are fast-forwarded from their recovered output rather than re-executed; the interrupted node re-runs with the supplied responses.
This path (_run_impl -> ReplayManager.scan_workflow_events) has no
is_resumable guard — the scan matches events purely by invocation id. The loop
state is not persisted and the checkpoint the workflow writes is not read back
on the load path; the session event log is the source of truth. So within an
invocation, a workflow is inherently replay-resumable, flag or no flag.
is_resumable actually adds: durabilityResumabilityConfig.is_resumable is a durability switch. When it is set:
LlmAgent,
SequentialAgent, LoopAgent, ParallelAgent, and LlmAgents wrapped as
workflow nodes) write agent_state / end_of_agent events into the session
only when the invocation is resumable. These make progress loadable across a
process boundary.The config's own definition is durability-shaped: pause an invocation on a long-running call, and resume it from the last event if it paused or failed midway, best-effort and at-least-once, with in-memory state lost. So the accurate statement is: the flag decides whether progress is persisted as loadable checkpoints and whether an invocation can be resumed across runner calls — not whether the workflow can resume. Resumability here is really durability.
Workflow node writes a checkpoint, but does not read oneWorkflow._emit_node_checkpoint does persist a node-status snapshot when
ic.is_resumable — an agent_state event carrying {"nodes": {name: {status, interrupts, resume_inputs}}} for each static child. Two sibling markers are
written under the same flag: _maybe_reemit_replayed_output re-surfaces a
fast-forwarded node's output, and _emit_end_of_agent records that the
workflow ran to completion.
What is missing is the other half. Nothing loads that checkpoint: setup still
begins with ReplayManager.scan_workflow_events over the whole invocation, and
_run_impl carries a TODO: resume from checkpoint event. So the checkpoint
is currently an observable record of progress rather than the durable state
resume actually runs off. Resume cost still scales with history length.
Every mainstream agent framework persists a state snapshot with a position cursor and, on resume, loads that snapshot — none reconstruct by replaying the entire history.
| Framework | Durable unit | Position cursor | Resume |
|---|---|---|---|
| LangGraph (graph) | StateSnapshot per super-step in a pluggable checkpointer | next nodes + parent-pointer chain + per-task pending writes | re-invoke same thread id; load latest checkpoint, re-run only the interrupted node |
| pydantic-graph (graph) | NodeSnapshot{state, node, status} via a state-persistence backend | the snapshot's node = next node to run; status created/pending/running/success | iter_from_persistence() loads the next created snapshot |
| OpenAI Agents SDK (loop) | serialized RunState blob | run cursor inside the state; correlate by tool-call id | deserialize the state, apply approvals, resume the run |
| Pydantic AI (loop) | message history + deferred-tool results | implicit in the transcript; correlate by tool-call id | new run over the prior message history |
ADK's durable unit today is the event log itself, and resume is by replay over it. Two patterns from the peers are worth copying, both consistent across them:
The write half exists. The remaining step — peer-aligned, and the one ADK's own composite agents already take — is to make that checkpoint the thing resume reads:
agent_state
checkpoint instead of scanning the invocation's events, then continue:
re-run only the interrupted node and dispatch newly-ready successors.Workflow node with composite agents and with LangGraph / pydantic-graph /
the OpenAI SDK.This only applies when durability is on. Without is_resumable nothing is
persisted, and the workflow continues to resume within an invocation by replay.
rerun_on_resume. This is decided in the
shared replay-interception logic and should be settled before a load path
relies on it.model_dump),
so any persistence backend works and no code objects are serialized; node
objects are rebound from the in-memory graph definition on resume.