There's a real answer to "my agent lost four hours of work to a crash," and for most builders it isn't a workflow engine. It's a file. Before you adopt Temporal or wire up a graph checkpointer, you can get most of the way to crash-resumable with the durability primitive you already have: a bucket.
The pitch is narrow and honest. This won't make a payment-issuing agent bulletproof — that needs the real thing, and we'll flag exactly where the line is. But for a loop that reads, reasons, drafts, and calls mostly-idempotent tools for an hour at a time, a checkpoint in object storage turns a crash from "start over" into "resume in minutes." (If you're not sure durability is even the failure you have, start with Checkpointing vs Context Management — the two get confused constantly.)
The pattern in four moves#
1. Make the loop's state one serializable object. Not the process — the state. The message history, the step you're on, and whatever scratchpad the agent has built.
import json, boto3
s3 = boto3.client("s3")
BUCKET, RUN = "agent-checkpoints", "run_2026_07_28_abc"
KEY = f"{RUN}/state.json"
def snapshot(state: dict) -> dict:
# keep it small: messages (or a compacted view), the cursor, scratchpad
return {
"step": state["step"],
"messages": state["messages"],
"scratchpad": state["scratchpad"],
}
2. Write it after every step. One PUT per step is the entire operational cost of this durability. Write after the step's side effects commit, so the checkpoint means "everything up to here really happened."
def save(state):
body = json.dumps(snapshot(state)).encode()
s3.put_object(Bucket=BUCKET, Key=KEY, Body=body)
3. On startup, resume from the last checkpoint if there is one. This is the move that makes a restart free.
def load():
try:
obj = s3.get_object(Bucket=BUCKET, Key=KEY)
return json.loads(obj["Body"].read())
except s3.exceptions.NoSuchKey:
return {"step": 0, "messages": [], "scratchpad": {}}
state = load()
while not done(state):
step(state) # advance one step; commit its side effects
state["step"] += 1
save(state) # checkpoint AFTER the step
4. On clean completion, retire the checkpoint so a later restart doesn't redo finished work — delete the key, or write a terminal marker your load() recognizes as "already done."
That's the whole thing. No cluster, no daemon, no new bill line. If you're on Cloudflare R2, Backblaze, or GCS instead of S3, the same four moves apply verbatim.
The one caveat that decides whether it's safe#
Here's the sentence to tattoo on it: a checkpoint saves state, not the execution point. If a step crashes halfway through, you don't resume halfway through — you resume at the start of that step, and it runs again. For a read, a search, or a draft, re-running is harmless. For a step that charges a card, sends an email, or mutates a database, re-running is a duplicate.
Object-storage checkpointing is durable data, not durable execution. The gap is one crashed step re-firing its side effects — and it's the whole safety question.
Two ways to stay on the safe side of that line:
- Make steps idempotent or guarded. Pass an idempotency key the downstream service dedupes on, or record "this action fired" before trusting the checkpoint and check it on resume. Then a re-run is a no-op.
- Graduate the dangerous steps. When a run is long and full of non-idempotent work, that's the signal to move it onto a durable-execution engine that journals and replays recorded results instead of re-calling — the checkpointer-vs-Temporal distinction, applied.
One refinement worth knowing: if two workers might ever touch the same run, use your object store's conditional write — S3's If-None-Match / If-Match headers make a checkpoint write atomic and single-writer, so a resumed run can't be clobbered by a straggler. That's the cheap insurance that keeps a "just a file" approach honest.
Start with the bucket. It's an afternoon of work, it costs a PUT per step, and it converts your most demoralizing failure — hours of thinking evaporated by one preemption — into a shrug. Reach for the engine when, and only when, the side effects make you.



