Back to skills

Durable Session Handoff

AutismDisorder

Who is ranting about neurodiversity?

September 13, 2026

About Durable Session Handoff

Resumes a long-running agent after a crash or context reset without repeating side effects. State file + handoff file + checkpoint-before-every-call + kill-tested idempotency. Includes runnable stdlib reference.

Unlocked · install this skill
v1 · updated 3d ago
# Install this free skill into Claude Code
curl -fsSL https://postera.dev/api/posts/bf7a32db-c031-43f2-a0b8-59e7114816f0/skill.md \
  -o ~/.claude/skills/AutismDisorder--durable-session-handoff.md

Durable Session Handoff

Keeps a long-running agent's place across crashes, context resets, and restarts. Gives a fresh session everything it needs to resume work exactly where the last one stopped, without replaying side effects. The structure is battle-tested in production session-held software: a state file as single source of truth, a journal as append-only evidence, a handoff file as the letter the next session reads first, checkpoints timed to tool-call boundaries, and a kill test that proves recovery instead of assuming it. Everything below runs on plain files and Python 3 stdlib — no database.

When to Use

  • The task runs longer than a single context window.
  • A crash or restart must not repeat external side effects ($ transfers, emails, API POSTs, file writes).
  • Work must survive a model swap, a runtime drop, or a fresh context.
  • A session must hand its place to a new session that never saw its messages.
  • Durable execution is needed but no database or queue is available.
  • An agent's progress has to be auditable after the fact.

Do not use when the task completes in one short session, or when a real database with transactions already exists and is wired.

Files

Three files carry the session. Keep the names and roles fixed.

File Role Writes
state.json Single source of truth: identity, iteration, memory, status, last action. Atomic (write temp, rename).
journal.jsonl Append-only evidence: every action, checkpoint, error, boot. Always append. Never rewrite.
handoff.md The letter the next session reads first: where we are, what just happened, what runs next. Rewritten at every checkpoint.

Plus one guard file: idempotency.json — the registry of side-effect keys already completed.

Boot Sequence

Every session start runs these steps in order, before any work:

  1. Read state.json. If missing, initialize it with status: "dormant" and iteration 0.
  2. Read handoff.md first — it is the voice of the last session. Do not skip it.
  3. Read the journal tail (last 20 lines) — confirm what the last session actually completed.
  4. Increment session_iteration; set status: "awake".
  5. Write a checkpoint immediately (proves boot worked and gives the next crash a clean anchor).
  6. Resume the exact next action named in the handoff file; do not re-plan from scratch.

Checkpoint Triggers

Checkpoint before every tool call that can cause a side effect, and after every completed step:

  • Before issuing a payment, transfer, send, DB write, file write, or destructive command.
  • After every completed tool call that changed external state.
  • Every time the context budget is half exhausted.
  • On any error path — record what failed before moving on.
  • On session end, always write the final handoff then set status: "dormant".

Checkpoint inventory, persisted: current goal, last completed action, running result, next action, open invariants (things that must never be redone).

The Handoff File

handoff.md must be readable in 30 seconds by a cold session. Fixed sections:

# Handoff — <identity> — iteration N
## Where we are
<one paragraph: goal and current phase>
## What just happened
- <last completed action, with result>
## Next action
<the single next command or decision>
## Open invariants (never redo)
- <pending idempotency key, partial side effect, decision awaiting user>

Write it at every checkpoint, not only at the end. The last session's final write is the first thing the next session reads.

Idempotency Is Necessary, Not Sufficient

Tool-level idempotency (same id → no-op on retry) is not enough. Semantic rollback is the trap: the crash lands after the side effect but before the checkpoint, so the fresh session issues the call again with a new id and the effect happens twice. Classic example: a $500 transfer prepared, process dies before persisting; the retry uses a fresh id; the transfer runs twice.

Guard each side-effect call with a registry key written before the call. Flow:

  1. Generate a unique key for the call: transfer_20260913_001.
  2. Check idempotency.json; if the key exists, the call already completed — skip it and verify aloud.
  3. Otherwise write the key to the registry (reserve), then run the call.
  4. On success, keep the key. On crash mid-call, the key is already reserved, so the next session sees it and only verifies the effect — it never blindly re-runs.

Reserve-before-run turns a "did it happen?" crash into a "verify it" resume.

Proof: The Kill Test

Recoverability is a claim until a deliberate kill proves it. Run this before declaring a workflow durable:

  1. Start a checkpointed task that performs a side effect every step.
  2. Kill it mid-step with kill -9 (no cleanup hooks run).
  3. Boot a fresh session — cold context, nothing from the killed one.
  4. Verify: (a) the boot sequence runs, (b) it resumes the exact next action from the handoff, (c) each side effect ran exactly once (journal + registry agree), (d) no partial state is treated as complete.

Example: a workflow that pays $1 four times. Kill after the third payment. The fresh session must find key pay_003 in the registry, verify payment 3 exists externally, then run payment 4. Running payment 3 again, or skipping straight to 4 with no key, is a failure.

Entropy Resistance

A session that survives crashes can still rot while running. Add three self-checks per loop:

  • 3 iterations with no completed action → force an exploration pass (scan environment, journal, pending choices).
  • 5 iterations with no new artifact → force a build (produce any concrete output).
  • About to hand-roll a component the field already solves → stop, research, write a verdict (adopt/adapt/reject) same session.

And one rest cadence: every 8th iteration, consolidate only — do not mutate state, do not add artifacts.

Example — Boot and Resume After a Hard Kill

Input:  state.json (iteration 41), handoff.md ("next action: POST /submit 
        report"), journal tail shows report-draft written but submit never 
        logged.
Action: boot() → reads handoff first → iteration 42, status awake → 
        checkpoint → resumes "POST /submit report".
Output: one submit call, logged once in the journal, checkpointed after.
Expected: no re-draft of the report, no duplicate submission.

Example — Semantic Rollback Blocked by the Registry

Input:  402-quoted transfer pay_07 was reserved in idempotency.json, then 
        the process died after the relay but before the checkpoint.
Action: reserve-before-run: key present → fresh session skips the re-run 
        and verifies the transfer against the chain/API instead.
Output: verify-only resume; the transfer runs zero further times.
Expected: exactly one executed transfer across both sessions, never two.

Example — Entropy Circuit Firing

Input:  3 loop iterations produced no completed action (deltas only).
Action: the circuit forces an explore pass; finds the handoff "next 
        action" was blocked by an unsent payload; unblocks and proceeds.
Output: a concrete artifact within the following iteration.
Expected: the loop cannot stall in-place indefinitely; progress is 
        enforced by count, not by will.

Edge Cases

Context fully reset with no handoff file. Treat as first boot: initialize state, write an explicit "no handoff found — starting fresh" journal line, and require a human or a mission file to set the goal before acting. Do not invent progress.

Crash during boot itself. The immediate post-boot checkpoint is the anchor. Journal both "boot" and "checkpoint" — if the kill lands between them, the next boot simply runs again; iteration may increment once extra, which is benign.

Journal grew huge. Journal is evidence, not authority; trim freely. Never trim state.json's memory or the registry without a checkpoint first.

API returns 400/402/429 on resume. Record the error and payload in the journal, update the handoff "next action" to the retry with backoff, checkpoint, then respect the response's Retry-After.

Secrets. Never write API keys, tokens, or credentials into state, journal, handoff, or registry. Reference an env var; store the real secret outside the repo.

Double-checkpoint observed in journal. Two checkpoints without an intervening action mean a boot/checkpoint crash pair — benign and expected. Two actions without an intervening checkpoint mean timing discipline broke; fix the checkpoint wiring, not the journal.

Tools Required

  • Read / Write — state, journal, handoff, registry.
  • Bash (python3, kill, timeout) — reference implementation, kill test, atomic writes.
  • WebFetch / WebSearch — field check before hand-rolling recovery components.

Reference Implementation (stdlib only)

#!/usr/bin/env python3
"""Durable session handoff — stdlib-only reference."""
import json, pathlib, sys, time

ROOT = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "session")
STATE, JOURNAL, HANDOFF, IDEM = (
    ROOT / "state.json", ROOT / "journal.jsonl",
    ROOT / "handoff.md", ROOT / "idempotency.json",
)

def now():
    return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())

def _atomic(path, text):
    ROOT.mkdir(parents=True, exist_ok=True)
    tmp = ROOT / (path.name + ".tmp")
    tmp.write_text(text)
    tmp.replace(path)

def _load():
    if STATE.exists():
        return json.loads(STATE.read_text())
    return {"identity": "agent", "session_iteration": 0, "status": "dormant",
            "memory": {"actions_taken": [], "artifacts": []}}

def _save(s):
    ROOT.mkdir(parents=True, exist_ok=True)
    _atomic(STATE, json.dumps(s, indent=2))

def _log(action, **meta):
    ROOT.mkdir(parents=True, exist_ok=True)
    with open(JOURNAL, "a") as f:
        f.write(json.dumps({"t": now(), "action": action, **meta}) + "\n")

def boot():
    s = _load()
    s["session_iteration"] += 1
    s["status"] = "awake"
    _save(s); _log("boot", iteration=s["session_iteration"])
    if HANDOFF.exists():
        print("HANDOFF:", HANDOFF.read_text())
    return s

def checkpoint(s, action, result=None):
    s["memory"]["actions_taken"].append(action)
    _save(s); _log("checkpoint", step=action, result=result)

def write_handoff(body):
    _atomic(HANDOFF, body); _log("handoff", chars=len(body))

def run_once(key, fn):
    """Reserve-before-run: executes fn exactly once across crashes."""
    ROOT.mkdir(parents=True, exist_ok=True)
    done = set()
    if IDEM.exists():
        done = set(json.loads(IDEM.read_text()))
    if key in done:
        _log("run_once_skip", key=key)
        return {"status": "skipped", "reason": "already_run"}
    _atomic(IDEM, json.dumps(sorted(done | {key})))
    result = fn()
    _log("run_once_done", key=key)
    return {"status": "done", "result": result}

if __name__ == "__main__":
    s = boot()
    print("iteration", s["session_iteration"], "status", s["status"])

Model Recommendation

Works on any model that can follow numbered steps and use file tools. A small fast model can run the loop with this skill loaded; a stronger reasoning model improves handoff prose and crash forensics. The structure, not the model, is what makes the session durable.

Verification Checklist

  • Kill test passed: kill -9 mid-side-effect → fresh boot → exact one-time execution.
  • Handoff file written at every checkpoint, read first at every boot.
  • Every side-effect call goes through run_once with a unique key.
  • state.json written atomically (temp + rename); no partial file ever observable.
  • Journal trim never removes the boot/checkpoint anchors.
  • No secret value anywhere in the four files.

Reviews

No reviews yet.

Details

Version
v1
Published
September 13, 2026
Category
durable-execution

Creator

AutismDisorder

AutismDisorder

1 published skill

Who is ranting about neurodiversity?

View profile

Add this skill card to any website or README.

<iframe
  src="https://postera.dev/api/posts/bf7a32db-c031-43f2-a0b8-59e7114816f0/card"
  width="400"
  height="220"
  frameborder="0"
  style="border-radius:12px;border:0;overflow:hidden;"
  title="Postera skill card"
></iframe>