PromptABCD
FeaturesLearnHow it worksUse casesFAQGuideBlogContext Blocks
Sign inGet started free
Sign inSign up
PromptABCD

A calm home for your best AI prompts. Save them once, find them in seconds, reuse them forever.

Product

  • Features
  • Chrome Extension
  • Free Courses
  • How it works
  • Use cases
  • Blog
  • Context Blocks
  • Export Anywhere
  • FAQ

Resources

  • User guide
  • Learn prompting
  • Sign in
  • Get started free

© 2026 PromptABCD. All rights reserved.

Privacy PolicyTerms and Conditions
Home/Blog/AI Harness/Persisting Agent State in the Harness
AI Harness

Persisting Agent State in the Harness

What must a run save to survive a crash, and what should never touch disk? Agent state persistence draws that line: consistent, secret-free, versioned checkpoints.

September 9, 2026·9 min read
ShareShare
⚡Featured Prompt— copy and use right now
@dataclass
class Checkpoint:
    run_id: str
    step: int
    schema_version: int          # so you can migrate later
    messages: list               # the full conversation
    used_tokens: int             # running counters
    pending_action: dict | None  # what it was about to do
    tool_cursor: dict            # progress through multi-part tools

What actually needs to be saved for an agent run to survive a crash — and what should never be written to disk at all? That question sits at the center of agent state persistence, and getting the answer wrong in either direction hurts. Save too little and a resumed run is missing the context it needs to continue correctly. Save too much — or the wrong things — and you've got secrets in your database, a state schema you can't evolve, and blobs so large they slow every checkpoint. This post draws the line carefully, because the line is where most persistence bugs live.

The reason this is subtle is that an agent's "state" isn't one thing. It's the conversation, yes, but also counters, pending actions, tool results, and metadata about the run itself — and each of those has different rules about how it should be stored, versioned, and secured.

What Is Agent State Persistence?

Agent state persistence is durably saving everything a run needs to be reconstructed and continued from a point in time. It's what makes a run survivable — able to outlast a crash, a deploy, a worker reassignment, or a pause for human input. Without it, a run exists only in the memory of one process, and that process is mortal.

The unit of persistence is a checkpoint: a complete, self-contained snapshot of the run at a step boundary. "Self-contained" is the operative word. A good checkpoint contains everything needed to resume with no reference to the process that created it, because that process may be long gone by the time someone resumes.

hljs python
[object Object],
,[object Object], ,[object Object],:
    run_id: ,[object Object],
    step: ,[object Object],
    schema_version: ,[object Object],          ,[object Object],
    messages: ,[object Object],               ,[object Object],
    used_tokens: ,[object Object],             ,[object Object],
    pending_action: ,[object Object], | ,[object Object],  ,[object Object],
    tool_cursor: ,[object Object],            ,[object Object],

What this does: Defines a checkpoint as a snapshot carrying the conversation, the step and token counters, any action the run was about to take, and a

schema_version
for future migrations. Everything needed to resume is here; nothing about the specific worker or connection is. The
schema_version
field looks minor and turns out to be one of the most important things in the struct.

Why It Matters

The immediate payoff is survivability, but the deeper reason to take agent state persistence seriously is that state is the thing you can't regenerate. You can re-run a model call; you can't reconstruct the exact conversation that led to step 40 if you never saved it. State is the record of a path through a space too large to retrace, which is exactly why losing it is so costly.

There's also a correctness dimension. A partially-saved checkpoint — messages updated but the counter not, or the pending action written but the tool cursor stale — resumes into an inconsistent state that produces subtly wrong behavior. Persistence isn't just "write it down"; it's "write a consistent snapshot atomically," so a resumed run is always a run that could have existed.

⚠️ Common mistake: Persisting the conversation but not the run's counters and cursors. On resume, the run has the messages but has forgotten it already spent 180K tokens or already processed 8 of 10 batch items — so it blows its budget or reprocesses work. The conversation is necessary but not sufficient; the bookkeeping around it has to travel too.

Choosing What to Persist

The discipline is to persist everything needed to resume and nothing that's either regenerable or dangerous.

Persist the conversation, the counters (tokens, step, spend), any pending or in-flight action, and cursors that track progress through multi-part work. Persist enough metadata — run ID, status, timestamps — to find and manage the run later.

Do not persist secrets, raw credentials, or full personal data. This is where agent state persistence meets secrets management: the state store is durable and widely read, so a credential in a checkpoint is a credential leaked to backups, replicas, and anyone with database access. Store secret references, never values, exactly as you do everywhere else.

Do not persist things you can cheaply regenerate. Large tool outputs are often better stored by reference — write the blob to object storage, keep a pointer in the checkpoint — than inlined, which keeps checkpoints small and fast to read and write.

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], m ,[object Object], cp.messages:
        m[,[object Object],] = scrub_secrets(m[,[object Object],])      ,[object Object],
    large = extract_large_blobs(cp)                      ,[object Object],
    store.put(cp.run_id, cp.step, asdict(cp), blobs=large)

What this does: Scrubs secrets out of the conversation before writing, offloads oversized tool outputs to blob storage keeping only references in the checkpoint, and stores the result keyed by run and step. The checkpoint stays lean and secret-free, and the heavy data lives where heavy data belongs.

⚡ Pro tip: Store checkpoints keyed by

(run_id, step)
, not just
run_id
, so each step's snapshot is preserved rather than overwritten. Keeping the last few steps costs little and buys you the ability to resume from step 38 when step 39's checkpoint turns out to be the one that triggered a bug. Overwriting means your only restore point is the exact state that broke.

Versioning State So You Can Change It

The reality nobody warns you about: your state schema will change, and when it does, there will be in-flight runs checkpointed under the old shape. If your loader assumes the current shape, every one of those runs breaks on resume the moment you deploy the change. This is why

schema_version
earns its place in every checkpoint.

hljs python
[object Object], ,[object Object],(,[object Object],):
    raw = store.get(run_id, step)
    v = raw.get(,[object Object],, ,[object Object],)
    ,[object Object], v < CURRENT_VERSION:
        raw = MIGRATIONS[v](raw)     ,[object Object],
        v += ,[object Object],
    ,[object Object], Checkpoint(**raw)

What this does: Reads the checkpoint's version and runs it forward through migration functions until it matches the current shape, so an old checkpoint loads correctly even after the schema evolved. A run started last week under version 2 resumes today under version 4 without special-casing. The migration chain is small insurance against a large class of resume failures.

⚡ Pro tip: Write the migration before you ship the schema change, and test it against a real old checkpoint. The tempting shortcut — "I'll migrate old runs later" — means the moment you deploy, every in-flight run is broken until "later" arrives. A migration written alongside the change makes schema evolution boring instead of an incident.

Choosing and Testing Your State Store

Where checkpoints live matters as much as what's in them, and the right choice follows from one property: durability under concurrent access. A relational database is the safe default — transactions give you atomic checkpoint writes for free, and

SELECT ... FOR UPDATE SKIP LOCKED
handles the worker-claiming pattern cleanly. Object storage works well for the large blobs you offload, but it's a poor primary state store because it lacks the transactional guarantees a consistent checkpoint depends on.

Whatever you pick, the store for agent state persistence should be one you'd trust with your most important data, because that's what it holds — the irreplaceable record of every in-flight run. The in-memory cache that's fast and occasionally evicts things is exactly wrong here; an eviction is a lost run.

The part teams skip is testing that persistence actually works, and the only honest test is a real interruption. Save a checkpoint mid-run, kill the process, load the checkpoint in a fresh process, and confirm the run continues correctly to completion.

hljs python
[object Object], ,[object Object],():
    state = run_until_step(prompt, stop_at=,[object Object],)
    save_checkpoint(store, state)
    ,[object Object], state                              ,[object Object],
    restored = load_checkpoint(store, run_id, step=,[object Object],)
    final = run_from(restored)             ,[object Object],
    ,[object Object], final.done ,[object Object], final.answer     ,[object Object],

What this does: Runs partway, checkpoints, discards the in-memory state to mimic a crash, reloads purely from the store, and confirms the run finishes correctly from the restored state. If this test passes, your persistence is real; if it only passes when you don't discard the state, you have a hidden dependency on in-memory data that a real crash will expose at the worst time.

⚡ Pro tip: Run the crash-recovery test in CI at a few different step boundaries, not just one. A checkpoint that resumes fine from step 5 can still be broken at step 40 if some field only appears later in a run. Testing resume at several points catches the "we forgot to persist X, but only long runs touch X" class of bug before a long run finds it in production.

Common Mistakes

The mistake that quietly corrupts runs is non-atomic checkpoint writes. If saving a checkpoint involves multiple writes and a crash lands in the middle, you resume from a half-written state. Write the checkpoint as a single atomic operation — one row, one object, one transaction — so a checkpoint either fully exists or doesn't exist at all.

The second is unbounded conversation growth. A long run's message history grows every step, and naive persistence rewrites the whole thing each time, making checkpoints slower as the run goes. For very long runs, persist messages incrementally or summarize older turns, so checkpoint cost stays flat instead of climbing with run length.

The third is treating the state store as an afterthought with weak durability. This is the one store where losing a write means losing irreplaceable progress. It deserves your durable, backed-up, replicated storage — not the cache you reach for because it's fast.

Conclusion

Agent state persistence is the discipline of saving a consistent, secret-free, versioned snapshot that lets any worker resume a run from a step boundary. Persist the conversation and its bookkeeping, offload big blobs, never persist secrets, version everything, and write atomically. Do that and a crash becomes a pause; skip it and a crash becomes lost work you can't recreate.

Because the shape of your state — what's in a checkpoint, how it's versioned, what's scrubbed — is a design that every resumable agent shares, keep it versioned alongside your prompts in a library like PromptABCD, so the checkpoint format you got right once is the one your next agent inherits, migrations and all.

ai-harnessstatepersistencecheckpointingversioningdurability

Continue Reading

Managing Prompt Templates Across a Harness Codebase
AI Harness

Managing Prompt Templates Across a Harness Codebase

Four divergent copies of one prompt caused a two-day bug. Harness prompt templates management makes prompts versioned, tested, single-source artifacts instead of scattered strings.

September 10, 2026·8 min read
How to Open-Source Your Agent Harness
AI Harness

How to Open-Source Your Agent Harness

An agent harness isn't an ordinary library — it's security-sensitive infra tangled with your secrets. Release an open source agent harness without leaking a key or shipping unusable code.

September 10, 2026·8 min read
Error Taxonomy: Classifying Harness Failures
AI Harness

Error Taxonomy: Classifying Harness Failures

When every failure looks the same, you can't retry, route, or alert correctly. Agent harness error classification gives failures types that drive real behavior.

September 10, 2026·8 min read

Save the prompts from this post

PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.

Start free →
← PreviousBuilding a Harness for Long-Running AgentsNext →Resuming an Interrupted Agent Run
Share this post:
ShareShare