🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

文本死循环断路器 GRUST303/dsh-loop-guard

DeepSeek 客户端
GRUST30332 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

检测单条消息内的重复刷屏并注入破环提醒

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/GRUST303/dsh-loop-guard.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 32 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 13 处,展示前 13 条)
  • 读取本地文件
    density-study.mjs:22
    for (const n of readdirSync(dir)) {
  • 读取本地文件
    density-study.mjs:23
    const p = join(dir, n); const st = statSync(p)
  • 读取本地文件
    density-study.mjs:49
    try { r = decompressFrames(readFileSync(p)) } catch { continue }
  • 读取本地文件
    verify-polluted.mjs:24
    for (const n of readdirSync(dir)) {
  • 读取本地文件
    verify-polluted.mjs:25
    const p = join(dir, n); const st = statSync(p)
  • 读取本地文件
    verify-real.mjs:23
    for (const n of readdirSync(dir)) {
  • 读取本地文件
    verify-real.mjs:24
    const p = join(dir, n); const st = statSync(p)
  • 读取本地文件
    verify-real.mjs:69
    try { r = decompressFrames(readFileSync(p)) } catch { continue }
  • 读取本地文件
    verify-static.mjs:16
    const hostSrc = readFileSync(hostPath, 'utf8')
  • 读取本地文件
    verify-static.mjs:51
    const pkg = JSON.parse(readFileSync(`${dir}/package.json`, 'utf8'))
  • 读取本地文件
    verify-static.mjs:64
    check('文件存在', existsSync(clientPath))
  • 读取本地文件
    verify-static.mjs:65
    const clientSrc = readFileSync(clientPath, 'utf8')
  • 读取本地文件
    zstd-frames.mjs:35
    const buf = readFileSync(file)
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 检测单条消息重复刷屏
  • 注入破环提醒
  • 打断纯文本重复退化

DSH 插件:检测并打断智能体输出中纯文本重复退化(generation degeneration)。7917 条真实消息实测,误报率 0.03%。

README

由 DeepSeek 最新模型翻译生成
dsh-loop-guard DeepSeek Harness (DSH) 插件:纯文本死循环断路器。 检测单条模型消息内部的重复退化(degeneration / 重复吸引子),并在命中阈值时向下一步注入破环提醒,阻止 agent 循环无限空转。 起因是一台真实机器上的故障:deepseek-v4.1-flash 在 thinking 模式下反复吐「好。写。执行。」,一个 tool call 都不产生,循环两小时无人察觉。 它解决什么问题 这个故障由四个因素叠加造成,与上下文长度无关: 1. thinking 默认开启、effort 偏高 —— CoT 体量最大,重复轨迹最长。 2. tools 场景下 reasoning_content 全量回传 —— DeepSeek 官方要求:请求带 tools 时,历史所有轮次的 reasoning_content 必须完整传回并拼进上下文。 于是重复轨迹每一轮都被重新强化一次,形成自增强闭环。 3. 有损 KV Cache 压缩在放大它 —— 该模型的核心卖点就是 KV cache 压缩。 它把低熵重复内容压得几乎无损,却先抹掉稀疏的、带区分度的边缘信号 ——而那是唯一能打破循环的东西。 4. 上下文压缩永远等不到触发条件 —— 循环每轮只吐极少 token,上下文几乎不增长,compaction 全程旁观。
展开完整文档(README 剩余 1 节)
dsh-loop-guard

DeepSeek Harness (DSH) 插件:纯文本死循环断路器。

检测单条模型消息内部的重复退化(degeneration / 重复吸引子),并在命中阈值时向下一步注入破环提醒,阻止 agent 循环无限空转。

起因是一台真实机器上的故障:deepseek-v4.1-flash 在 thinking 模式下反复吐「好。写。执行。」,一个 tool call 都不产生,循环两小时无人察觉。

它解决什么问题

这个故障由四个因素叠加造成,与上下文长度无关:

1. thinking 默认开启、effort 偏高 —— CoT 体量最大,重复轨迹最长。
2. tools 场景下 reasoning_content 全量回传 —— DeepSeek 官方要求:请求带 tools 时,历史所有轮次的 reasoning_content 必须完整传回并拼进上下文。
于是重复轨迹每一轮都被重新强化一次,形成自增强闭环。
3. 有损 KV Cache 压缩在放大它 —— 该模型的核心卖点就是 KV cache 压缩。
它把低熵重复内容压得几乎无损,却先抹掉稀疏的、带区分度的边缘信号 ——而那是唯一能打破循环的东西。
4. 上下文压缩永远等不到触发条件 —— 循环每轮只吐极少 token,上下文几乎不增长,compaction 全程旁观。这就是「明明是死循环却从不触发上下文压缩」的原因:compaction 针对输入总量,而它在因果链之外。

为什么调采样参数没用

DeepSeek 官方文档明确写着:thinking 模式下 不支持 temperature / presence_penalty / frequency_penalty(设了不报错但完全无效),top_p 的下限被钉死在 0.95。指望靠采样多样性破环,方向就是错的。

关键认知:循环发生在单条消息内部

这是理解这个故障的分水岭。基于 7917 条真实会话消息的实测:

| 观测 | 结果 |
| 跨消息「相邻输出严格相同」 | 0 次(全部会话) |
| 单条消息内部单行重复最大值 | 1729 次(占比 97.8%) |
| 正常消息的单行重复 p99 | 3 |

结论:循环是单条 assistant 消息内部的刷屏,不是跨消息重复。 任何"比较前后两条消息是否相同"的守卫都够不着它。

循环有两种形态

它们外观完全不同,只覆盖第一种的判据会完全失效:

| 形态 | 例子 | 单行最多重复 | 单行占比 |
| 单行刷屏 | "</parameter> ×1729 | 1729 | 98% |
| 多标记交替滚动 | (输出)(生成)(工具调用)(好。) | 136 | 仅 21.7% |

第二种是致命的:最常重复的行只占整条消息的 21.7%,被"占比 ≥ 50%"的门槛挡在外面, 整个判据失效。所以主判据必须是密度类指标。

主判据:窗口重复密度

滑动窗口(默认 12 行)内「出现次数 >1 的行」占比的均值。

| 内容 | 密度 | 说明 |
| 普通文档 / 代码 | 0.0% | p50~p80 都是 0 |
| 含分隔线的 markdown 表格 | 15.0% | 结构性重复的合理上限 |
| 多标记交替滚动(第二种形态) | 59.7% | 真循环 |
| 单行刷屏(第一种形态) | 97.5% | 真循环 |

真实分布:p90 = 0.3%、p95 = 7.6%、p99 = 17.7%、p100 = 97.5%。

平衡档取 35%:落在正常上限(17.7%)与真循环(60%+)之间的安全空档。
全量 7917 条真实消息实测命中 2 条(0.03%),且命中的全是真循环。

三道防线

挂在 agent/pre-step(与 DSH 原生 guard 同一挂点)。

1. 当前消息内部退化 —— 密度超阈值,或单行极端重复(次数达标 且 占比 ≥ 50%) → 注入破环提醒,带上密度、重复次数与重复行原文。对上述两种形态都有效。
2. 上下文已被污染 —— 全部历史聚合后单行重复 ≥ 20 次 且 该行占比 ≥ 30% → 每会话注入一次强化提醒。
这是「旧会话装了插件仍循环」的对策:污染在装插件之前就写进历史了。
3. 跨消息重复 —— 连续输出同一文本达到阈值的整数倍。

安装

注意:dsh plugin --profile <name> add <target> 实际是转调 pnpm,它只把包写进 dependencies,不会把包名加进 dsh.profile.bundles。而 bundle 层才是决定插件是否加载的地方。下面的第 2 步不能省,否则装完不生效。

从 GitHub 安装

1) 取到本地并安装为 profile 依赖
git clone https://github.com/GRUST303/dsh-loop-guard.git
dsh plugin --profile <你的 profile> add ./dsh-loop-guard

或直接作为 git 依赖(同样是 pnpm add)
dsh plugin --profile <你的 profile> add github:GRUST303/dsh-loop-guard

2) 把 dsh-loop-guard 加进 profile 的 package.json:

{
"dependencies": { "dsh-loop-guard": "link:./dsh-loop-guard" },
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-loop-guard" // ← 必须在这一行 ] } } }

3) 在 profile 目录 pnpm install,然后完整重启 profile。

bundle 层在启动时被读取并合成 cordis.yml,所以必须重启,HMR 不够。

如果你有 DSH 的插件管理器工具

官方推荐路径是用 plugin_manager 的 install_bundle,它会自己处理安装和 bundle 选择,不用手工改 package.json:

plugin_manager({ action: "install_bundle", target: "<到 dsh-loop-guard 的绝对路径>" })

依赖(可选)

@deepseek-ai/schemastery(官方包)用来声明 Config。

- pnpm 正常安装(pnpm add <npm 包>、git 依赖)会自动拉取它,无需额外操作。
- 用 link:/path 或本地目录方式安装时,link 目标不会自动获得 dependencies。
此时若缺它,插件仍然正常工作(核心防循环完全不依赖它),只是设置页不可用。
需要设置页就在插件目录先跑一次 pnpm install。

插件对缺依赖做了降级:缺 schemastery 时 Config 为 undefined,cordis 会跳过 schema 校验,而不是让整个 profile 起不来。

为什么必须用 schemastery 而不能用裸 JS 对象:cordis 的 resolveConfig() 要求 Config 是 Standard Schema(读 Config["~standard"].validate);裸对象会抛 Cannot read properties of undefined (reading 'validate') 并让整个 profile 起不来。

配置

配置通过 profile 插件行的 config 段提供:

- id: loop-guard
name: dsh-loop-guard
config:
preset: balanced # conservative | balanced | aggressive
repeatDensity: 0.35 # 主判据:窗口重复密度阈值 window: 12 # 滑动窗口行数 lineRepeatThreshold: 6 # 单行重复次数(需同时满足占比 ≥ 50%) contextLineRepeatThreshold: 20 # 历史污染阈值 minChars: 120 # 短于此长度的消息不参与判定 crossStepThreshold: 3 # 跨步重复次数 debug: false # 写入调试日志

改完重启 profile 生效。

档位对照

| 档位 | 密度阈值 | 适用场景 |
| 保守 | 0.5 | 误报率最低。适合大量输出表格 / 代码的场景,宁可漏报也不打断正常输出 |
| 平衡(默认) | 0.35 | 7917 条真实消息实测误报率 0.03%,不漏任何一种已观察到的循环形态 |
| 激进 | 0.25 | 最早介入,轻微重复也会提醒。适合长任务 / 无人值守,代价是提醒更频繁 |

关于图形设置页

插件声明了官方 Config(schemastery schema),DSH 的 Settings 服务会据此派生配置项;客户端也注册了设置页(settings.plugins.tab 槽位)。

在实测的 DSH 0.1.7-rc.2 上,该设置页没有被渲染出来。 已确认的部分:插件出现在 Web 端 boot manifest 里、dsh.client 声明正确、client bundle 可正常加载(HTTP 200)。但 tab 不出现,根因未定位。

这不影响核心功能 —— 防循环完全由 host 端承担,与设置页无关。
请使用上面的 config 段配置。如果你定位到了原因,欢迎提 PR。

验证

仓库自带验证脚本。它们默认从 DSH_HOME 读取会话数据,先设置该环境变量:

export DSH_HOME=/path/to/harness # PowerShell: $env:DSH_HOME="..."

node verify-static.mjs # 43 项接入契约(Config 是 Standard Schema、槽位名、前后端档位一致性…)
node verify-real.mjs # 真实循环形态召回 + 全量消息误报率
node verify-chain.mjs # 跨步判据与自引自环防护
node verify-polluted.mjs # 历史污染场景
node verify.mjs # 激活路径与事件契约
node density-study.mjs # 在真实数据上重算密度分布与阈值

不需要 DSH 环境的纯离线脚本:verify-static.mjs、verify-chain.mjs、verify.mjs。

实现说明(给二次开发者)

1. 不要导出裸对象的 Config。 cordis 的 resolveConfig() 要求 Config 是 Standard Schema(读
Config["~standard"].validate);裸对象会抛
Cannot read properties of undefined (reading 'validate') 并让整个 profile 起不来。默认值与校验全部由本文件自己承担(fail-loud)。

2. 不要在 apply 里 await ctx.inject([...])。
ctx.inject() 返回 fiber,依赖不可用时 await 永不 settle —— 这会让整个插件
永久挂起,连核心注册都不执行。可选增强必须用「同步探测 + 异步兜底」,且绝不阻塞核心路径。

3. repeatStats 的空行过滤不能省。 空行天然"重复",一旦计入窗口统计会让密度虚高 —— 实测这一个疏漏把误报率从 0.03% 抬到 12.5%。

4. 主动过滤自己注入的提醒。 提醒本身是 user role,不过滤掉它就会被当成链上的下一个「重复项」,把守卫变成自我引爆器。

5. DSH 会话是「每行一个独立 zstd 帧」。 session.v4.jsonl.zstd 不是单个 zstd 流。单次 zstdDecompressSync 只返回第一帧(一个 2.6MB 的会话因此只解出 189 字节)。用 zstd-frames.mjs 逐帧解压。

6. 测试时必须给每个样本独立的 agent 对象。 复用同一个 agent 会把互不相干的样本累计成一条假的「跨步重复链」,制造假误报。

7. 设置页槽位用 settings.plugins.tab。 官方 dsh-client-ui-settings-agent-loop 用的是 plugins.item,但在 DSH 0.1.7-rc.2 上没有任何宿主消费该槽位(全量搜索 0 命中),注册进去不会显示。

设计边界(刻意为之)

- 不改写任何 LLM 请求参数。 降 temperature / reasoning_effort 这类动作要么被 thinking 模式静默忽略,要么会打掉 KV Cache 的前缀复用,得不偿失。
- 不 veto、不 block、不重置会话。 上游若用 reject 拒绝这一步,原样透传。
- 按 agent 隔离状态(WeakMap / WeakSet),agent 回收后自动清理。
- 任何可选增强失败都不得影响核心功能 —— 这是硬约束,不是偏好。

判据演进史

洗手记录,避免重蹈(每一版都被真实数据推翻过):

| 版本 | 判据 | 结论 |
| v1 | 跨消息相等 | 全错。实测相邻输出严格相同的次数为 0 |
| v2 | 单行次数 + 该行占比 | 过拟合。第二种形态的占比只有 21.7%,被 50% 门槛挡住 |
| v3 | 窗口重复密度(当前) | 同时覆盖两种形态,正常内容上限 15% |

与 dsh-purge 的关系

dsh-purge 的补丁 #18(REPEAT_TOOL_REMINDER_DISABLED)会把 DSH 原生的 dsh-repeat-tool-reminder 整个拆掉(tools/post-execute 与 agent/pre-step 两个监听都被移除,注释写着 "The guard is fully inert.")。

原生守卫处理的是「重复调用同一工具」,本插件处理的是「单条消息内部的纯文本重复」——后者在原生体系里本就没有覆盖。两者监听同一挂点但互不冲突。

本插件不依赖 dsh-purge。 它的配置路径是官方 Config,任何 DSH 0.1.7 环境都能用。

License

MIT
预览分享卡片长什么样 ↓GRUST303/dsh-loop-guard
分享 · DeepSeek Harness Hub
GRUST303/dsh-loop-guard

检测单条消息内的重复刷屏并注入破环提醒

这是你的仓库?认领条目

回到顶部