メインコンテンツへスキップ
C

dsh-snapcompact

cyberbolt/dsh-snapcompact

DeepSeek Harness plugin that compacts long chats by rendering history into compact bitmap frames a vision model can read, while keeping exact head/tail text and a searchable archive.

インストール

dsh plugin --profile web add github:cyberbolt/dsh-snapcompact

README

Snapcompact archive frame icon

dsh-snapcompact

English · 简体中文

npm downloads license DSH stars

Snapcompact compacts a long conversation by rendering it, not summarizing it. DSH selects a safe history region; Snapcompact serializes the discarded middle into dense PNG frames of pixel-font text that a vision model reads directly, and keeps exact head/tail text plus a sha256-addressed archive behind the checkpoint. Rendering is local and deterministic: the visual path makes no summarizer call and needs no API key or network, and snapcompact_recall can still recover exact commands, paths and identifiers from the archive. If the route cannot take images, it falls back through the configured text methods and finally to DSH's native summary. It ships as opt-in presets for DSH's standard, PTC and Cordis modes; the official presets and running sessions are unchanged.

Where it pays off. Against raw history it spends fewer prompt tokens for the same recall. Against a native summary it keeps far more of the original and costs no generation call, but its checkpoint is larger on every later turn; on a short session a terse summary is cheaper. This is measurable, not a slogan: run /snapcompact benchmark on your route instead of assuming a fixed ratio.

Highlights

What you needWhat Snapcompact provides
A smaller context window without throwing the transcript awayEvery checkpoint ships with an exact archive of the selected history.
Recover exact strings after compactionExact head/tail text, an archive reference and the snapcompact_recall tool.
Know which files are in play after compactionA bounded FILES index from read/write/edit calls, with directory folding, (Read)/(Write)/(RW) markers and archive recall for exact content.
A safe failure modeDefault chain snapcompact → handoff → shake → soft, configurable as a subset/order; a candidate that cannot prove a smaller, readable checkpoint is skipped.
Native DSH behaviorCompaction region selection, transaction locks, recovery and capacity checks stay in the host.
Change the common knobs without hand-editing YAMLThe Plugins → Snapcompact settings card edits the chain, threshold, frame budget and limits, and saves through the profile's config patch.
Evidence instead of a slogan/snapcompact benchmark reports recall, one-time generation cost and per-turn checkpoint size for the text, summary and visual arms.

How it works

DSH selects a safe history region
        │
        ▼
exact archive (sha256:…)  ──┐
        │                   │  survives the checkpoint
        ▼                   │
snapcompact → handoff → shake → soft
        │
        ▼
native compaction transaction commits one checkpoint
OrderMethodModel callWhat it produces
1snapcompactNone (local)Bounded 1568px bitmap frames of the compact middle, exact head/tail text, a bounded FILES index and an archive reference.
2handoffOne handoff callA structured task handoff: goal, constraints, decisions, exact commands, completed and unfinished work, next action, critical facts.
3shakeNoneLarge tool outputs are archived, then replaced with bounded head/tail excerpts and recall references.
4softNative summaryDSH's native text summarization as the final fallback.

Why frames: vision models bill an image by area, largely independent of how many characters are packed into it. Up to the reader's legibility limit, a dense frame can carry more text per token than the same text sent verbatim; the ratio depends on frame geometry, the reader's image pricing and how dense the transcript is.

The visual method is attempted only when the current route declares image input and supplies image pricing (or a labelled estimate). It renders up to rendering.maxFrames (20 by default, configurable to 64) and never past the host's per-message image cap, selecting the frame count dynamically so the checkpoint fits the room left under the trigger. It is skipped when no image slot remains, when every candidate frame would be downscaled below the readable glyph floor, or when the budget leaves no room for a frame. Existing images are carried into the checkpoint (up to four) instead of forcing a text fallback.

Does it save tokens?

The visual path pays no generation cost: the compaction itself makes no model call. On the bundled synthetic fixture it turned 14,357 prompt tokens of raw history into an 8,152-token checkpoint and matched the original's 8/9 exact answers. A native summary read only 523 tokens per turn, but generating it cost a one-time 16,052 tokens and it scored 7/9. Counting that generation, the visual checkpoint is cheaper for the first compaction; the summary becomes cheaper in raw token count after roughly 2–3 turns. The method must also fit the room left under the trigger — at most half of what a checkpoint cannot replace, capped by half of the source — so a history too small for that budget falls back to text. Cache-read discounts and image pricing decide the actual bill, so treat this as a measurement, not a promise.

A checkpoint looks roughly like this (abridged):

Snapcompact v2; method=snapcompact
Exact archived history: sha256:…
Visual snapcompact checkpoint: N frames; M carried images; use snapcompact_recall for exact strings.
FILES (touched by read/write/edit tools; not a current disk-state guarantee)
(Read) read-only; (Write) written without a read; (RW) read and written
============================================================
# src/compaction/
visual-source.ts (Read)
## methods/
snapcompact.ts (RW)
[exact text head]
[image frame 1 of N] … [image frame N of N]
[exact text tail]
[file] snapcompact archive reference

Safety properties:

  • Images are never the only copy of task state; exact head/tail text and the archive remain. A text handoff is the fallback when visuals are unavailable.
  • The FILES index is derived from read/write/edit tool calls in the compacted range and earlier snapcompact checkpoints. It is a navigation aid, not a disk snapshot; bash and apply-patch changes are not tracked.
  • Cancellation stops the compaction and never silently selects the next method.
  • Recall is granted only by a successfully completed compaction in the owning session.
  • Existing images and earlier visual checkpoints are carried forward, not re-rendered as attachment descriptors.
  • Unknown vision support fails closed to text; unknown pricing is estimated only for DeepSeek model IDs and is labelled as an estimate.
  • The plugin adds no custom Session event type and does not replace the host's transaction engine.

Install

Snapcompact declares DeepSeek Harness ^0.2.2-rc.1, the 0.2.x line, and is verified on 0.2.2-rc.1. A 0.2.x patch or prerelease installs without an exact-version exemption; the next minor line is declared only after the install and activation checks have passed against it.

RequirementValue
DSH^0.2.2-rc.1 (0.2.x; verified on 0.2.2-rc.1)
Node.js24 or newer (the host's requirement)
Build stepNone; the npm package ships compiled dist/
Install-time build scriptsNone
  1. Open Plugins → Add plugin in DSH.
  2. Paste dsh-snapcompact@0.5.2 and click Install.
  3. Follow the page's restart prompt if one appears.
  4. Start a new session and pick the Snapcompact variant that matches how you work: Snapcompact (Standard), Snapcompact (PTC) or Snapcompact (Cordis). From then on, /compact and automatic compaction use Snapcompact; no extra command is required.

Pin the version to install exactly this release. Without a version, the package manager resolves the newest published release.

From the command line

dsh plugin --profile web add dsh-snapcompact@0.5.2

Replace web with the profile you actually use, then follow the restart instruction. On DSH Desktop, install through the in-app Plugins page: the CLI writes to the named profile, which may not be the profile the desktop app uses.

Other install sources

dsh plugin add accepts more than an npm package name. You can install a locally packed tarball or a tarball URL:

pnpm install --ignore-scripts
pnpm pack
dsh plugin --profile web add "file:$PWD/dsh-snapcompact-0.5.2.tgz"
dsh plugin --profile web add https://example.com/dsh-snapcompact-0.5.2.tgz

The npm package is the prebuilt option; a local checkout needs pnpm pack (or pnpm run build) first.

Enable a preset

Installation adds opt-in presets; it does not change the official presets or any running session. Create a new session, choose one of the Snapcompact variants, and continue there.

Each variant copies the official preset it mirrors and swaps only the compaction backend: compaction-basic out, the Snapcompact chain in. The destructive result pruner stays out because shake archives before externalizing. Choosing a variant is the enable action. DSH mounts each agent's compaction backend inside the preset's isolated group, so a profile-plane plugin cannot replace the native backend globally, and a shipped preset cannot be extended in place. This is a DSH isolation boundary, not an extra per-turn mode.

Official modeChooseWhat you keep
standardSnapcompact (Standard)The regular tool set: shell, file tools, search, subagents and workflows, web, plan mode.
ptcSnapcompact (PTC)run_code as the only direct tool; every other tool stays reachable through tools.*, including snapcompact_recall. Workflows and the plugin manager keep the official PTC disablement.
cordisSnapcompact (Cordis)tool-cordis plus the official Cordis authoring skills mounted from the shipped @deepseek-ai/dsh-agent-preset skills directory.
minimalnot providedMinimal deliberately omits read/write/edit and the full tool surface; adding image frames and archive recall would defeat its purpose, so it is out of scope.

The settings card is shared: it edits the profile-plane snapcompact-settings row, and whichever variant a session selects reads it at mount time.

Use it

Entry pointWhat it does
/compactDSH's standard manual compaction command; inside any Snapcompact preset it runs the Snapcompact chain.
/snapcompact statusShow the method-chain preset, automatic-compaction setting, current route, effective threshold and compactable space, per-method availability and the archive count.
/snapcompact benchmarkRun the synthetic comparison. This explicitly authorizes up to four auxiliary model requests and can incur fees.
snapcompact_recallLet the model list, page through or literally search exact archived content.

Recall examples:

{ "action": "list" }
{ "action": "read", "archiveId": "sha256:…", "offset": 0, "limit": 100 }
{ "action": "search", "archiveId": "sha256:…", "query": "pnpm deploy --filter" }

Recall returns physical line numbers and (nextOffset, nextColumn) cursors. Pass both back to continue a very long line. Text pages are capped at 200 line fragments and 20,000 UTF-16 code units. Search is a case-sensitive literal substring match, not a regular expression.

Archive IDs must be referenced by a completed compaction in the owning session (including inherited history). Arbitrary paths and other sessions' archives are not accessible.

Configuration

Configuration lives on the snapcompact row inside the selected Snapcompact preset's isolated compaction group. The settings card on the Plugins → Snapcompact page covers the common keys; the preset/config editor covers everything else. Both write into the active profile's configuration patch, so neither requires editing profile files by hand.

methodOrder: [snapcompact, handoff, shake, soft]
policy:
  auto: true
  # Trigger at 80% of the window; /snapcompact status shows the effective token count.
  thresholdRatio: 0.8
  # Verbatim tail kept after compaction. Mutually exclusive with retainTokens.
  retainRatio: 0.16
guard:
  # Refuse to mount when thresholdRatio - retainRatio falls below this; 0 disables.
  minCompactableRatio: 0.1
  # Skip a compaction whose source is below max(1024, window x this) tokens; 0 disables.
  minSourceRatio: 0.01
rendering:
  width: 1568
  height: 1568
  maxFrames: 20
  lineHeight: 20
  minGlyphPx: 12
serialization:
  toolResultMaxChars: 2000
  toolArgumentMaxChars: 500
  messageTextMaxChars: 8000
  headChars: 2000
  tailChars: 2000
  includeReasoning: false
  maxCarriedImages: 4

Defaults use 1568×1568 frames and 20 frames per checkpoint, with a configurable maximum of 64; the host's per-message image cap (20 by default) still clamps the effective count. Other limits are 16 MiB of generated images, 64 MiB of source/archive data and a 15-second render deadline. The frame count is chosen dynamically under the checkpoint budget implied by the trigger (see the configuration reference).

In-app settings

The Snapcompact detail page under Plugins carries a settings card backed by a snapcompact-settings row in the current profile. The card writes through DSH's settings service into that profile's configuration patch, so saved values survive restarts without hand-editing YAML. It manages:

ControlConfig pathDefault
Method chainmethodOrder[snapcompact, handoff, shake, soft]
Custom trigger thresholdpolicy.thresholdRatiounset (host default 0.8)
Retained tailpolicy.retainRatio or retainTokensunset (host default 0.16)
Frame sizerendering.width and rendering.height1568
Maximum framesrendering.maxFrames20 (max 64)
Archive limitarchiveMaxBytes64 MiB
Handoff budgethandoffMaxTokens4096
Externalize aboveshakeThresholdChars8192

Rules worth knowing:

  • The engine is built when a session mounts the preset, so saved changes apply to new sessions; a running session keeps its configuration.
  • For a key the card manages, the saved value overrides the same key in preset YAML. thresholdRatio is the exception: it stays unset until you enable the custom threshold, so a preset-level policy.thresholdRatio still applies.
  • The retained tail has one three-state control: inherit the native 0.16, set a ratio, or set an absolute token budget. retainRatio and retainTokens are mutually exclusive; saving one removes the other, and hand-written YAML that sets both is rejected.
  • guard is YAML-only. Both guards default to on, and both accept 0 to disable them.
  • "Restore default" removes the override and falls back to the plugin default (or to YAML, for the threshold).
  • Everything else — the rest of policy, serialization, routePolicies, render timeouts — remains YAML-only.

Retained tail and usefulness guards

The trigger only matters relative to the retained tail:

compactable = threshold - retained tail

At a 1M-token window the native 0.16 tail is about 160K tokens, so thresholdRatio: 0.2 leaves only about 4% of the window — roughly 41K tokens — to pay for a checkpoint. That is the classic "20% never becomes 19%" trap: the estimated savings are smaller than the rounding step.

The plugin now fails fast instead of mounting that pair: when thresholdRatio - retainRatio is below guard.minCompactableRatio (default 0.1), the engine refuses to start and the error names the values and the fix. /snapcompact status prints the same arithmetic — retained tail, compactable space, the floor, and a warning when the space is below it. A source history below max(1024, window × guard.minSourceRatio) (default 1% of the window) is skipped before any archive or model call.

Existing profiles that combine thresholdRatio: 0.2 with the default retained tail will be rejected after upgrading. Fix it in one of three ways:

  • raise the trigger (the card floor is 0.5; the host default is 0.8), or
  • lower the retained tail (retainRatio: 0.03 leaves about 30K at a 1M window), or
  • set guard.minCompactableRatio: 0 to disable the mount check (not recommended).

Custom method chains and thresholds

methodOrder is a priority list: the engine tries methods in order and the first candidate that succeeds within its compression constraint wins, so the order is the fallback order. The final method must be soft. Subsets are allowed:

PresetmethodOrderBehavior
Visual-first (default)[snapcompact, handoff, shake, soft]Local visuals first, then handoff, excerpts, native summary
Visual + native[snapcompact, soft]Skip handoff/shake; native summary when visuals fail
Local-first[snapcompact, shake, soft]Call the native summarizer only after both local paths fail
Text-first[handoff, shake, soft]Spend one model call on a structured handoff first
Native-only[soft]Back to the DSH native summary; archive and recall remain

Two easy misreadings: shake succeeds whenever any tool output exceeds shakeThresholdChars, so putting it first preempts the visual path; and the checkpoint budget (half of the room left under the trigger) only constrains snapcompact, while every other method merely has to shrink the source.

The trigger is still the native policy.thresholdRatio (a window fraction, default 0.8). /snapcompact status converts it into tokens for the current route, prints the retained tail and the compactable space between them, and explains whether each method will run or be skipped.

If a model genuinely has image input and a known price but DSH's adapter exposes no image pricing, DeepSeek model IDs use a labelled 1024-token/frame conservative estimate. Other models can declare a conservative ceiling explicitly:

routePolicies:
  - provider: your-provider
    model: exact-model-id
    visualTokensPerFrame: 4096 # Example only; calibrate for the model and frame size.

A ceiling is not actual usage and cannot grant missing image modality. See configuration for every field, the native policy pass-through and route-matching rules.

Compatibility

ComponentSupported
DSH^0.2.2-rc.1 (0.2.x)
Node.js24+
DSH packagesHost-provided peers; no local copy is needed
npm installPrebuilt package, no install scripts

The plugin declares a bounded range rather than a single version. The host's gate is a semver check; the floor is the newest host release this build was verified against, and the ceiling is the next minor line. A compatible patch installs without a new plugin release, while a newer minor stays rejected until it passes the same revalidation — types, native integration, package contents and live preset activation — and ships in a new plugin release. Do not bypass the host check by editing profile metadata.

Limits and privacy

  • Not lossless. Frames can omit characters (missing glyphs, a truncated middle), and the text fallbacks are summaries. The archive preserves the selected message content, but it cannot recover content another plugin already deleted. Only up to four existing images are carried into the next checkpoint; older image pixels remain outside the visual summary (their archive descriptors are retained).
  • Savings depend on the route and the horizon. The numbers above come from one synthetic fixture on one route. A terse native summary can be smaller per turn on a short session; the visual checkpoint can win on a long history or a route with cheap image tokens. Cache-read discounts, image pricing and turn count decide the bill: a smaller checkpoint is not proof of a lower bill.
  • Visual eligibility is narrow. The route must declare image input and pricing or a labelled estimate. Unrenderable characters become visible ? placeholders, downscaled frames are rejected as unreadable, and insufficient budget or storage failures fall back to text. The visual middle may be truncated; the exact archive remains the source of truth.
  • FILES is a tool-observed index, not a snapshot. It covers read/write/edit calls in the compacted range plus earlier snapcompact FILES blocks, is capped at 20 sorted paths, and does not see bash or apply-patch changes. Paths elided from an earlier checkpoint are not re-indexed; the archive and recall remain exact.
  • Host metering caveat. On a route whose adapter does not price images, DSH's own context meter may underprice the checkpoint until provider usage anchors the next request; the plugin uses its own conservative frame price for the visual gate.
  • Benchmarks cost money. /snapcompact benchmark makes up to four auxiliary model calls using the bundled synthetic fixture. It does not read or upload private session history.
  • Archive access is scoped. Recall accepts only archive IDs referenced by a completed compaction in the owning session; there is no path or cross-session escape hatch.
  • No telemetry. Archives are stored through DSH's attachment storage in the profile. The plugin does not upload conversation data anywhere else.

Troubleshooting

I installed it, but the Snapcompact presets are not in the picker

Confirm the plugin appears as installed, restart DSH if the Plugins page asks for it, and create a new session. Look for Snapcompact (Standard), Snapcompact (PTC) and Snapcompact (Cordis). Existing sessions keep the preset they started with. On DSH Desktop, make sure you installed through the app's Plugins page rather than a CLI profile the desktop app does not use.

It always falls back to text and never renders images

Run /snapcompact status. The usual cause is that the current route has no declared image input or no usable image price estimate. This is the intended safe behavior; do not add a routePolicies ceiling unless you know the model accepts images and have calibrated the value. Existing images are carried forward, up to four; if there is no image slot left, the text handoff is used.

Compaction fails with "No compaction method produced a safe smaller checkpoint"

Every candidate was skipped or failed its budget check. Check /snapcompact status first, then retry when the history is larger or choose a route with a working text summarizer. The error lists each method's reason; cancellation is reported separately and does not fall through.

It refuses to start with "leaves only … of the window compactable"

The configured threshold and retained tail leave too little room for a checkpoint to pay for itself, so the plugin refuses to mount instead of burning model calls. Run /snapcompact status for the exact token numbers, then raise policy.thresholdRatio, lower policy.retainRatio/retainTokens, or set guard.minCompactableRatio: 0 to disable the mount check.

Compaction fails with "below the …-token floor"

The selected source history is smaller than max(1024, window × guard.minSourceRatio). Wait for more history, or lower guard.minSourceRatio (set it to 0 to disable the earnings check). The guard runs before the archive and before any method, so nothing was spent.

Recall says the archive is not referenced by this session

Use the archive ID from a checkpoint created by a completed compaction in the current session (inherited history counts). IDs copied from another session, another profile, or a failed attempt are intentionally rejected.

The install is rejected as incompatible

This build declares DSH ^0.2.2-rc.1 and is verified on 0.2.2-rc.1. Upgrade or switch the host to a 0.2.x release, or install a Snapcompact release that declares your host's minor line. Do not hand-edit the compatibility check to force the install.

Documentation

Development

pnpm install --ignore-scripts
pnpm run check          # types, lint, formatting, build, preset, tests, package resources
pnpm run check:install  # install the packed tarball into a throwaway DSH profile
pnpm run release:plan   # preview whether the current version would publish
pnpm pack

check:install never touches your real profiles and keeps its temporary home under .artifacts/ for debugging with --keep. DSH packages are host-provided peers; the development install supplies local types and integration-test modules. The package needs no install scripts.

Source is organized by compaction, archives, rendering, budgeting, DSH integration and evaluation, with strict TypeScript checking. Start with the architecture and reading guide. Comments and primary documentation are English; Chinese documentation is maintained alongside them.

License

Original application code is MIT. Bundled GNU Unifont 18.0.01 data retains the SIL Open Font License 1.1; see third-party notices.

関連プラグイン