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.

Loop Detector — DSH Plugin for DeepSeek Harness
← Plugins
L

dsh-loop-detector

Loop Detector

Infinite-loop/duplicate-output detection and hard-interrupt plugin for DeepSeek Harness (dsh 0.2.x): text duplicate detection + intent-level duplicate detection for tool calls; upon detection, performs a hard cancel interrupt and uses steer to guide a retry

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

npx -y @deepseek-ai/dsh plugin --profile web add github:viego-qiin/dsh-loop-detector#9833097a205f2fff62acdf0ce91501e9960c3550
READMECompatibilityVersions

Compatibility and provenance

Loop Detector is published as dsh-loop-detector and currently resolves to version 2.0.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/14/2026

Versions

2.0.0stable
10/14/2026

Related plugins

Loading related plugins…

Latest
2.0.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
any
License
MIT
Source
github
GitHub
★ 2
Weekly downloads
0
Last push
10/14/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 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 inspectionExperimental Inspector Profile@deepseek-ai/dsh-experimental-inspector-profileOptional Web bundle for raw Session logs and Chat node inspection

README

dsh-loop-detector

Dead-loop / repetition detector with hard interrupt for DeepSeek Harness (dsh).

Local LLMs (especially quantized / RL-tuned reasoning models) sometimes get stuck: repeating the same sentence forever, or re-running web_search with reworded queries for the same topic without gaining any new information. This plugin watches the agent's stream in real time, detects both kinds of loops, and interrupts them hard — not a soft reminder the model can ignore.

中文文档见 README.zh.md.

v2.0.0 targets dsh 0.2.x. If you installed v1 on dsh 0.2.x and it never fired, that is expected — see the next section.

Why v1 does not work on dsh 0.2.x

v1 was written against an older dsh. Three things broke, each fixed in v2:

Problemv1Reality on 0.2.xv2
Never sees streamed textlistened to session/event → assistant/chunkassistant/chunk is no longer a live v2 session event (only the legacy-format migration packages read it)listens to agent/assistant-stream
Interrupt does not interruptused agent.steer() as the interruptsteer() only queues a message for the next step; it does not abort the current generationagent.cancel(cause, { keepInbox: true }) first, then steer() to guide
Retry budget never runs outreset the counter on turn/endevery steer() opens a new turn, so the counter was cleared as soon as it grewresets on genuine user input (plugin-injected messages are tracked by id and excluded)

v1's intent detector also tokenized on "non-alphanumeric", which collapses a whole Chinese sentence into a single token — so Chinese reworded-search loops were never detected. v2 uses "stop-character splitting + CJK bigrams + Jaccard similarity".

Features

  • Text-level repetition detection — watches agent/assistant-stream for text-delta and (optionally) reasoning-delta, with three layered detectors:
    1. Period scan: the tail repeats with a fixed period (default 8–256 chars) ≥4 times
    2. n-gram: the same 64-char block appears ≥3 times inside the last 2048 chars
    3. Large anchor: the trailing 512 chars reappear earlier and extend backwards (catches "the whole plan printed twice")
  • Noise rejection: a repeated unit needs ≥2 distinct characters and ≥30% substantive characters (letters/digits/CJK), so markdown rules (--------, |:---|:---|) are not mistaken for a loop.
  • Tool-call intent-level detection — watches tools/post-execute, extracts keywords from each call's arguments, and compares them against the union of the last 3 calls. A low novelty ratio or a high Jaccard similarity, twice in a row, means "same task, reworded". toolPatterns takes wildcards, so grep, shell commands, etc. can participate.
  • Hard interrupt: the current generation is actually aborted, then a guidance message is handed to the model for one retry; after maxRetries the whole turn is cancelled.

dsh ships @deepseek-ai/dsh-repeat-tool-reminder, but it is advisory only (injected as context). In practice local models (e.g. Qwen / Ornith 35B MoE with reasoning off) ignore it and keep looping — which is exactly why this plugin exists.

Quick start

Prerequisite: a running dsh profile (dsh 0.2.x, Node ≥ 22.18 — the bundled runtime qualifies).

Option 1: link it as a local dependency

cd $env:USERPROFILE\.dsh\profiles\desktop
pnpm add link:"D:\path\to\dsh-loop-detector"

Dependency-resolution contract: every runtime dependency of this plugin (@deepseek-ai/cordis, dsh-agent, dsh-llm, dsh-tools, schemastery) is declared in peerDependencies. For a link:-ed plugin, dsh's profile resolver routes a bare specifier to dsh's own installed copy only when the plugin's package.json lists that name under peerDependencies. Do not move these into dependencies: Node would then look inside the plugin's own node_modules, and a miss fails the whole plugin load.

Option 2: copy it in

Copy-Item -Recurse -Force "D:\path\to\dsh-loop-detector" `
  "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-loop-detector"

Register it as a bundle (skip this and the GUI will not list it)

The plugin ships dsh.bundle.patch (pointing at this repo's cordis.patch.yml), which is its self-mount patch. So all you need is to add the package name to the profile's dsh.profile.bundles:

// $env:USERPROFILE\.dsh\profiles\desktop\package.json
"dsh": {
  "profile": {
    "bundles": [
      "@deepseek-ai/dsh-base",
      // …existing entries…
      "dsh-loop-detector"     // ← add this line
    ]
  }
}

Restart dsh: the plugin now shows up under Settings → Plugins → Installed, where the toggle can enable or disable it. (pnpm add link: alone makes the plugin work, but it will not appear on the plugins page — that list is exactly this bundles array.)

To override defaults, put an id-targeted override at the end of the profile's cordis.patch.yml; it survives plugin upgrades:

- id: loop-detector
  config:
    minLen: 1024
    checkReasoning: false

To disable temporarily, use the GUI toggle, or:

- id: loop-detector
  disabled: true

Then let a model loop on purpose. The log shows:

[loop-detector] session xxx 正文本命中: periodic repeat: period=8 x4|片段 "abcdefgh"
[loop-detector] session xxx 检测到死循环(重试 1/1): periodic repeat: period=8 x4;已打断并引导重试
[loop-detector] session xxx 意图级重复: repeated web_search intent: novelty=0.29 similarity=0.71

Configuration

Interrupt behaviour

FieldDefaultMeaning
minLen512Start detecting only after this many characters
maxRetries1Interrupt-and-guide attempts; then the turn is cancelled
action'steer''steer' = interrupt + guided retry; 'cancel' = cancel on first hit
cooldownMs1500Cooldown after a hit (residual chunks must not re-trigger)
checkTexttrueDetect plain text output
checkReasoningtrueDetect reasoning output
checkEvery64Run the detector at most once per this many new characters
bufferLimit16384Trailing characters kept per stream (bounded memory)

Tool-call intent-level detection

FieldDefaultMeaning
toolPatterns['web_search']Tools to watch (wildcards); empty array disables the feature
historySize3How many recent calls form the topic keyword set
noveltyThreshold0.25Novel-keyword ratio below this counts as "no new information"
similarityThreshold0.5Jaccard similarity at or above this counts as the same topic
stallLimit2Consecutive stalled calls before it is a loop

Detector tuning (rarely needed)

FieldDefaultMeaning
minPeriod / maxPeriod8 / 256Period scan range
periodRepeats4How many identical periods make a loop
ngramSize / ngramRepeats64 / 3n-gram block size and minimum repeats
ngramWindow / ngramStep2048 / 16n-gram window and sampling stride
anchorSize / anchorWindow512 / 8192Anchor length and window
minDistinctChars2Minimum distinct characters in a repeated unit
minSubstantiveRatio0.3Minimum substantive-character ratio in a repeated unit
legacySessionEventsfalseOld-dsh compatibility channel (session/event → assistant/chunk)

Invalid configuration (unknown action, minLen < 1, ratios outside [0,1]) fails loudly at mount time instead of silently falling back.

How it works

Text level (src/detector.ts):

1) period scan  tail repeats a period of [minPeriod..maxPeriod] for periodRepeats times
2) n-gram       the same ngramSize-char block appears >= ngramRepeats times in ngramWindow
3) anchor       trailing anchorSize chars reappear earlier and extend >= anchorSize back
every layer additionally requires a "meaningful" unit: >= minDistinctChars distinct chars
and substantive-character ratio >= minSubstantiveRatio

Tool-intent level (src/intent.ts):

keywords   = latin words + CJK bigrams split on stop characters
topic      = union of the last historySize calls' keywords
novelty    = novel keywords / current keywords
similarity = |intersection| / |union|            (Jaccard)
novelty < noveltyThreshold OR similarity >= similarityThreshold  -> one "stall"
stallLimit consecutive stalls -> intent-level loop -> interrupt

CJK bigrams exist because whitespace tokenization is meaningless for Chinese; the extra similarity criterion exists because rewording in Chinese naturally introduces new bigrams.

Development and tests

npm test    # node --test runs .ts natively (Node >= 22.18 type stripping)

39 tests cover all three detectors (hits and non-hits: markdown rules, ordinary prose, short text), Chinese/English keyword extraction and intent decisions, configuration validation, and — using a real cordis Context and the real dsh-agent scoped dispatcher — the wiring of agent/assistant-stream / agent/pre-step / tools/post-execute plus the "interrupt + guide, then cancel" behaviour.

Running the tests requires @deepseek-ai/cordis, @deepseek-ai/dsh-agent and @deepseek-ai/dsh-llm to be resolvable. A bare node --test never goes through dsh's profile resolver, so this repo may contain a git-ignored node_modules junction pointing at dsh's installation node_modules — it exists only for local tests. At runtime dsh uses its own resolver and does not depend on it.

New-Item -ItemType Junction -Path .\node_modules `
  -Target "$env:LOCALAPPDATA\Programs\dsh\resources\app\dsh\node_modules"   # your dsh install dir

Limitations

  • Detection is heuristic; tune the thresholds for your model. For small local models try minLen between 256 and 512.
  • Intent-level detection compares argument strings, not meaning. A very broad toolPatterns (e.g. '*') may false-positive on legitimately varied tool use.
  • Interrupts are in-process: counters restart from zero after a dsh restart or session resume.
  • Subagents are counted independently (keyed per agent); parent and child loops are never merged.

License

MIT — see LICENSE.