dsh-session-auto-resume
A DeepSeek Harness plugin that automatically resumes agent sessions interrupted by app closure. When the DSH host relaunches, sessions that were mid-work at closure are detected from persisted state and recovered without human action:
- Sessions with an active goal are explicitly resumed and the goal re-armed — after which the bundled
goal-round-driver continues the round budget automatically. (The driver deliberately "never revives work on its own"; this plugin performs the explicit resume the driver requires.)
- Mid-turn sessions without a goal are detected and logged with a ready-to-run resume command — surfaced, never auto-worked.
- Cleanly-ended, stale (>48 h), live, and subagent-owned sessions are skipped. Per-boot resume cap. Fail-closed on goal transitions. Never-throw activation.
Why
The desktop host has quit-time inspection of active work but no startup recovery path: after any closure (crash, SIGTERM, deliberate quit/reload), interrupted sessions stay interrupted until a human notices. This plugin closes that gap entirely outside the app bundle, so it survives app updates.
Live verification
Tested against a real closure-then-relaunch cycle (operator-approved SIGTERM of the running host; app relaunched):
- An unscripted interrupted goal session (not planted, not known to the operator) was auto-resumed and its goal re-armed at boot; the round driver then continued the session.
- A session holding an active goal continued on an automatic continuation round with no human message.
- Round budgets advanced correctly; a live session was correctly skipped; 489 subagent-owned sessions were correctly excluded in one pass.
verify-d1.mjs (included) turns any future closure into a one-command verdict: PASS (exit 0) / FAIL (exit 1, names the broken check + evidence tail) / VOID (exit 2, no closure recorded after the plugin armed). Its own fixture selftest (verify-d1.test.mjs) exercises every verdict path against synthetic evidence trees.
Design notes
- Policy is explicit and minimal: only durable goal phase
active auto-resumes; paused/blocked goals are deliberate human states and are never re-armed; non-goal sessions are surfaced only. The plugin's installation is the one-time authorization.
- Resume mirrors the harness's own resume flow (
agents.resume({resumeSessionId, agentOptions, setup}) + goals.resume(agent, {id, revision})), including binding the default model — a resume without a model binding produces turns failing with prompt variable "{{model}}" has no value (the agent loop binds that variable from agent.options). This plugin binds it and logs a loud resume-no-model line if it cannot.
- It never hand-appends to session logs. Session-log append contracts (
headerSeq must reference an earlier request/header) make cold-log markers unsafe; detection is recorded in the plugin's own log instead.
- Fail-closed everywhere: goal resume throws on exhausted budgets or invalid phase transitions (caught, logged, never retried); the boot pass, classification, logging, and resume are each guarded so a plugin failure cannot block host boot.
Install (as a local profile plugin)
-
Copy this package to ~/.dsh/plugins/dsh-session-auto-resume.
-
Link it into your profile's dependencies and add it to the profile's dsh.profile.bundles list:
"dependencies": { "@local/dsh-session-auto-resume": "link:/Users/you/.dsh/plugins/dsh-session-auto-resume" },
"dsh": { "profile": { "bundles": [ "@local/dsh-session-auto-resume" ] } }
-
The package's own cordis.patch.yml carries the required insert row (a bundles-list entry alone is silently skipped). It ships with dryRun: true — the first boot logs would-resume decisions without acting; verify them against your live sessions, then set dryRun: false.
-
Restart the app. Evidence lands in ~/.dsh/logs/session-auto-resume.log (ARMED on every activation, one pass-done per boot).
Test
node selftest.mjs # classification truth table, fake-ctx passes, mutation checks
node verify-d1.test.mjs # proves verify-d1 on every verdict path (synthetic fixtures)
Status
- Selftest: all legs green under the app runtime (
ELECTRON_RUN_AS_NODE).
- Live fire: one real closure-recovery verified end to end; one defect found and fixed during it (the model binding above).
- Known limitation: sessions are matched against the most recent 30 persisted sessions by creation time (config
scanLimit), and the mechanism mounts at boot only (no hot reload of plugin-file changes).
Not affiliated with DeepSeek. MIT licensed. Filed upstream as a discussion — this repo is the reference implementation.