DeepSeek Harness sessions: resume, compact, and recover without losing replay evidence
Quick answer
At the source snapshot checked for this guide—default branch master, commit 141eb6fef83422698aef7a981029e843e8161534—DeepSeek Harness treats the session event log as the source of truth. The model transcript is a projection of that log; persistence makes the same events durable; compaction appends a summary that replaces older nodes on the current surface without deleting their raw evidence.
append-only SessionEvent log
-> JSONL or SQLite persistence -> cold-load repair -> resume
-> foldSurface / deriveMessages -> current model context
-> compaction start + summary + replacement + end
-> shadowed raw events remain queryable and traceable
This design gives you replay evidence, but not an automatic backup policy or exactly-once side effects. A durable tool/call with no tool/result is recovered as an unknown outcome: verify the external effect before retrying.
DeepSeek Harness is still a Developer Preview. GitHub currently exposes dsh-v0.1.0-rc.8 at this commit, while npm still reports @deepseek-ai/dsh@0.1.0-rc.7. Pin the artifact and format you actually test.
Who this is for
This guide is for agent reliability engineers and developers running long coding sessions that must survive process crashes, context pressure, and operator handoff. Read the Cordis architecture guide for the service seams and the permission and sandbox pipeline before resuming sessions against a real repository.
The state-ownership map
| State | Owner | Replay role | Boundary to verify |
|---|---|---|---|
| Raw events | In-memory Session |
Canonical append-only history, including stream chunks | Events are not durable merely because turn/end exists in memory |
| Header | Persistence metadata | Format version, cwd, parent, seed length, preset | Metadata is outside model history |
| Durable log | JSONL or SQLite provider | Restores exact logical events | JSONL has one artifact; SQLite shares one database |
| Current surface | foldSurface() / deriveMessages() |
What the next model request sees | Shadowed and log-only events are not current context |
| Compaction checkpoint | compaction/* plus replacement user/message |
Shrinks current context | Summary is a projection, not deletion of raw history |
| Spill artifact | Separate SpillStore |
Holds oversized tool text | A fork inherits locators, not copied files or retention guarantees |
The persistence listener batches synchronous session/event notifications in the background. session/flush drains pending writes and is the durability/error checkpoint. A JSONL path returned by locate() may not exist yet or may omit the current unflushed turn; it is a location hint, not proof of freshness or authorization.
Resume, replay, and fork are different operations
Resume loads a persisted session into a live agent. A cold log that ended inside a turn keeps every valid event and receives a synthetic turn/end with reason interrupted. It is not silently truncated to the last ordinary turn. A live session with an open turn is not repaired underneath its writer.
Replay re-derives the surface from the stored events. Raw assistant/chunk entries remain durable for fidelity and accounting, while the assembled assistant/message is authoritative for model history. Unknown required event types or an unsupported format are refused instead of skipped.
Fork copies a stable prefix into a new child with parentSession and seedLength. The selected prefix must end outside an open turn; the API rejects an unsafe boundary rather than clipping it. A session/end-seed event marks where the new lifecycle begins.
What compaction changes—and what it preserves
Compaction records compaction/start, produces a summary, appends the bookkeeping record and a replacement user/message, then closes with compaction/end. The replacement cites every shadowed surface node. Session Query can classify events as current, shadowed, or log-only and trace replacement/source relationships.
The raw log therefore grows while the model-visible surface shrinks. That is the useful invariant to test. It also explains three failure boundaries:
- A changed selection or summary failure leaves the surface unchanged but records the failed attempt.
- A commit failure can follow partial in-memory mutation; a persistence failure means the closed bracket did not flush durably.
- A crash after
compaction/startleaves detectable unmatched evidence. A newer lifecycle boundary can classify an inherited unmatched start as stale; it is not proof that concurrent writers are safe.
The basic backend may first prune oversized text tool results, remeasure token pressure, and skip model summarization if pruning is sufficient. Tool-call/result boundaries must remain balanced.
Twelve crash-resume-replay canaries
Run these against disposable sessions and harmless side effects.
| Canary | Expected evidence | Stop condition |
|---|---|---|
| 1. Clean checkpoint | Completed turn plus explicit flush reloads with identical header, seqs, and surface | Latest event is missing or reordered |
| 2. Crash mid-turn | Cold load preserves the valid tail and appends interrupted repair |
Recovery drops a large partial turn |
| 3. Unknown tool outcome | A call without a result becomes unknown and triggers effect verification | Runtime blindly repeats a non-idempotent action |
| 4. Live writer | Open live turn is not cold-repaired | A reader closes another writer's turn |
| 5. Stable fork | Child records parent/seed metadata and diverges only after the boundary | Parent or child mutates the other's log |
| 6. Unsafe fork | Boundary inside an open turn is rejected | API silently clips the requested prefix |
| 7. Successful compaction | Raw event count grows; current surface shrinks; trace reaches shadowed seqs | Original evidence disappears |
| 8. Changed span | Attempt closes with error and surface stays unchanged | Stale summary replaces newer context |
| 9. Crash mid-compaction | Unmatched start remains detectable after reload | A partial transaction is presented as complete |
| 10. Format refusal | Newer/older unsupported format and unknown required events fail closed | Reader guesses or drops required state |
| 11. Spill after fork | Inherited locator behavior is tested with the original artifact present and absent | Fork is assumed to copy external spill data |
| 12. Backend parity | JSONL and SQLite restore identical logical events and surface | Physical packing leaks into replay semantics |
Backup and export checklist
- Record the harness commit, release tag, npm version, persistence backend, format version, and agent preset.
- Quiesce the session and await an explicit flush before taking any external snapshot.
- For JSONL, use the provider's raw-artifact/export path; preserve the header and the complete encoded artifact together.
- For SQLite, use a database-consistent backup while the writer is quiescent; there is no per-session raw artifact.
- Inventory spill locators separately. The session log can preserve a pointer after the target has expired.
- Restore into an isolated root, validate exact event continuity, derive the surface, and run a read-only prompt before enabling tools.
- Keep the original snapshot immutable until the resumed or forked session passes the twelve canaries.
Common mistakes
- Calling the current surface “the session.” It is only a projection of the raw log.
- Copying the path from
locate()before flush and calling it a backup. - Retrying unknown tool outcomes without checking external state.
- Treating compaction as deletion, or treating its summary as full-fidelity evidence.
- Forking from an open turn or assuming a fork copies spill artifacts.
- Running two processes against the same JSONL session. The shipped provider documents one live writer per session.
- Assuming pre-release format migration exists. The current format version is
0and unsupported versions are refused.
FAQ
Does compaction reduce the stored log?
No. At this snapshot it appends transaction and replacement events. The folded model surface becomes smaller, while original events remain in the durable log and can be classified as shadowed.
Is a completed turn automatically durable?
Not solely because turn/end exists in memory. The persistence controller batches writes; use the explicit flush checkpoint before external reads, backup, handoff, or shutdown-sensitive assertions.
Should an interrupted tool call be retried?
Only after classifying the tool as read-only/idempotent or verifying the possible side effect. “No result” does not mean “did not run.”
Sources
- DeepSeek Harness repository and Developer Preview boundary
- dsh-v0.1.0-rc.8 Pre-release
- npm: @deepseek-ai/dsh 0.1.0-rc.7
- Session event log and fork contract at commit 141eb6f
- Persistence, flush, crash repair, format, and backends
- Compaction transaction and failure boundaries
- Session Query surface and relationship tracing
- Spill storage and fork locator boundary
- JSONL durability and recovery contract
- SQLite storage and compatibility contract
- Basic compaction provider behavior