DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Thinking Breaker — DSH Plugin for DeepSeek Harness
← Plugins
T

dsh-thinking-breaker

Thinking Breaker

DSH host plugin: three circuit breakers — abort a runaway reasoning (thinking) loop, abort a reasoning stream that has degenerated into *** or emoji with no words left, and stop/steer repeated tool failures before they are re-run.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:wwwwwwwwwwwwwwy/dsh-thinking-breaker#509520b82a0e45089bba7e3034fd2a5586dbb9be
READMECompatibilityVersions

Compatibility and provenance

Thinking Breaker is published as dsh-thinking-breaker and currently resolves to version 1.2.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
any
Release source
github
Registry updated
10/8/2026

Versions

1.2.0stable
10/8/2026
1.1.0stable
10/5/2026

Related plugins

Loading related plugins…

Latest
1.2.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
any
License
MIT
Source
github
GitHub
★ 3
Weekly downloads
0
Last push
10/8/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in

Related plugins

More verified plugins in developer-tools.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Sdk Minimal@deepseek-ai/dsh-sdk-minimalThe standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, and JSONL sessionsSdk App@deepseek-ai/dsh-sdk-appThe dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-baseExperimental Inspector@deepseek-ai/dsh-experimental-inspectorExperimental cross-realm CDP hub for Host debugging and Client Runtime inspection

README

dsh-thinking-breaker

Three circuit breakers for DeepSeek Harness (dsh) agent sessions — in one host plugin.

  • A. Reasoning-loop breaker — when the model repeats itself ≥10 consecutive times while thinking, the request is aborted immediately and the step is retried once with thinking forced off, so it answers instead of re-reasoning.
  • A2. Degeneracy breaker — when the thinking stream stops carrying meaning altogether — literal asterisks (***, *****), a rotating emoji flood, punctuation soup — the request is aborted on the same terms, after two blocks (256 code units) instead of waiting for a repeat to be confirmed.
  • B. Tool-failure breaker — when the same tool call keeps failing in one turn, the model is first told to change approach, and after the threshold the identical call is denied before dispatch.

English | 简体中文

Install

dsh plugin --profile desktop add github:wwwwwwwwwwwwwwy/dsh-thinking-breaker

Then restart the host — ESM caches are per-process. Full instructions, including the link: caveat and rollback, are in INSTALL.md.


Why a plugin and not just a prompt

A skill or prompt is a document the model has to choose to read. A model already stuck in a loop will not read it, and nothing can interrupt it mid-thought from the prompt layer. Interruption has to happen in the host — which is what this plugin does.

It is also worth stating what dsh has natively: nothing for this. maxTokens only caps a whole response (it cannot see that the model is going in circles), reasoningEffort only picks a level, and streamIdleTimeoutMs only fires when no token arrives — a loop keeps emitting, so it never fires. There is no thinking-token budget (reasoningTokens is a reporting field, not a budget) and no maxSteps/maxTurns anywhere in the harness.

Hooks

HookRole
llm/stream (prepend)Feeds reasoning-delta chunks to two pure detectors — repetition and degeneracy — and text-delta to a third when degeneracyOnAnswer is on. On a trip: drain the rest silently, emit a terminal finish chunk (kind: 'aborted').
agent/request-error (prepend)Recovers only this plugin's own aborts, within a per-turn budget, by returning { kind: 'retry' }. Everything else delegates to next().
agent/request (prepend)Stamps the session's turn; forces reasoningEffort: 'off' on the recovered attempt.
tools/post-execute + tools/pre-execute (prepend)Post-trip guidance; then tool-failure steering, then pre-dispatch denial.

Three design constraints that are not preferences

  1. A llm/stream listener must not throw, and the stream must not end without a terminal chunk. dsh-llm registers its own prepend validator that fails the call with LLM stream ended without a terminal finish chunk; and a throwing listener is a middleware/consumer failure, documented to "remain thrown and close the turn directly" — so it never reaches agent/request-error and the recovery logic could not take over. Hence: lawful abort first, explicit recovery second.

  2. The terminal reason is aborted, not error. The retryable code set is EMPTY_RESPONSE | RATE_LIMIT | SERVER | TIMEOUT | TRANSPORT; ABORTED is not in it. So the default handling is terminal (safe), and the retry is something this plugin opts into explicitly — once per turn, then it falls back to terminal.

  3. The pre-dispatch gate can only key on tool + normalised arguments. At pre-dispatch time there is no result, so a signature that folded in error text could never match what post-execute recorded. Steering uses tool + args + code + normalised error text; the gate uses tool + args only. Digits and hex collapse during normalisation, so a retry that only changes a line number still counts as the same call — while a structurally different approach gets a fresh signature and passes.

Detection algorithm — lens A: repetition

See the header comment in lib/detector.js. The stream is cut into disjoint blocks (blockChars, default 128 UTF-16 code units); each block yields

  • a 128-bit whole-block fingerprint (four independent FNV-1a seeds) → exact-repeat;
  • a set of 4-code-point shingles, compared by Jaccard overlap against recent blocks → low-novelty after noveltyStreak consecutive blocks.

Why not a sliding window

The first implementation compared overlapping sliding windows. It is measurably broken: with window W and stride S, two windows S apart already share W−S characters by position, so the overlap score has a structural floor. Measured on a real case (a 35-code-unit Chinese paragraph, W=256, S=64) the loop scored 0.67 — inside the clean-text band, so no threshold separated them. Disjoint blocks share nothing by position, so all overlap is genuine repetition.

Thresholds are measured, not guessed

tools/calibrate.mjs sweeps the signal over 6 loop shapes × 3 clean corpora. blockChars was swept over 96/128/192/256 — only 128 lets every loop shape clear the threshold with a long run while all clean corpora score zero.

At noveltyStreak: 10, noveltyOverlap: 0.7:

CorpusResult
Verbatim / near-verbatim / ping-pong loops, English and Chinesetrip at block 12–18
Clean: unique prose, varied English reasoning, varied Chinese reasoningno trip

Measured cost before the abort fires: 533 code units for a tight short loop, 1 681 ping-pong / CJK paragraph, 1 927 English paragraph, 2 952 near-verbatim, 6 027 CJK paragraph with a counter. Longer repeated units need longer confirmation windows.

Detection algorithm — lens B: degeneracy

See the header comment in lib/degeneracy.js. Lens A asks has this been said before; lens B asks does this still say anything. It measures what a block is made of rather than comparing it to anything.

Each block yields up to five evidence flags, and the decision is

degenerate(block)  =  meaningless  AND  at least degEvidenceMin others
FlagReadsFires when
meaninglessletters + digits over non-spacegate: no lexical content left
noisypunctuation + emoji over non-spacethe block is mostly symbols
low-varietydistinct code points over the first 48a handful of glyphs reused
run-heavylongest / count of symbol runs (≥ 3)one absurd run, or many runs
emoji-floodemoji count and densityemoji are the block's substance

The trip is then named after the most specific evidence: symbol-flood, emoji-flood, or degenerate-output.

Why the gate, and why several signals

meaningless is a hard gate, not a vote, and that buys a property instead of a tuned threshold:

A block carrying real lexical content can never be degenerate.

Every false-positive risk — markdown tables, dense code, base64 blobs, numeric tables, arrow chains, JSON, ==== rules surrounded by prose, emoji used as annotation — contains words, so all of them are excluded by construction. This is measured, not asserted: tools/calibrate.mjs reports that removing the gate makes three clean corpora trip, and it is the gate that stops them.

The remaining flags then vote among themselves, so the rule stays multi-signal rather than a single test. An OR would fire on ordinary markdown, and "do not disturb normal thinking" is a hard requirement here, not a preference.

run-heavy is what separates a *** flood from a ==== rule: a separator is one run, the flood is many. Both are covered (degRunCountMin, degRunLenMin) and neither is sufficient alone.

Thresholds are measured, not guessed

tools/calibrate.mjs sweeps 11 degenerate corpora against 11 clean ones and reports cleanTrips / degenerateMisses for each evidenceMin × streak pair:

evidenceMinstreak 1streak 2streak 3streak 4
11 / 00 / 00 / 00 / 2
21 / 00 / 0 ← shipped0 / 00 / 2
31 / 20 / 20 / 20 / 4
40 / 100 / 100 / 100 / 10

The shipped 2 / 2 is the largest setting that misses nothing and the smallest that trips on nothing. evidenceMin: 1 trips on legitimate markdown; 3 already misses two real floods.

Measured per-block figures for the shapes that matter (default thresholds, blockChars: 128):

Corpusmeaningnoisevarietyrunsverdict
*** flood0.0001.000.0432trip at block 1
one 600-char * run0.0001.000.021trip at block 1
rotating emoji flood0.0001.000.060trip at block 1
200 distinct emoji, no repeat0.0001.001.330trip at block 1
CJK punctuation soup0.0001.000.130trip at block 1
varied English reasoning0.9720.030.600no trip
markdown table0.3330.670.259no trip (gate shut)
code block0.7190.280.540no trip
200-char ==== rule in prose0.0470.950.191no trip (streak never reaches 2)
ASCII box with labels0.1080.890.234no trip (gate shut)
emoji as annotation0.7520.250.630no trip

Why this is not redundant with lens A

The 200-distinct-emoji corpus above is the case that justifies the second lens. It is cycled with a stride co-prime to the block size, so no two blocks are byte-identical and the 4-gram shingle sets never line up (measured overlap ≈ 0.28, against a noveltyOverlap of 0.70). Lens A is blind to it by construction; lens B catches it in two blocks. Both directions are pinned by tests — including the converse, that a repetitive flood is normally caught by lens A first.

The two lenses are also complementary in the other direction: degenerate-but-wordy shapes (* 检查 * 检查 * 检查, 好的好的好的) carry lexical content and are out of scope here — they are exactly what lens A's repetition rules catch.

Configuration

The full recommended block lives in cordis.patch.yml. Every key is optional; the plugin's defaults equal the values shown there.

Known limits (stated plainly)

  • Context is never rewound. This is the fundamental limit: the looped reasoning is already committed to the transcript and no hook removes it (assembly re-reads the log, and rewriting history would break the provider prompt cache). "Re-think" means the same step, done cleanly once — not a cleared history. The post-trip hint is the available counter-measure.
  • No round budget. Cross-step loops (failing differently every time) are out of scope; the harness has no maxSteps/maxTurns.
  • A loop unit shorter than one block, dragged along by varying content, is missed (measured overlap 0.22 for a 6-character phrase followed by a ticking counter).
  • Semantic circling is missed — detection is character-repetition based.
  • A tool that reports failure as success is not caught (by design: that is the tool's semantics).
  • Each trip costs something: a few hundred to a few thousand tokens.

Specific to the degeneracy lens:

  • A block with any real lexical content is invisible to it, by design. That is the guarantee, and it is also the blind spot: * 检查 * 检查 * 检查 and 好的好的好的 are wordy, so they are out of scope. Lens A catches those.
  • Pure-symbol ASCII art can trip. A block that is entirely +/-/| with no labels in it raises the gate plus three signals. Requiring degStreak: 3 is the first thing to try if a workload draws a lot of it. A box with words in it (measured above) is safe.
  • The gate is a ratio, not a word count. A block that is 95 % symbols and 5 % words passes the gate (meaning ratio 0.05 ≤ 0.08) and can trip. This is deliberate — a *** wall with a stray word in it is still a wall — but it is the boundary to know about.
  • Emoji classification is heuristic. \p{Extended_Pictographic} minus a deny-list for pictographs that technical prose uses as text (arrows, ©, ®, ™). A misclassified glyph only moves it between the emoji-flood and noisy signals, both of which are secondary, so detection power is unaffected — but the rule name in the log can be off for exotic symbols.
  • The answer stream is not watched by default. Aborting output the user is already reading is a bigger deal than aborting a thought; turn on degeneracyOnAnswer if that trade is right for you.

Verify

npm run check     # everything below in one shot: no network, no install
npm test          # 132 tests
npm run calibrate # re-measure both threshold tables
npm run verify    # cordis contract regression

On Windows use check.cmd; on Linux/macOS use ./check.sh.

CI status

The workflow lives at .github/workflows/ci.yml and runs on every push and pull request: the full test suite, the host-contract regression, the calibration sweep, a syntax pass over every module, and an npm pack --dry-run check that the runtime files are actually inside the published package.

npm run check runs the same checks locally, with no network and no install.

Install

See INSTALL.md.

License

MIT