Rapid-refill circuit breaker for DeepSeek Harness automatic compaction. It refuses a compaction that would be futile — the context refilled within fewer than N tool turns M times in a row, which the unbounded step-pressure trigger cannot detect — and instead of burning a summarization call on every step it reports the situation with advice, usually that a single read or tool output was too large. Shows state and re-arms through /compaction-breaker, and injects one prompt section so the model relays it to the user. Covers host-plane sessions through its profile bundle; ordinary sessions need one row replaced inside an agent preset.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:BOWLUNA/dsh-zcode-breaker
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
A rapid-refill circuit breaker for DeepSeek Harness automatic compaction: it stops the futile compact-refill-compact loop and tells the user which oversized read or tool output caused it. Derived from the ZCode implementation of the same idea.
The problem, concretely
Automatic compaction has two trigger paths in @deepseek-ai/dsh-compaction-basic:
| Trigger | Entry point | Attempt bound |
|---|---|---|
| Step-boundary pressure | agent/pre-step calls compactIfNeeded(agent, "pressure") |
none |
| Context-overflow recovery | agent/request-error calls compactIfNeeded(agent, "context-overflow") |
maxOverflowRetries |
The pressure path is unbounded: once measured pressure exceeds thresholdRatio, the next step compacts again, and every compaction is a full summarization model call. One oversized file read or tool output therefore produces a loop that burns a model call per step until the session is abandoned, and the transcript shows only this repeating line:
compaction (step pressure): shadowed N surface nodes (seqs A-B, ~T tokens)
What it does
It extends BasicCompactionEngine and wraps exactly one method, compactIfNeeded:
- State is per session, so sessions cannot pollute each other.
- A tool turn is read from the durable session log: one assistant message carrying at least one tool call. That is the unit the compaction seam itself uses when it reasons about surface balance, rather than a guessed step count.
- A compaction requested fewer than
toolTurnThresholdtool turns after the previous one counts as a rapid refill. - At
maxConsecutiveRapidRefillsin a row the attempt is refused before it runs, so the summarization call is never spent. - A refusal latches and throws a
CompactionRapidRefillErrorcarrying actionable advice. - It also injects one prompt section while tripped, because the host catches errors from
compactIfNeededand continues the turn: a thrown error alone would leave the person with no explanation. - The command
/compaction-breakerreports the state and can re-arm it.
Everything else stays the base engine's: trigger policy, retention, surface mutation, tool-pairing safety and summarization.
Two planes, and why both matter
compaction-basic exists twice in a stock harness: once as a host-plane row in @deepseek-ai/dsh-base, and once inside the agent preset's compaction group, which is declared isolate: { compaction: true, toolResultPruner: true }. An agent session resolves ctx.compaction inside that isolated realm, while a session that joins no preset resolves the host row. That is why a profile patch alone cannot change an agent's compaction, and why the preset row in the second half of this section exists.
The seam is single-slot: a second provider in the same isolate scope makes ctx.provide() throw, and ctx.reflect.set() accepts writes only from the owning fiber. Each plane is therefore covered by replacing its row, never by shadowing it.
| Plane | Who it serves | How this package covers it |
|---|---|---|
| Host | sessions that join no agent preset | automatically, through the bundle patch the installer mounts |
| Agent realm | every ordinary session | one row replaced inside your agent preset, because no profile patch can reach a realm |
Install
dsh plugin --profile web add dsh-zcode-breaker
The install mounts cordis.patch.yml, which disables the host-plane compaction-basic row and puts this engine in its place. Replacing rather than wrapping is forced by the single slot.
Then, for agent sessions, replace the row inside your preset's compaction group in agent.cordis.yml:
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: compaction-breaker
name: 'dsh-zcode-breaker'
config:
toolTurnThreshold: 2
maxConsecutiveRapidRefills: 3
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
Copy the preset into your user preset directory rather than editing the shipped one, or a harness upgrade will overwrite the edit.
Configuration
Every base-engine key survives, re-declared on the subclass so it is neither rejected by the base engine's strict validator nor lost:
| Key | Default | Meaning |
|---|---|---|
thresholdRatio |
0.8 |
pressure ratio that triggers compaction |
retainRatio |
0.16 |
retained tail ratio after compaction |
retainTokens |
none | absolute token retention instead of the ratio |
summarizationProvider |
empty | route used for summaries |
summarizationModel |
empty | model used for summaries |
maxTokens |
8192 |
summary output cap |
compactionRetries |
1 |
retries after a failed summary |
maxOverflowRetries |
1 |
overflow-recovery retries |
modelPolicies |
none | per provider and model overrides of the keys above |
auto |
true |
automatic compaction master switch |
Added by this plugin:
| Key | Default | Meaning |
|---|---|---|
toolTurnThreshold |
2 |
a gap under this many tool turns is rapid; exactly the threshold is not |
maxConsecutiveRapidRefills |
3 |
refuse on the Nth consecutive rapid refill |
announceInPrompt |
true |
inject the advisory prompt section once tripped |
That is 13 config keys in total, and this file declares compatibility with dsh >=0.1.5-rc.2 <0.2.0-0.
Surfaces
| Surface | What it shows |
|---|---|
| Log | one warn line naming the consecutive count, the threshold and the last gap |
/compaction-breaker |
a state report; /compaction-breaker reset re-arms |
| Prompt section | injected while tripped, instructing the model to relay the situation |
Semantics — three deliberate decisions
- A refusal latches. A refused attempt never commits, so without latching the breaker would release itself once the gap passed the threshold, blocking twice and allowing once. The latch is what makes this a stop rather than a slowdown.
- Two ways out exist: the reset subcommand, or a successful manual
/compact. A person deliberately compacting is new information and should not be refused because of what the automatic path did earlier. - Reset does not zero the tool-turn clock. It is the session's monotonic clock, and zeroing it would make every later gap look healthy. Only the rapid counter, the last compaction mark, the latch and the refused-attempt count are cleared.
A trip is not a global disable: the session keeps working, only automatic compaction is off for it.
Compatibility and known limits
- It is mutually exclusive with other compaction engines. Several backends occupy the same
ctx.compactionslot, and none of them can be mounted alongside this engine. That is a limitation of the seam rather than a choice made here. - The host-plane row is untouched, so sessions that do not compose a preset are unaffected.
- Base defaults are inherited, since the base engine's own schema carries no defaults and its resolver supplies them. A future base-engine key therefore needs re-declaring here to remain configurable.
- State lives in module-level WeakMaps rather than private class fields. That is deliberate: the object a realm hands out as
ctx.compactionis not reliably the one whose private initializers ran, and a private-field read would throw inside the compaction path where the host swallows it. - The model-driven end-to-end acceptance has not been run. Everything up to and including the engine serving
ctx.compactioninside a real realm is verified; seedocs/MEASUREMENTS.md.
Composing with another backend
The policy core is exported so another backend can adopt it in roughly twenty lines:
import { RapidRefillTracker } from 'dsh-zcode-breaker/tracker';
const tracker = new RapidRefillTracker({ toolTurnThreshold: 2, maxConsecutiveRapidRefills: 3 });
tracker.noteToolTurn();
const gate = tracker.gate();
if (gate.blocked) throw new Error('rapid refill loop');
const result = await myEngine.compact();
if (result !== null) tracker.commitCompaction(gate.projectedConsecutiveRapidRefills);
Tests
npm test
19 tests in 2 suites, with no services, no model and no session: the policy core in test/tracker.test.js and the engine wiring in test/engine.test.js. The wiring suite needs the peer packages resolvable and skips rather than fails where they are missing.
Roadmap
- Run the model-driven acceptance: build a refill loop in a real session and watch the breaker trip.
- Choose the base class at runtime, so the breaker composes with whichever backend a user prefers.
- Offer the policy core upstream to
@deepseek-ai/dsh-compaction-basic.
License
MIT
Links
More in this category
Minglink/dsh-infinite-gen-3★ 1864
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
liangmianya/dsh-synapse★ 414
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
ranxianglei/billion-context★ 235
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
Nwflower/dsh-chat-import★ 193
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
Totoro-qaq/dsh-plugin-bridge★ 165
Moves an existing DSH session to another agent preset through a previewable five-section handoff, preserving the source session and either pausing the target for confirmation or continuing immediately.
Anionex/dsh-turn-rewind★ 117
Rewind conversation and workspace state, powered by a persistent Change Ledger.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.