Decomposed from #2116 per the #2118 Phase 1 task "clarify and decompose
#2116". Refs #1968; complementary to #2121, which adds safe-boundary
checkpoint capture for manually driven runs and lists runner-level durable
resumption as remaining work.
Summary
AgentRun is a sans-IO, Serialize + Deserialize state machine, and its
module docs advertise persisting a run between steps and resuming it in
another process. The capture half works today by hand-driving next_step() /
model_response() / tool_results().
The resume half is missing: AgentRunner::run / stream build their
AgentRun internally and cannot accept a restored one. Anyone resuming a
persisted run must hand-write the entire drive loop for the resume leg — and
thereby loses the runner's hook stack, tool-server dispatch (including MCP
tools), conversation-memory append, and telemetry. A process restart or an
approval-gate suspension leaves the second half of the run without the
runner's integrations.
Proposal
One additive entry point that hands a deserialized run back to the existing
shared drive loop, so a resumed run behaves identically to one that was
never suspended:
let run: AgentRun = serde_json::from_str(&persisted)?;
let response = agent.resume(run).run().await?; // or .stream().await
Scoped to the epic's stated boundary — "define safe pause/checkpoint
boundaries; do not attempt to serialize or suspend arbitrary Rust futures":
resume operates only on the states AgentRun already serializes (model
boundary, pending tool batch). No future suspension, no new checkpoint
mechanism — this is the missing consumer of the serialization AgentRun
already has.
Acceptance criteria
I have an implementation ready and will open a PR against this issue.
Decomposed from #2116 per the #2118 Phase 1 task "clarify and decompose
#2116". Refs #1968; complementary to #2121, which adds safe-boundary
checkpoint capture for manually driven runs and lists runner-level durable
resumption as remaining work.
Summary
AgentRunis a sans-IO,Serialize + Deserializestate machine, and itsmodule docs advertise persisting a run between steps and resuming it in
another process. The capture half works today by hand-driving
next_step()/model_response()/tool_results().The resume half is missing:
AgentRunner::run/streambuild theirAgentRuninternally and cannot accept a restored one. Anyone resuming apersisted run must hand-write the entire drive loop for the resume leg — and
thereby loses the runner's hook stack, tool-server dispatch (including MCP
tools), conversation-memory append, and telemetry. A process restart or an
approval-gate suspension leaves the second half of the run without the
runner's integrations.
Proposal
One additive entry point that hands a deserialized run back to the existing
shared drive loop, so a resumed run behaves identically to one that was
never suspended:
Scoped to the epic's stated boundary — "define safe pause/checkpoint
boundaries; do not attempt to serialize or suspend arbitrary Rust futures":
resume operates only on the states
AgentRunalready serializes (modelboundary, pending tool batch). No future suspension, no new checkpoint
mechanism — this is the missing consumer of the serialization
AgentRunalready has.
Acceptance criteria
run()/stream()—no second loop; hooks, tool server, memory, and telemetry wired.
internal_call_ids; behavior when the tool registry changed acrossthe suspend/resume boundary is defined and documented.
max_turns,add_hook, …) keep working afterresume, seeded from the suspended run's values.
history) and the completed run's messages are appended exactly once
across the boundary.
I have an implementation ready and will open a PR against this issue.