Skip to content

Durable suspend and resume

A long agent session sometimes has to wait on a human: a mid-flight approval, an external review, a credential rotation, a dependency landing. Historically that wait cost real infrastructure. The pre-spawn approval gate can halt a task only before a worker exists, and the post-completion review gate only after it exits; there is no human-in-the-loop halt inside a running session. So the process, its worktree sandbox, its parallelism seat, and its budget-envelope reservation all stayed allocated for the entire wait. On a capped pool that reservation blocked other tasks from dispatching.

Durable suspend and resume closes that gap. A task that must wait can park: the park emits an attested receipt that releases the seat, the sandbox, and the budget envelope, and a later resume restores from that receipt deterministically. The suspension itself is the artifact -- without the chain there is no suspension, only a dead process.

The park is a receipt, not a flag

A park is a pair of Merkle-chained journal rows plus matching HMAC audit-chain receipts, and every infrastructure release hangs off the suspend receipt's hash:

  1. The suspend row is the identity. A suspend row is appended to the task's event journal (.sdd/runs/task-<task-id>/journal.jsonl) with the same row discipline as the checkpoint substrate: the adapter-native session id, a workspace hash over the worktree, the journal head, and the envelope balance at park time. The row's event_hash is the suspension's identity.
  2. The receipt binds the hash before any effect. A task.suspend_receipt is written into the HMAC audit chain before a single resource is freed.
  3. Each release references the receipt. The process is terminated and reaped, the sandbox is torn down, the parallelism seat is returned, and unspent envelope headroom is released back to the pool as a chained budget event -- each recorded as a task.suspend_resource_release row referencing the suspend receipt hash. A release effect with no matching receipt is rejected, fail closed.
  4. The park is persisted. The task enters a SUSPENDED state recorded in the work ledger, so the park survives orchestrator restarts and daemon crashes the same way detached run state does. A parked task is deliberately kept out of the resume frontier: it wakes on an explicit resume, never on an auto-restart.

Released headroom

The headroom returned to the pool is the reservation minus the spend recorded against it at park time (clamped at zero):

released_usd = max(reserved_usd - spent_usd, 0.0)

It appears as a chained budget event, and because no cost is recorded while the task is parked, the per-envelope rollup attributes zero spend to the parked window. On a capped pool the freed headroom is immediately dispatchable to a queued task.

Resume is a deterministic projection

bernstein task resume re-materializes the continuation and derives its mode as a pure function of the suspend row and the adapter capability -- the same warm/fork/cold decision the checkpointed retry path uses:

Condition Effective mode
Same workspace hash + live native session warm
Fork requested + fork-capable adapter fork
Stale session, drifted workspace, or no capability cold (with a recorded reason)

The decision carries a stable decision_hash: two hosts with the same suspend row and adapter capability derive the byte-identical decision. Downgrades are never silent -- a fork or cold resume always records its reason.

The resume writes a task.resume_receipt binding the suspend receipt it continued from, the effective mode, and the new workspace hash. The suspend and resume receipt pair is the continuity proof.

Wake on approval

bernstein task suspend <task-id> --until approval composes the park with the pre-spawn approval sentinel. The parked task refuses to resume until bernstein approve <task-id> lands its decision file; the resume receipt binds the approval decision digest, and a <task-id>.resumed marker binds the resume receipt hash, so the approval record and the resume receipt reference each other. This makes approval checkpoints usable mid-session, not only at the pre-spawn and post-completion boundaries.

Verifying continuity offline

bernstein audit verify-suspension <task-id> proves, from a copied chain and the task journal alone, that a resumed task continued from exactly the parked workspace hash -- or shows the recorded fork/cold downgrade with its reason. No worker, no network, and no live worktree are required. Two tamper posture guarantees back the proof:

  • Mutating the suspend row breaks the journal Merkle chain at that exact index, and the fail-closed read refuses to fuel a resume from a tampered row.
  • Mutating the suspend receipt breaks the HMAC audit chain at that exact position, which bernstein audit verify reports.

Commands

# Park a running task, releasing its seat, sandbox, and budget envelope.
bernstein task suspend T-abc123 --reserved-usd 10 --spent-usd 2.5

# Park until an operator approves the wake.
bernstein task suspend T-abc123 --until approval
bernstein approve T-abc123          # lands the wake decision

# List durably-parked tasks with their parked-at hash and freed resources.
bernstein task list-suspended

# Resume from the attested suspend receipt (warm when the workspace matches).
bernstein task resume T-abc123

# Prove the continuation is the same run, offline.
bernstein audit verify-suspension T-abc123

What this buys you

Runs that wait on humans stop paying for the wait: an overnight approval halt holds zero seats, zero sandboxes, and zero reserved envelope headroom, so capped pools keep dispatching. And every park is explainable afterwards -- the suspend and resume receipt pair proves the continuation is the same run, so audits of long-lived runs no longer contain unexplained process gaps.

Relationship to the steering pause

The in-place steering pause is the momentary halt for quick correction: it checkpoints a worker and parks its claim but keeps the infrastructure reserved. Durable suspend is the variant that frees infrastructure and proves continuity. Both share the checkpoint row shape and the receipt-before-effect rule, so a steering pause that exceeds a wait threshold can upgrade into a durable suspend.