DeepSeek Harness plugin

dsh-session-pruner

Full-type session lifecycle management: one-shot subagents archived on completion, idle continuable subagents / main sessions archived (recoverable), a capacity cap, projection-cache cleanup, and a hot-reload settings panel.

Jump to install

Source facts

Repository
mrzhangkris/dsh-session-pruner
Latest update
Aug 21, 2026
Category
Sessions & Messages
GitHub stars
1

Install

Start with a prompt that asks an agent to read the source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to read the page and repository first.

Do not install anything yet. Read this DeepSeek Harness plugin and explain what it does, which files, networks, or credentials it can access, and how to install and remove it.

Plugin page: https://deepseekplugins.org/plugins/mrzhangkris/dsh-session-pruner
GitHub: https://github.com/mrzhangkris/dsh-session-pruner
Plugin: dsh-session-pruner
Author: mrzhangkris
Install command: dsh plugin --profile web add dsh-session-pruner

Do not run the install command until I confirm.

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

dsh-session-pruner

DSH session lifecycle management plugin — full-type session lifecycle management: one-shot subagents archived on completion, continuable subagents and main sessions archived when idle, a capacity cap, and projection-cache cleanup. Prevents session-library accumulation stalls at the source.

> Every session type has a defined destination: finished one-shot subagents are archived automatically, idle continuable subagents / main sessions are archived, and overflow is recycled by priority. Archive first (recoverable), delete after expiry — the GUI syncs within 30s, fully panel-configured with hot reload.

[简体中文](README.zh-CN.md) · [Apache-2.0](LICENSE) · npm

Why

DSH (DeepSeek Harness) caches a full projection of every session in session_projcache.json (token stats, context pressure, ...), and the storage backend rewrites the whole file atomically on every write. When the session library accumulates thousands of subagent sessions:

  • The cache balloons past 100MB and each checkpoint fully re-serializes → main process CPU 250%+
  • The single-threaded event loop is saturated → every session load stalls, even GET / times out

Managing session lifecycle (this plugin) is the root fix: no session accumulation → no cache rows → no stalls.

Features: full-type lifecycle

Session typeTriggerActionDefault
one-shot subagentlog contains session/end-seed (finished)archive/delete at next scan30min interval
continuable subagentidle over N daysarchive (recoverable)off (0 days)
main sessionidle over N daysarchive (recoverable)off (0 days)
any typetotal exceeds capacity caprecycle by one-shot → continuable → main + oldest400
archive directorykept over N hoursphysically deleted24 hours

Archive mechanism (recoverable)

Cleaned sessions are moved to ~/.dsh/sessions-archive/ first (workspace/session-id structure preserved) — they disappear from the GUI immediately (the list only reads the sessions directory), but the files remain and can be restored manually:

# Restore: mv back into the sessions directory
mv ~/.dsh/sessions-archive/<workspace>/<session-id> ~/.dsh/sessions/<workspace>/

A "delete directly" mode (no archive, irreversible) is also available.

Safety (double protection)

  • Running sessions are never touched: logs without session/end-seed are never cleaned (checked in both the one-shot path and the capacity cap)
  • live protection: sessions still held in the in-memory session store (open/loading) are skipped
  • Main sessions do not participate in capacity recycling by default (configurable)
  • Per-action failure isolation: every action is try/catch wrapped

How it works

scan (scheduled, default 30min)
  ├─ pruneArchive: physically delete expired archive sessions
  ├─ iterate ~/.dsh/sessions/*/ decompress log (system zstd, multi-frame)
  │     ├─ origin: main | subagent       (session header)
  │     ├─ mode: one-shot | continuable  (subagent/descriptor event)
  │     └─ ended: contains session/end-seed
  ├─ one-shot + ended ──→ archive (archiveMode)
  ├─ continuable/main idle N days ──→ archive
  ├─ total > cap ──→ recycle by priority + oldest (skip running/live)
  └─ each archive also: purge projcache row + workspace accounting

GUI sync: the client calls sessions.refreshList() every uiRefreshSeconds seconds, so cleaned sessions disappear from the sidebar automatically — no page reload needed.

Install

From npm (recommended)

dsh plugin --profile web add dsh-session-pruner

From source (development)

dsh plugin --profile web add /path/to/dsh-session-pruner

Restart dsh web after install (launchctl kickstart -k gui/$(id -u)/com.deepseek.dsh-web).

Configuration (settings panel, hot reload)

After install, open Settings → Plugins → 会话生命周期管理 card. All 9 options save with hot reload (no restart):

FieldDefaultDescription
Scan interval (min)30cleanup loop period
Capacity cap (sessions)400recycle by priority + oldest when exceeded
UI refresh interval (s)30GUI session list refresh period
Archive retention (hours)24physical delete after retention
Archive modearchivearchive (recoverable) / delete directly (irreversible)
Continuable idle archive (days)0archive after N idle days, 0 = off
Main idle archive (days)0archive after N idle days, 0 = off
Clean main on overflowoffmain participates in capacity recycling
One-shot min survival (min)3newly finished subagents are not cleaned within N minutes (protects finishing/references)

Env vars (fallback, panel wins): DSH_SESSION_LIFECYCLE_INTERVAL_MS / _MAX / _CLEAN_MAIN / _ARCHIVE_HOURS / _ARCHIVE_MODE / _CONTINUABLE_IDLE_DAYS / _MAIN_IDLE_DAYS / _ONE_SHOT_MIN_AGE_MINUTES.

Logs

Output in guard server-*.out.log:

[session-lifecycle] armed: interval=30min cap=100 cleanMain=false
[session-lifecycle] hot-reloaded: interval=30min cap=100 ... contIdle=1d mainIdle=2d
[session-lifecycle] archived a1b2c3d4 (subagent/one-shot) one-shot done cache=true
[session-lifecycle] archive pruned: 2 expired

cache=true/false tells whether the projection cache row was purged along with the session.

Tests

node test/dry-run.js   # read-only full-library scan, verify classification (no deletion)
node test/e2e.js       # create a fake one-shot session, verify the real cleanup path

Implementation notes

  • Multi-frame zstd: DSH session logs are concatenated zstd frames (append writes); Node zlib decodes a single frame only, so the plugin shells out to the system zstd CLI (brew install zstd on macOS)
  • Cache row purge: storageDomain.get('session_projcache').table('sessions').delete(id) — the official write chain (atomic persistence + in-memory sync)
  • Workspace accounting: the session id is removed from the workspace domain on archive, keeping the data source consistent with disk
  • Zero npm deps: plain Node built-ins + cordis runtime injection
  • Panel + hot reload: installSettingsSection + hand-written client card (__ModuleLoader__ bundle), onChange re-schedules the timer instantly

Developer guide

[docs/DEVELOPMENT-GUIDE.md](docs/DEVELOPMENT-GUIDE.md) — DSH plugin development practice guide (architecture, Host/Client, settings panel, deployment ops, 10 pitfalls with fixes), the foundation for future plugin work.

Known limits

  • A finished one-shot subagent survives at most one scan interval
  • Requires the system zstd CLI
  • The root fix lives upstream: projcache stale-session eviction / incremental storage writes, see deepseek-harness Discussion #1550

License

[Apache-2.0](LICENSE)