dsh-context-reclaim
Tool results are reclaimed before they enter history, so the prompt cache never goes cold.
⚠️ DSH 1.5 only (
>=0.1.5-rc.2 <0.2.0). Newer DSH releases will be supported in follow-up updates.
English · 简体中文
What this is
A DSH session's context is not blown up by the conversation; it is blown up by tool results: a single read of several hundred lines, a single pwsh dumping a wall of logs, a system prompt repeated verbatim every turn. Once this content has entered the history, the only thing left is for the compactor to operate on it after the fact — and compaction has to rewrite already-committed history, which knocks out the whole server-side prefix cache and makes every compaction more expensive than the last.
dsh-context-reclaim moves to a different position: it handles them before the tool result is appended to history, and it never rewrites content that has already been committed. So the prefix cache is never invalidated because of this plugin.
It hooks into 5 seams:
| Seam | What it does | Default |
|---|---|---|
tools/post-execute |
Purify a tool result the moment it is produced + bound it by content type, inserting a retrieval pointer at the truncation point | on |
system-prompt/assemble |
Fold the tool definition surface, stabilize the order of prefix sections, truncate over-long description lines | on |
tools/pre-execute |
Duplicate-edit interception (a second identical edit to the same content of the same file is rejected outright) | on |
session/event |
Compaction-summary integrity audit (warns when a compaction summary is missing sections) | on |
llm/stream |
One cost-attribution line per request, printing route / prompt / cache hit / output | on |
1. Tool-result purification and bounding
This is the core. Every time a tool call returns, the plugin first performs lossless purification, then bounding.
Lossless purification
Collapses ANSI escape sequences, strips trailing whitespace, normalizes CR. This step is pure purification and loses no information.
It only acts when the content meets both thresholds: length ≥ 32 characters, and a reduction ratio after purification ≥ 0.2%. Small results are not touched at all.
Turn it off: config.noise = false, or the environment variable DSH_RECLAIM_NOISE=0.
Bounding by content type
Different types of tool results have completely different tolerances. The plugin sorts tools into three classes:
| Class | Tools covered | Head / tail kept | Per-line cap | Total character cap |
|---|---|---|---|---|
| Read-only | read grep glob codegraph_* lsp_* |
80 lines / 12 lines | 512 | 160 |
| Side-effecting | pwsh bash docker_* job_output |
40 lines / 40 lines | 1024 | 256 |
| Faithful | web_fetch web_search knowledge_* read_image pilot_snapshot |
left exactly as-is | — | — |
Tools it does not recognize are treated as faithful — better to save nothing than to cause unintended damage.
Wherever content was truncated, a retrieval pointer is inserted telling you how much of the original is left and how it can be retrieved, rather than dropping it silently.
The trigger threshold for bounding is "saves more than 5%"; if it does not save much, nothing is touched.
Turn it off: config.bounded = false, or the environment variable DSH_RECLAIM_BOUNDED=0.
About oversized results: content above
config.maxInlineBytes(default 50000) is handed to the spill mechanism to be written to disk. This requires the spill plugin to be installed in your profile — without it, this threshold has no effect and over-limit content passes through unchanged.
The faithful allowlist
The lists for all three tool classes can be replaced wholesale in the configuration:
- id: reclaim
config:
readOnlyTools: ['read', 'grep', 'glob'] # a full-table replacement, not an append
sideEffectTools: ['pwsh', 'bash']
faithfulTools: ['web_fetch', 'web_search']
Note that this is a full-table replacement, not a per-field merge — writing only headLines will lose the character caps. Either write the whole table or write none of it.
2. Tool definition surface folding
At the start of every session, DSH stuffs the complete definitions (name, description, parameter schema) of every available tool into the system prompt. With dozens of plugins installed, this block can account for a large proportion of it, while most of those tools will not be used at all in the current turn.
By default the plugin folds this part: it keeps the full definitions of the most frequently used tools and compresses the rest into a one-line list (name + one-line description, no parameter schema).
A folded tool is still callable. The one-line list only removes the schema from the prompt — the tool itself stays registered with the host, so the model can still find it with search_tools, call it straight away with invoke_tool, or make it resident again with load_plugin. There is no load_tools tool; search_tools + invoke_tool is the pair. If you would rather keep every schema in the prompt, turn the folding off (next line).
Turn it off: config.foldTools = false, or the environment variable DSH_RECLAIM_TOOL_FOLD=0.
3. Prefix stabilization
A server-side prefix cache only hits when the prefix is byte-for-byte unchanged. Yet the system prompt inherently carries a pile of things that change every turn: token counts, timestamps, session sequence numbers.
By default the plugin flattens these volatile numbers and reorders the sections so that stable sections come first and volatile ones last. Each reorder prints one [reclaim/section-order] line in the log, telling you which sections were moved.
4. Truncating over-long description lines
The system prompt occasionally contains a single exceptionally long description line (long comments written by some plugins). Lines above the threshold (not exposed in config; the environment variable DSH_RECLAIM_PROMPT_LINE_MAX, default 120 characters) are truncated, and the line numbers that were truncated are printed to the log.
5. Duplicate-edit interception
If the model issues exactly the same modification to the same content of the same file twice in a row, the second one is intercepted and an explanation is returned — such a call is almost certainly a meaningless duplicate that burns a whole round trip for nothing.
Log line: [reclaim/dedup-deny].
Turn it off: the environment variable DSH_RECLAIM_DEDUP_DENY=0.
6. Compaction-summary integrity audit
If the summary produced by the compactor is truncated by the output budget, it silently becomes shorter — you receive no notice at all, the next turn simply starts with a piece of the context missing.
After every compaction the plugin audits whether the summary contains the structural sections it should have, and warns when any are missing:
[reclaim/summary-audit] ⚠️ 摘要缺 3/8 段
It only warns, it does not modify the summary. What actually prevents summaries from being truncated is dsh-aux-degrade; see the interaction section below.
7. Cost attribution
One line is printed at the end of every request:
[reclaim/usage] route=aux prompt=12043 hit=11890 miss=153 out=204 purpose=compaction
hit / miss are the prefix-cache hit and miss character counts, and purpose is the purpose of this call (if it has one). When the cache is broken, an extra [reclaim/cache-break] line is printed.
This log is the fastest entry point for diagnosing "why is my cache hit rate not high?".
8. Writable-request takeover — no dsh-llm patch required
This is the reason you can install the plugin and stop patching files.
On a stock @deepseek-ai/dsh@0.1.5-rc.2 install the main-path conversation request is deep-frozen by the host before it reaches @deepseek-ai/dsh-llm. Its entry point is:
stream(options) { return this.streamWithRegistration(options) }
streamWithRegistration(options, prepared) {
return this.ctx.waterfall(this, "llm/stream", options, () => this.adapterStream(options, prepared))
}
The waterfall's next is a closure that ignores its argument and the adapter reads the outer options. So a plugin listening on llm/stream can read the request but cannot rewrite it — writing to a frozen object throws, and passing a new object to next(...) does nothing at all. That is why the desktop build of DSH has always shipped with a local file patch to dsh-llm/lib/index.js whose entire body is six lines: copy the frozen request shallowly, copy the messages array, re-mark it with markAgentLoopRequest, and hand the copy to the waterfall.
Version 1.1.0 does the same thing from the plugin layer. On load, this plugin wraps ctx.llm.streamWithRegistration once:
- request is frozen → shallow copy +
messagesarray copy + every message object copied, then re-marked throughmarkAgentLoopRequesttaken from the host's owndsh-llmmodule, and the copy entersllm/stream; - request is not frozen (host already patched) → passed through untouched, byte for byte. Zero behaviour change;
- anything unexpected → logged and passed through untouched. This plugin never turns a working session into a broken one.
Because of this, an unpatched install gets a writable request without a single file being touched. Measured A/B on a stock runtime that carries no patches at all (dsh --profile headless, real request, [reclaim/wire] line):
# takeover on (default)
[reclaim/wire] 入口探测:messages=5 条 · options 冻结=false · messages 冻结=false · route=deepseek-official|deepseek-flash|main|ba8a6419
# takeover off (DSH_RECLAIM_WRITABLE_REQUEST=0)
[reclaim/wire] 入口探测:messages=5 条 · options 冻结=true · messages 冻结=true · route=deepseek-official|deepseek-flash|main|d7d22e75
Turn it off with DSH_RECLAIM_WRITABLE_REQUEST=0. The startup log tells you exactly what happened:
[reclaim/writable] 已接管 llm.streamWithRegistration:冻结的主请求先浅拷贝再进 llm/stream ⇒ 不再需要给官方 dsh-llm 打文件补丁(可用 dsh-llm 副本 2 份)
How the host's own module is found
markAgentLoopRequest writes into a WeakSet that lives inside one module instance, and Node creates a separate instance for every distinct module URL. Marking a copy with a foreign instance is useless: the object simply is not in the set your other plugins check. So this plugin
- derives candidate locations from
process.argv(the real…/@deepseek-ai/dsh/lib/bin.js— on the desktop shell it sits inargv[2], notargv[1]), fromcreateRequireresolution of the host entry, and from$DSH_HOME/profiles{/*}/node_modules; - imports each candidate and round-trip tests it against the actual request: the first copy whose
isAgentLoopRequest(options)returnstrueis the host's own instance and is used; - if none matches, it warns once and takes over anyway with the first copy. Honest limitation: in that case context rewriting still works, but
@deepseek-ai/dsh-session-titlemay stop auto-titling sessions, because it checks the sameWeakSet. This has been observed on one npm-style install layout and not on the desktop layout. Nothing else in DSH consumes that marker.
The takeover is skipped automatically if an invariants service is present (the dsh-agent-loop frozen-contract assertion would otherwise abort your session). No shipping 1.5 profile loads it, but the plugin steps aside rather than breaking a session if it ever appears.
The startup patch check — it reports, it never patches
On load the plugin inspects the official patch targets it can locate and prints exactly one line:
[reclaim/patch-check] 官方补丁体检(只读,本插件不会自动改官方文件):7 个目标里未检测到 7 条 —— dsh-llm 可写请求、agent-loop 快照回收 / 系统提示收敛、…
It is deliberately read-only. This plugin is not a patcher: modifying files inside your DSH installation is your decision, not a side effect of installing a plugin. The check is a heuristic (it looks for the *.orig backups a patch script leaves next to the file it touched), so it says "not detected" rather than "not patched", and it stays completely silent when it cannot work out your install layout. Silence it with DSH_RECLAIM_PATCH_CHECK=0.
When something is missing and you do want it, do not hunt for a script — hand the job to your own agent. The file shipped inside this package, tutorial-install-and-official-patches.md (Chinese original: tutorial-install-and-official-patches.zh-CN.md), contains a ready-to-paste prompt describing each patch, its target, and its acceptance check; your agent reads it and patches your machine. That is the recommended route, because your agent can see your actual installation while a hard-coded script inside a plugin cannot.
The patch library — all ten official patches, published separately → https://github.com/xiamubumu/dsh-patch-library The patches themselves, an adaptive applier, five ways to apply them and a one-command revert.
node apply.mjs --checkis a read-only health check,--applybacks every file up and runsnode --checkon each one,--revertputs everything back.
The other official patches: what a plugin can and cannot replace
Stated plainly, because installing these plugins does not make the other file patches unnecessary:
| Official 1.5 file patch | Edits | Can a plugin replace it? |
|---|---|---|
@deepseek-ai/dsh-llm — writable request |
lib/index.js |
✅ Yes — done by this plugin, see above |
@deepseek-ai/dsh-client-ui-sidebar — flex-wrap on the footer slot |
lib/lib-client.js |
✅ Yes (a client plugin can inject CSS); not shipped here |
@deepseek-ai/dsh-agent-loop — runtime-snapshot reclaim, system-prompt convergence |
lib/index.js |
❌ No — needs the loop's private projection class and its commit point |
@deepseek-ai/dsh-llm-pi-ai — splitSystemPrompt collects every leading system message |
lib/index.js |
❌ No, and it is pointless without the dsh-agent-loop patch |
@deepseek-ai/dsh-compaction-tool-result-pruner — result pruning + load-bearing guard |
lib/index.js |
❌ No — the host instantiates the exported class directly; there is no registration seam |
@deepseek-ai/dsh-client-ui-renderer — conversation.chat.turnTail chain election |
lib/client.js |
❌ No — the official client bundle itself must be edited |
@deepseek-ai/dsh-session-format-v1-to-v2 — turn/start repair |
lib/index.js |
❌ No — runs inside the session restore path |
@deepseek-ai/dsh-session-persistence-jsonl — the same, persistence side |
lib/worker.cjs |
❌ No — runs in a worker thread |
@earendil-works/pi-ai — max_tokens floor |
dist/api/simple-options.js |
❌ No — third-party dist, its ESM namespace is read-only |
In short: the one patch that these plugins had to depend on is gone. The rest are independent improvements that change how DSH's own machinery behaves, and they remain optional. To apply any of them, use the patch library: https://github.com/xiamubumu/dsh-patch-library.
9. The official-patch notice: one dialog, naming the patch and how to apply it
The official patches this plugin benefits from are announced by this plugin itself, in one dialog. No other plugin speaks for it, and it never speaks for another.
- Which patches: each row gives the patch id, the package and file it edits, and why this plugin wants it (marked required or recommended);
- How to apply them (any one of three): A press "Copy the prompt" and hand the whole block to your own agent (it runs a read-only health check first, applies one patch at a time, and stops to ask you if a hunk does not match); B use the patch library script,
node apply.mjs --checkthennode apply.mjs --apply --only <id>; C apply by hand,cd <package> && git apply -p1 <library>/patches/<id>/change.diff; - The patch library: https://github.com/xiamubumu/dsh-patch-library (ten self-contained patches, an adaptive applier, five ways to apply them);
- Detection: judged from the
*.origbackup next to each target file plus a content hash against the library manifest (pristine/patched). When neither can decide, it says "cannot tell" outright and never guesses "applied"; - Once applied, it stops appearing. The dialog offers "Later" (this once) and "Do not remind me again" (permanent, recorded per patch id — a patch added later still gets announced).
The same dialog also lists the other official patches (owned by no plugin — apply them by symptom), so nothing goes unseen.
Configuration
The configuration file is the profile's cordis.patch.yml:
- id: reclaim
config:
enabled: true
noise: true
bounded: true
stripLines: false
noOpGuardRatio: 0.05
maxInlineBytes: 50000
attribution: true
trimSections: false
foldTools: true
activePlugins: []
| Key | Type | Default | Effect |
|---|---|---|---|
enabled |
boolean | true |
Master switch (!== false counts as on) |
noise |
boolean | true |
Lossless purification (ANSI / trailing whitespace / CR) |
bounded |
boolean | true |
Bounding by content type + retrieval pointer |
stripLines |
boolean | false |
Strip line-number prefixes |
noOpGuardRatio |
number | 0.05 |
The "only act if it saves enough" threshold for bounding, 5% |
maxInlineBytes |
number | 50000 |
Above this byte count, content goes to spill (spill plugin required) |
readOnlyTools |
string[] | see the table above | Read-only tool list, a full-table replacement |
sideEffectTools |
string[] | see the table above | Side-effecting tool list, a full-table replacement |
faithfulTools |
string[] | see the table above | Faithful tool list, a full-table replacement |
attribution |
boolean | true |
Print the [reclaim/usage] attribution line |
trimSections |
boolean | false |
Section trimming (note: the real gate is the environment variable DSH_RECLAIM_SECTION_TRIM, see below) |
foldTools |
boolean | true |
Fold the tool definition surface |
activePlugins |
string[] | [] |
Plugin list participating in section-ownership computation |
⚠️ The value of the
trimSectionsoption does not actually open the gate — the gate in the code only honours the environment variableDSH_RECLAIM_SECTION_TRIM, and it additionally requiresactivePluginsto be non-empty. The old documentation got this one wrong; it is stated accurately here.
Common environment variables
There are more than 40 environment variables in total, covering every internal threshold. Grouped by function, the most commonly used ones are:
| Variable | Default | Effect |
|---|---|---|
DSH_RECLAIM_NOISE |
1 |
Set to 0 to turn off lossless purification |
DSH_RECLAIM_BOUNDED |
1 |
Set to 0 to turn off bounding |
DSH_RECLAIM_STRIP_LINES |
0 |
Set to 1 to strip line numbers |
DSH_RECLAIM_TOOL_FOLD |
1 |
Set to 0 to turn off tool definition surface folding |
DSH_RECLAIM_PROMPT_LINE_MAX |
120 |
Truncation threshold for a single system-prompt line |
DSH_RECLAIM_DEDUP_DENY |
1 |
Set to 0 to turn off duplicate-edit interception |
DSH_RECLAIM_SECTION_TRIM |
0 |
Set to 1 to turn on section trimming (works together with activePlugins) |
DSH_RECLAIM_STABILIZE |
1 |
Set to 0 to turn off prefix stabilization |
DSH_RECLAIM_WRITABLE_REQUEST |
1 |
Set to 0 to stop taking over llm.streamWithRegistration (see section 8); the official dsh-llm patch is only needed if you turn this off |
DSH_RECLAIM_PATCH_CHECK |
1 |
Set to 0 to silence the one-line "official patch check" notice printed at startup |
DSH_RECLAIM_LOG |
1 |
Set to 0 to turn off this plugin's log output |
DSH_RECLAIM_DUMP_SYSTEM |
0 |
Set to 1 to dump the assembled system prompt to a temporary directory, for diagnosis |
The remaining threshold variables (sampling line counts, budget observation, pollution-detection window, archive limits, reasoning stripping, etc.) each have a per-item comment at the top of the source.
📚 Tutorials
| Document | What it covers |
|---|---|
| Install & official patches (English) | How to install (two verified routes) and what to do about the official patches — including prompts that let your own agent apply them the patches themselves ship as a library: https://github.com/xiamubumu/dsh-patch-library |
| 安装与官方补丁(中文) | the same, in Chinese 补丁本体已开源成补丁库:https://github.com/xiamubumu/dsh-patch-library |
🔗 The rest of the family
Each plugin installs on its own; together they save the most. The other three:
dsh-aux-degrade— Auxiliary-call degradation: summaries and titles stop burning their budget on reasoningdsh-capability-composer— Capability composer: switchable tool/skill/MCP sets instead of one bloated surfacedsh-composer-launcher— Launcher: one panel for compositions, plugins and MCP, plus the extension walkthrough
All of them are DSH 1.5 only; newer DSH releases will be supported in follow-up updates. Plugins that need official patches announce them in the UI once you install them — nothing goes unseen.
Installation
From GitHub (recommended; measured on a fresh DSH_HOME)
dsh plugin --profile web add github:xiamubumu/dsh-context-reclaim
One command. DSH registers it into the profile's dsh.profile.bundles by itself; no config to hand-edit.
From a local tarball (air-gapped machines)
Download dsh-context-reclaim-1.1.0.tgz from Releases, then:
dsh plugin --profile web add /path/to/dsh-context-reclaim-1.1.0.tgz
From the DSH plugin marketplace / npm (not yet possible)
The marketplace submission (an awesome-dsh-plugin PR) has not been filed and nothing is published on npm yet ⇒ neither route works today.
Once it is listed/published, installing from the marketplace, or dsh plugin --profile web add dsh-context-reclaim, becomes valid.
⚠️ Known issue: do not feed the release asset's tarball URL to
dsh plugin add.dsh plugin --profile web add https://github.com/.../releases/download/v1.1.0/....tgzfails on the pnpm DSH bundles (10.34.5) withERR_PNPM_MISSING_TARBALL_INTEGRITY(pnpm#14351, fixed upstream only in pnpm ≥ 12.5.1). Installing a second plugin that way always fails — use the two routes above instead.
After installing you need to restart DSH — host-side plugins are only loaded when DSH starts.
This plugin has no runtime dependencies at all; peerDependencies only declares the official @deepseek-ai/dsh-tools. Installation will not stuff any third-party package into your profile.
Compatibility
| DSH kernel | 1.5 series (0.1.5-rc.2 ≤ version < 0.2.0) — this is exactly the range written in package.json's engines.dsh. Later DSH versions will be followed up |
| Form factor | Desktop ✅ · Web ✅ — more precisely, anything running DSH is supported; this plugin does not depend on any desktop-only capability |
| Operating system | Windows · macOS · Linux |
| Node syntax | 20 and above (this is the syntax floor the source uses; you do not need to install Node yourself, DSH ships its own runtime) |
This plugin has a hard, ranged requirement on the DSH kernel version: engines.dsh is declared as >=0.1.5-rc.2 <0.2.0, and the plugin marketplace judges compatibility accordingly. Once DSH upgrades to 0.2.0 this plugin will be ruled incompatible, and a new version will be published at that point.
A stock, unpatched DSH install is fully supported. Context reclaiming needs a writable request, and since 1.0.0 this plugin produces one itself — see section 8. Nothing has to be patched for it to work. The remaining official patches alter DSH's own machinery and cannot be expressed as a plugin; what each of them does, and whether a plugin could ever replace it, is listed honestly in the section 8 table. Installing a patch later never conflicts with this plugin: a request that arrives unfrozen is passed straight through.
💡 Companion advice: add a context-compression plugin (not part of this project)
What this plugin does is keep the request from growing without bound and the prefix from moving: tool-result purification, bounding by content type, tool-definition folding, description-line truncation, duplicate-edit interception, summary-integrity audit, cost attribution, and making the request writable (section 8).
What it does not do is turn a long history into a summary. So we recommend installing a context-compression / history-summarisation plugin as well — they are complementary: the compression plugin collapses history as the conversation grows, while this plugin keeps the prefix stable across that compression and keeps the raw material (tool results) from inflating. A stable prefix is easier to cache-hit, which is cheaper.
Measured on one machine (2026-10-14, for reference only)
| Metric | Measured |
|---|---|
| Context size in long sessions | stays under 200K |
| Prefix cache hit rate | about 98% |
| Cost (best case) | about under ¥3 (CNY) per half hour at the model pricing and usage then — the peak, not the normal case |
⚠️ These numbers are the best case under a long continuous run — not the normal case, not promises, not limits and not a quote. In practice many things keep you from reaching them: a broken prefix (switching models mid-session, a changed system prompt, reordered context, another plugin inserting new content near the front); material that keeps arriving (a new skill catalogue, new MCP tools, very long documents, pasting large blocks); compression happening at a bad moment or tool results inflating beyond expectation; switching tasks, opening fresh sessions often, a changing tool surface; changed model pricing, cache policy or provider. Real behaviour on your machine is what counts.
Pick whatever companion you like. The one we measured with is the third-party plugin billion-context — it is not a plugin of this project and has no affiliation with it (not a dependency, not a partnership, not installed by this plugin); we are simply recording that we ran it alongside. A compression plugin from anyone else works too.
This plugin promises only its own behaviour: prefix stabilisation, bounded tool results, and a writable request when compression runs. Compression results and the final bill depend on the model, cache policy and conversation content you actually use.
🔗 Interaction with other plugins
🔗 With dsh-aux-degrade (the context governance pair) — install both together
dsh-context-reclaim and dsh-aux-degrade are the two halves of the same "context governance" scheme. Installing both together is strongly recommended; this is the most important interaction this plugin has.
① They share the same routing criterion. Both plugins use the same test to tell a "main-conversation call" from an "auxiliary call":
tools.length > 0 ? 'main' : 'aux'
reclaim uses it to tag cost attribution with route=, and aux-degrade uses it to decide whether to lower the reasoning effort of this call. The criterion must be identical, otherwise the two plugins will disagree about "which call counts as an auxiliary call", one saving while the other leaks.
② How they divide the work. reclaim handles content (purification and bounding of tool results before they enter history, tool definition surface folding, prefix stabilization); aux-degrade handles calls (lowering the reasoning effort of auxiliary calls such as compaction summaries / session titles to off, and appending two faithful-preservation requirements to the compaction instruction).
③ What happens without the other.
| Installed alone | Result |
|---|---|
| Only reclaim | Everything works as usual. But auxiliary calls still burn the full reasoning budget, and compaction summaries can still be truncated by the output cap — reclaim will only print [reclaim/summary-audit] ⚠️ 摘要缺 N/8 段 in the log to remind you, it will not modify the summary itself (this is deliberate: reclaim does not touch committed content) |
| Only aux-degrade | Everything works as usual. But tool results are not purified or bounded, and long results enter history unchanged |
④ They do not depend on each other. reclaim's source contains no import / require at all, and aux-degrade only imports the official @deepseek-ai/schemastery. They do not probe each other and do not reference each other — either one can be installed alone and work normally, just with different coverage. This is also why they are two repositories rather than one.
⑤ One practical detail. If you turn on reclaim's [reclaim/usage] attribution log, you will see a purpose= field in it. That field exists to diagnose "which class of auxiliary call is not being lowered to off by aux-degrade" — it is a read-only diagnostic that changes no behaviour. When you see purpose=unknown or a purpose you have never seen before, that usually means some auxiliary call path bypasses the degrade criterion.
⑥ In one sentence. With reclaim installed and aux-degrade missing, you will see the problem but cannot fix it; with aux-degrade installed and reclaim missing, your auxiliary calls are saved but your tool results are not. Install both, and context governance is complete.
🔗 With dsh-capability-composer
reclaim internally maintains an ownership table of "which section of the system prompt belongs to whom" (SECTION_OWNER), in which the "available plugins" section belongs to dsh-capability-composer.
In other words: the plugin list should not be trimmed by reclaim — that is the capability composer's job. When both are installed, narrowing of the section is carried out by the composer, and reclaim only keeps the default behaviour when the composer is absent.
🔗 With dsh-composer-launcher
No direct coupling. The two can be installed in any combination.
FAQ
Will it break my prefix cache?No. This is the most important design constraint of the plugin: it only acts before content enters history, and never rewrites content that has already been committed. The committed part stays byte-for-byte identical, so the prefix cache will never be invalidated because of reclaim.
Will it alter my tool output?It only truncates when the threshold is exceeded, and it always leaves a retrieval pointer at the truncation point. The purification layer is lossless (ANSI escapes, trailing whitespace — things that carry no information in the first place). In addition, three classes of tools are listed as "faithful": they pass through unchanged, not a single character is touched.
Do I need to restart after installing?Yes. Host-side plugins are loaded when DSH starts. After one restart it stays in effect.
Will it install anything else into my profile?No. The plugin has zero runtime dependencies, peerDependencies declares only official packages, and the installation process introduces no third-party code.
Look at the hit / miss fields of the [reclaim/usage] log line — those are the real per-request prefix-cache character counts. A truncated result carries its own note at the truncation point (how much was saved, how to retrieve it).
Yes. This plugin is a host-side plugin and only depends on Cordis's generic seams (tools/post-execute, system-prompt/assemble, session/event, llm/stream), with no dependency on Electron or any desktop-only capability. As long as it is DSH, both desktop and web work.
Because they handle two different layers of the same thing: one handles content (how results are processed before entering history), the other handles calls (how much reasoning budget an auxiliary call spends). See the interaction section above.
Repository layout
dsh-context-reclaim/
├── index.js # the entire implementation (single file, no external dependencies)
├── cordis.patch.yml # DSH plugin manifest patch
├── package.json
├── verify.mjs # offline self-check: node verify.mjs
├── README.md # English
└── README.zh-CN.md # Simplified Chinese
Self-check
node verify.mjs
Runs offline and writes nothing to disk. It checks the manifest fields, the syntax, whether any private key or backup file slipped in, and whether any hard-coded machine path exists in the runtime code.
No comments yet. Be the first to write one.