dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH guard against self-repetition in streamed model output: detects a degeneration loop in reasoning or visible text, aborts the runaway turn and wakes the agent with a corrective notice.
An output self-repetition watchdog for DeepSeek Harness. When a model starts cycling the same short block instead of producing a tool call, the guard aborts that generation and wakes the agent with a corrective notice — no human stop button, and no human typing "continue".
>= 0.2.0-rc.2, ESM, zero runtime dependencies.npm test).DSH ships @deepseek-ai/dsh-repeat-tool-reminder, which only sees duplicate tool calls
(same tool, same arguments, 3/5/8 times in a row). A degeneration loop in the reasoning
channel is invisible to it. In the session that motivated this plugin:
好。→(执行)→好。→Write.→… in its reasoning for 112,109 characters,
emitted in 50,196 stream chunks over 126 seconds of continuous decoding, and produced
zero tool calls — so repeat-tool-reminder could not fire;floor(min(W × 0.8, W − O − B)) ≈ 678k tokens for a 1M window with a 256k output cap,
while the session peaked around 508k tokens;output-loop-guard listens on the live agent/assistant-stream frames and looks at the text itself.
A sliding window over streamed output (1,200 characters by default, inspected every 64 new characters) feeds four signals:
| Signal | Rule | Catches |
|---|---|---|
repeated-block | Shortest-period block (≥2 newlines, ≥2 distinct non-whitespace chars) repeated ≥6 times | Multi-line cycles, e.g. the 106-character 好。→(执行)→Write. cycle |
dominant-gram | One n-gram covers ≥55% of the window with ≥24 occurrences and spans ≥2 lines | Loops whose tail is ragged but where repetition already dominates |
repeated-line | Single-line runs: ≥40 in the reasoning channel, ≥60 in visible text (singleLineTextMinRepeats, 0 disables) | Reasoning spinning on one line, runaway line duplication in answers |
flat-tic | Whitespace-free short gram covering ≥25% of the window with ≥48 occurrences, reasoning only | Line-less tics (好。好。好。…) |
Two design rules keep it precise:
==== / ---- rule lines and blank-line runs never trip the guard.On detection the guard:
agent.cancel({ kind: 'hook', reason: 'output-loop-guard: …' }) — the in-flight
generation is aborted and recorded as interrupted, so the degenerate text stops growing;agent.steer(<notice>) — because the signal is already aborted, steer upgrades to a
fresh turn and wakes the driver, so the agent immediately continues with an explicit
correction (stop restating, emit the next action or finish);maxTripsPerTurn trips in one turn it stops auto-continuing and only injects the
notice, so a genuinely broken model cannot spin forever.The package is a DSH bundle: the loader reads dsh.bundle.patch from package.json
(dsh.plugin.json is optional metadata used by some local plugin indexes — the official runtime
ignores it), and cordis.patch.yml inserts the output-loop-guard row.
# A. from a git host, pinned to a commit (requires `git` on the PATH of the DSH process)
dsh plugin --profile <profile> add "github:ADistanceThereIs/dsh-output-loop-guard#<commit>"
# B. from the release tarball — no git required
# download dsh-output-loop-guard-0.1.0.tgz from the Releases page, then:
dsh plugin --profile <profile> add ./dsh-output-loop-guard-0.1.0.tgz
# C. straight from the release asset URL (needs Node to trust your system CA store)
dsh plugin --profile <profile> add \
https://github.com/ADistanceThereIs/dsh-output-loop-guard/releases/download/v0.1.0/dsh-output-loop-guard-0.1.0.tgz
All three append the package to dsh.profile.bundles and compose the bundle's patch layer.
Restart DSH for the row to activate.
Uninstall:
dsh plugin --profile <profile> remove dsh-output-loop-guard
Override the row in the profile's cordis.patch.yml (a patch replaces the row's whole config,
so restate every key you want to keep):
- id: output-loop-guard
name: dsh-output-loop-guard
config:
dryRun: false # true = detect and log only, never cancel
autoContinue: true # wake the agent with the corrective notice
maxTripsPerTurn: 2 # auto-continue budget per turn
windowChars: 1200
checkEveryChars: 64
minPeriodRepeats: 6 # repeats required for a multi-line cycle
singleLineMinRepeats: 40 # reasoning-channel single-line threshold
singleLineTextMinRepeats: 60 # visible-text single-line threshold (0 = off)
useDominantGram: true
dominantGramCoverage: 0.55
dominantGramMinCount: 24
useFlatTic: true
ticMinCount: 48
ticMinCoverage: 0.25
verbose: false
Start with dryRun: true if you want to observe detections in your own sessions before letting
the guard abort anything.
Replayed over 27 real sessions / 1,783 assistant steps (a corpus containing both healthy and degenerate real output):
End-to-end check in a live DSH runtime, on a task engineered to repeat two lines 300 times. The session log of that run contains:
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"hook","reason":"output-loop-guard: repeated output (repeated-block)"}}}}
{"type":"assistant/message","interrupted":true} // cut off after 9 repetitions, not 300
{"type":"user/message","source":{"kind":"output-loop-guard","form":"notice"}}
The agent then delivered the same content as a file instead of continuing to repeat it, which is exactly the intended outcome: the guard stops runaway output, it does not kill the task.
npm test runs 21 self-contained checks (no corpus needed): the real degeneracy must be aborted
(24,180-character stream caught after 576 characters), healthy long documents must be untouched,
realistic written output (lists, code blocks, tables, rule lines, fixtures) must not trip,
runaway written lines must trip, dryRun must not cancel, a disabled plugin must attach nothing,
and a throwing agent handle must not break the stream.
singleLineTextMinRepeats: 0.repeated-block, though dominant-gram / flat-tic usually covers it.dryRun to observe first.agent/assistant-stream with payload { agent, frame }, where
frame = { type: 'start' | 'chunk' | 'end', attemptId, revision, …, chunk } and chunk is a
StreamChunk (reasoning-delta / text-delta / …).Agent.cancel(cause, options) — the closed cause union is user | parent | disposed | hook;
hook records its reason in turn/end, which is what this guard uses.Agent.steer() is send(message, 'next-step', true); when the active signal is already
aborted, send upgrades the target to next-turn and wakes the driver — hence
cancel(); steer(notice) means "abort this generation, then continue in a new turn".dsh plugin add github:… fails with resolveGit / resolveRef errors.
pnpm resolves git dependencies with the git executable, and DSH inherits the environment of
the process that started it. If you installed Git after starting DSH, the new PATH entry is
not visible to it. Put Git on the PATH of the shell you use (or restart DSH) and retry, e.g.:
$env:PATH = 'C:\Program Files\Git\cmd;' + $env:PATH
dsh plugin --profile <profile> add "github:ADistanceThereIs/dsh-output-loop-guard#<commit>"
Pin an exact commit (or tag) so a later push cannot silently change what runs on your machine.
dsh plugin add https://… fails with fetch failed / unable to verify the first certificate.
Node's fetch uses its bundled CA list, so TLS-inspecting security software (corporate proxies,
some antivirus products) can break it while curl still works. Node 22.15+/24 can use the system
trust store instead:
$env:NODE_OPTIONS = '--use-system-ca'
dsh plugin --profile <profile> add https://github.com/ADistanceThereIs/dsh-output-loop-guard/releases/download/v0.1.0/dsh-output-loop-guard-0.1.0.tgz
Downloading the tarball yourself and adding the local file avoids the issue entirely.
The guard tripped on output you wanted.
Set dryRun: true to keep detection but stop the cancellation, raise the thresholds, or set
singleLineTextMinRepeats: 0 to stop judging single-line repetition in visible text.
The guard seems inactive.
The bundle's row only activates after a DSH restart, and the row must appear in the composed
profile — for a non-application profile you can check it with
dsh --profile <profile> --dump-config | Select-String output-loop-guard.
node test/guard.test.mjs # or: npm test
detector.mjs is dependency-free and importable on its own (dsh-output-loop-guard/detector),
so the detection logic can be reused or calibrated outside DSH.
MIT — see LICENSE.