fast-compaction-dsh
September 20, 2026 · View on GitHub
English | 中文
Verdict-based context compaction for DeepSeek Harness (DSH). Replaces the lossy compaction summary with fast per-call keep/truncate/drop decisions from jev-latest — everything kept stays verbatim, nothing is ever rewritten.
A DSH port of tamaratran/fast-jev-compaction (MIT), adapted to the DSH compaction capability seam.
What and why
Most context compaction asks an LLM to summarize old turns. A summary is lossy: a file path, an exact error, a constraint, or a command can disappear even when it matters later. This plugin never rewrites anything. When compaction triggers, every tool call and tool result in the compacted region is scored by jev-latest through the TypeSafe System One endpoint; stale calls are deleted, half-stale results are truncated to a bounded head, and everything else stays byte-for-byte. User and assistant text is never removed.
On any failure — a missing TYPESAFE_API_KEY, a Jev error, malformed answers, an unfittable history, or an insufficient reduction — it falls back to the shipped compaction-basic summarizer, exactly like the original Claude Code hook falls back to Claude Code's built-in summary.
How it works
- Every
tool-callis paired with itstool-resultby call id. Calls in the first message and the newestpreserveRecentMessages(default 6) messages are pinned and never touched. - The state sent to Jev is the whole compacted region, oldest first, with every tool result replaced by a short note (
ok, 4213 chars (omitted)). Tool inputs and texts are included — nothing is summarized. - The state is fitted into
maxStateTokens(25k default) in stages: tool inputs truncated 1000 → 200 → 60 chars; long texts abridged to head + tail, oldest non-pinned first; old messages collapsed to a note; old calls reduced to one line each; old call-less messages left out; runs of call-only messages folded. If it still does not fit, the plugin falls back. - For every non-pinned call Jev gets two
noulquestions: should the call stay (knowing it was made, with its input, still matters), and should the result stay verbatim (its contents are still needed and re-running the tool would not do). - Questions are batched so state plus questions stays under
maxRequestTokens(30k default); batches run concurrently and answers are merged. - Decisions against
keepThreshold(default 0.5):keepResult≥ threshold → keep both verbatim; elsekeepCall≥ threshold → keep the call, truncate the result to its firsttruncateHeadChars(default 300) characters plus a note; else → drop the call together with its result. - The region is rebuilt as a verbatim transcript checkpoint (the DSH seam replaces the shadowed surface span with one durable user message), with role labels and the kept tool activity inline.
Measured on a real region: the Jev scoring round-trip takes ~700 ms.
How it plugs into DSH
DSH exposes compaction as a capability seam: ctx.compaction (service definition), an engine provider (compaction-basic), and the /compact consumer. The engine's single documented customization hook is summarize() — this plugin subclasses BasicCompactionEngine and overrides only that hook, so region selection, the durable transaction (lock, events, stability checks, shrink validation), checkpoint framing, and /compact keep working unchanged.
The shipped standard agent preset mounts compaction-basic inside an isolated compaction realm, so the engine row must be mounted from an agent preset patch — not from a profile-level cordis.patch.yml:
# ~/.dsh/.agent-presets/fast/agent.cordis.yml
- id: standard
name: cordis:include
config:
path: 'file:///path/to/deepseek-harness/packages/preset/agent-presets/presets/standard/agent.cordis.yml'
patches:
- id: compaction-basic
disabled: true
- id: compaction
insert:
- id: fast-compaction
name: 'file:///path/to/fast-compaction-dsh/src/index.ts'
# config: { keepThreshold: 0.5, ... } # all optional
Then point your default preset at it in ~/.dsh/cordis.patch.yml:
- id: agent-presets
config:
default: fast
Restart DSH. New sessions compact through jev-latest; watch the log for fast-compaction-dsh: kept N/M calls verbatim (…).
The same package also ships the Web settings card for that engine (the fast-compaction-dsh/web and fast-compaction-dsh/client entries under web/). To get the card, add the package to the web profile and enable the bundle, then restart:
// ~/.dsh/profiles/web/package.json
"dependencies": { "fast-compaction-dsh": "link:/path/to/fast-compaction-dsh" },
"dsh": { "profile": { "bundles": [ ..., "fast-compaction-dsh" ] } }
The bundle patch only mounts the settings-namespace entry in the web profile; the engine keeps being mounted by the agent preset above, and the web profile never loads src/index.ts.
Seeing the effect
No command line needed — the GUI has two layers, plus two optional channels:
-
Chat marker: after a compaction, the chat shows an expandable marker (
N items · M tokens); expanding it shows the verbatim transcript the model now sees, with[fast-compaction-dsh truncated …]notes where results were cut. -
Trajectory tab (the verdict details live here): switch the session view to the Trajectory tab → find the
Compactiongroup → click the compacted cell. The inspector has two tabs:- Summary: the rebuilt transcript (the context the model now sees), rendered as Markdown;
- Raw Output: block 1 is a readable verdict report this plugin emits (stats header + an aligned table of each tool call's
keepCall/keepResultprobabilities and final action), block 2 is the raw{decisions, stats, stateStage}JSON.
-
Inspector script (offline, read-only — handy for a cross-session overview):
pnpm run inspect:compaction # sessions for the current directory pnpm run inspect:compaction -- --all # every workspaceRenders each compaction's stats and per-call verdict table (parsed from the JSON block of the
compaction/summaryevent'srawOutput).--jsonfor machine-readable output. -
Service log:
journalctl -u deepseek-harness.service | grep fast-compaction— one summary line per verdict pass, plus fallback warnings.
No compaction yet? Run /compact in a session on the fast preset, or lower thresholdRatio in the preset config.
Requirements
- Node.js ≥ 22.19 (the plugin is TypeScript loaded directly by DSH's loader via type stripping).
- A DeepSeek Harness checkout (developer preview) — for development and for the
link:dev dependencies, clone it as a sibling directory of this repo (or adjust thelink:paths inpackage.json). - A TypeSafe API key:
export TYPESAFE_API_KEY=...in the DSH process environment (orapiKeyin the preset row'sconfig). Without a key the plugin behaves exactly likecompaction-basic.
Configuration
All fields are optional and stack in two layers:
- The settings.yaml user layer (the
fast-compaction:section of~/.dsh/settings.yaml) — wins per field and applies live to subsequent compactions: the engine watches the settings service's hot-publish and rebuilds the Jev transport in place whenapiKey/model/baseUrlchange, so no restart is needed. The friendly editor is this package's own Web settings card (web/, thefast-compaction-dsh/cliententry) on the Plugins page (apiKeyrides as asecretrole field — only a set/unset flag ever crosses the wire). - The preset patch
config:(composition layer) — the per-preset base values; changes require a DSH restart.
Per-field precedence: settings.yaml user layer > preset config: > environment (apiKey only, via TYPESAFE_API_KEY) > code defaults. Unlisted fields pass through to compaction-basic (thresholdRatio, retainRatio, retainTokens, summarizationProvider, summarizationModel, maxTokens, compactionRetries, maxOverflowRetries, modelPolicies, auto).
Note: a settings section whose values fail the schema (e.g. a string in keepThreshold) disables the whole settings layer and falls back to the composition layer, with a warning in the log; the layer retries on the next process start.
| Field | Default | Meaning |
|---|---|---|
apiKey | TYPESAFE_API_KEY | TypeSafe API key |
model | jev-latest | Jev model name |
baseUrl | https://api.typesafe.ai/v1/systemone | System One endpoint |
keepThreshold | 0.5 | Minimum keep probability for a call or result to stay |
preserveRecentMessages | 6 | Newest messages never touched (the first is always kept) |
maxStateTokens | 25000 | Estimated token ceiling for the state |
maxRequestTokens | 30000 | Estimated ceiling for state plus one batch of questions |
truncateHeadChars | 300 | Characters of a dropped tool result retained before its note |
minReduction | 0.25 | Fall back to the built-in summary below this reduction ratio |
disabled | false | Use only the built-in summarizer |
Differences from fast-jev-compaction
- The original rewrites Claude Code's message list in place. The DSH seam replaces the shadowed surface span with one user message, so the kept content is rebuilt as a verbatim transcript with role labels (
<user>/<assistant>/<tool-call>/<result>). Text is still byte-for-byte. - Assistant reasoning blocks are excluded from the transcript (intermediate thinking is not useful for resuming).
- Pinning is system-head aware: when
messages[0]is the system message, the first conversational message is pinned with it. - The Jev call does not go through DSH's
ctx.llmseam; the summary result is recorded as a remote (unmarked) variant withprovider: 'typesafe'and the Jev token usage.
Development
pnpm install # link: deps expect ../deepseek-harness to exist
pnpm run typecheck
pnpm test # 42 unit/integration tests, no network
TYPESAFE_API_KEY=... node scripts/e2e.ts # live verdict-path check against the real endpoint
Layout: src/jev.ts (System One transport + noul protocol), src/state.ts (fitted state), src/pairing.ts (call/result pairing), src/rebuild.ts (decisions + verbatim transcript), src/index.ts (the engine).
License
MIT — see LICENSE. Ported from tamaratran/fast-jev-compaction (MIT); the noul question wording, state-fitting stages, and decision thresholds are carried over verbatim.