DeepSeek Harness Hub
← 返回列表

pricklywiggles/dsh-circuit-breaker

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

一个用于 DeepSeek Harness 的循环防护器。

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/2 · 已提供中文文档

DeepSeek Harness 的循环防护:拒绝重复的相同工具调用,并限制每个代理的调用次数,位于模型之外,指令无法触及之处

综合分
28.9
GitHub 分
28.9
用户评分
★ Stars
1
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add pricklywiggles/dsh-circuit-breaker
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/19(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-tools@deepseek-ai/schemastery
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-circuit-breaker

一个用于 DeepSeek Harness 的循环防护器。
当智能体已经多次发出完全相同的工具调用后,它会拒绝该调用,并限制单个智能体可以发起的调用总数。该检查在代码中运行,位于模型之外。

为什么会有这个项目

我在本地运行 Qwen3.8-27B,作为 DeepSeek Harness 盒子背后的智能体。它的两个子智能体在同一个下午都陷入了退化性重复。以下是会话记录显示的内容。

| | 智能体 A | 智能体 B |
|---|---|---|
| 运行时长 | 33 分钟 | 68 分钟,直到我把它杀掉 |
| 工具调用 | 626 | 1,227 |
| 搜索次数 | 1,200,其中 74 次不同 | 1,043 |
| 最严重的重复 | 一个查询 555 次 | 一个查询 1,000 次 |
| 失败的调用 | 无 | 无 |

每一次调用都成功了。搜索每次都返回了结果。没有任何东西报错,所以没有任何告警,而我之所以能发现其中任何一个,唯一的原因只是我碰巧看了一眼。两者都在第 11 到 28 步左右开始重复,所以这不是上下文耗尽,而且两者都没有自行恢复。

让我下定决心写这个的部分是:智能体 B 已经读过一份简报,上面告诉它“预算:12 次 web_search 调用”以及“绝不要重新发出你已经运行过的查询”。然后它进行了 1,043 次搜索。告诉模型停下来是没用的,因为处于这种状态的模型在任何有意义的层面上都已经不再遵循指令了。重复发生在指令所能触及的层面之下。

所以防护器必须是绝不询问模型意见的代码。

为什么会发生这种情况,以及为什么这不仅仅是 Qwen 的问题

Qwen3.8 是我的触发因素,而厂商将这种情况视为一种已知故障。该模型卡自己的非思考预设设置了 presence_penalty: 1.5 来抑制重复,并指出将其推得更高会带来语言混杂的代价。该架构给了它一个理由。Qwen3.8 每有一个完整注意力层,就运行三个 Gated DeltaNet 线性注意力层,而线性层将历史压缩进一个小型循环状态,而不是对每一个过去的 token 进行注意力计算。当该状态发生漂移时,模型可能会锁定在自己最近的输出上。llama.cpp 正是在这条路径上发布过一个真实的算术 bug(key_gdiff 修复,PR #19324,2026 年 2 月合并),其症状是循环和输出退化,并且随着上下文越深而越严重。我的构建晚于该修复,所以它不是我的原因,但它展示了这个问题的形态。在这个模型家族中,引擎里的一个数值失误会以循环的形式浮现出来。

更深层的原因根本不是 Qwen 特有的。重复是一个自我强化的吸引子。一个序列每重复一次,再次重复它的概率就会上升,而关于这一点的已发表研究发现在加入采样随机性以及改变提示词的情况下,这种状态依然保持。这就是那个最让我惊讶的部分背后的机制。告诉一个正在循环的智能体它正在循环,会把它直接送回循环之中。该指令落在一个已经被该模式主导的上下文里。
有两件事让 agent 循环比大多数人见过的聊天重复更糟糕。
重复的单元是一整个工具调用,而不是一个词,所以 llama.cpp 的反重复
惩罚永远抓不到它。那个惩罚只扫描最近 --repeat-last-n 个 token,
默认是 64 个,而两次相同的工具调用相隔数千个 token。而且
一个陷入循环的 agent 会持续执行真实操作,所以它会消耗时间和一个模型槽位,
而你写下的每一道提示词层面的护栏都被它绕过去了。

这些都不是 Qwen 独有的问题。任何被当作 agent 驱动的本地模型都可能落入
同一个吸引子,主流托管模型也无法幸免。这个护栏作用于工具调用的
模式,而不是模型的任何特性,所以无论你在背后运行什么,它都能起到同样的作用。
Qwen 只是促使我写下它的原因。

它的作用

该插件通过 ctx.tools.guard() 注册一个护栏,DSH 会在
每次工具执行前运行它。返回一个字符串即拒绝该调用,并将该字符串
交还给模型。在 DSH 中,拒绝是单调的,所以下游任何东西都无法
重新允许被护栏拒绝的调用。

它在以下两种条件之一满足时拒绝:

- 同一个工具已经以相同的显著参数运行过
duplicateLimit 次,位于每个 agent 的滑动窗口内。
- 该 agent 的总调用次数已超过 maxCallsPerAgent,这能抓住那些
通过变化参数足以溜过重复检测的循环。

拒绝文本会解释发生了什么,并告诉模型停止或改变
方法,所以一个正常工作的模型可以恢复,而且整个交互会进入
记录,供人事后阅读。

这里有一个真实的例子。我让一个 agent 运行 echo cbprobe4 十次。第
六次调用被拒绝了。它尝试了 bash -c 和 sh -c 变体,然后停下来
说:

每一步执行的都是未改变的 echo cbprobe4。如果你需要全部 10 次都是
逐字节相同的工具调用,那在这个会话中是不可能的,因为
断路器。

这正是我想要的行为。它停了下来,并且告诉了用户原因。

安装

dsh plugin --profile web add github:pricklywiggles/dsh-circuit-breaker

之后重启该 profile。

该包以纯 ESM 形式发布,无需构建步骤。这比听起来更重要。DSH
自己的文档警告说,一个需要构建的、从 GitHub 安装的插件,还需要其用户
在 pnpm-workspace.yaml 中添加一个 allowBuilds 条目,
这会授予该包在安装时执行代码的权限。而这个插件什么都不要求。

如果你想确切知道自己运行的是什么,可以固定到一个提交:

dsh plugin --profile web add github:pricklywiggles/dsh-circuit-breaker#

配置

每个设置都有一个可用的默认值。要更改某个设置,请在 cordis 补丁层中
定位该插件的行 id,可以是你的 profile 的 cordis.patch.yml,也可以是
$DSH_HOME/cordis.patch.yml:

- id: circuit-breaker
config:
duplicateLimit: 6
maxCallsPerAgent: 300
incidentLog: /workspace/.circuit-breaker-incidents.jsonl
settings.yaml 中的 circuit-breaker: 部分不起作用,我测试过了。那个命名空间到达的是那些自己读取设置的插件,而不是一个 bundle 插件的配置。补丁层覆盖才是有效的路径。

| 键 | 默认值 | 作用 |
|---|---|---|
| enabled | true | 总开关 |
| duplicateLimit | 6 | 在窗口内出现这么多次完全相同的调用后拒绝 |
| window | 200 | 每个 agent 记住多少条最近的调用 |
| maxCallsPerAgent | 300 | 每个 agent 对象的生命周期调用上限;一旦达到,该 agent 将被永久停止。为无人值守的子 agent 设定,它们每次运行都会获得新的上限。0 表示禁用 |
| exempt | todo_write、ask_user_question、exit_plan_mode | 守卫忽略的工具 |
| only | [] | 如果设置,守卫只针对这些工具。覆盖 exempt |
| ignoreArgs | description、explanation、reason、thought、purpose | 比较两次调用时排除的参数名 |
| denyMessage | 见源码 | 拒绝文本。支持 {tool}、{count}、{limit} |
| incidentLog | ""(关闭) | 仅追加的 JSONL,记录每个 agent 每种类型的首次拒绝,以便监督者能注意到被触发的 agent。见下文 |

选择 duplicateLimit

默认值故意远高于正常行为。重新读取一个文件或重新列出一个目录几次是普通工作,不应受到惩罚。运行同一个搜索 555 次则不是普通工作。设为 6 时,真正的循环会在几秒内终止,而健康的 agent 永远不会注意到该插件已安装。

如果你想要更严格的控制,并且能接受偶尔的误报,就调低它。如果你的 agent 确实会轮询某些东西,就调高它。

我在构建这个时弄错的事情

第一个版本从未触发。 守卫在我的测试的全部十次调用上都被调用,却没有拒绝任何一次。DSH 的 bash 工具在 command 旁边接受一个自由文本的 description 参数,而模型每次都会重写它:“Run probe step 1”、“step 2”,等等。十条字节完全相同的命令产生了十个不同的比较键。

ignoreArgs 就是因此存在的。它在比较之前剥离仅用于注释的参数。如果你的工具接受类似的字段,就把它加入列表,否则断路器会坐在那里什么都不做。我的任何单元测试都不会发现这一点,因为测试夹具是我自己写的,而我的夹具没有对自己的参数撒谎。

被拒绝的调用不会被计数。 一个从未运行的调用绝不能把自己的计数推高,也不能把一条真实条目从窗口中挤出去。弄错这一点会让断路器一旦触发就永久锁死。

工作原理

状态是每个 agent 一个由最近调用键组成的有界环形缓冲区,而不是一个持续累加的计数。不需要检测轮次边界,内存不会无限增长,而来自更早时间的合法重复会逐渐过期,而不是计入未来的拒绝。

键在序列化之前会对对象属性排序,因此参数顺序永远不会改变结果。无法序列化的参数会被放行。守卫
无法评判它们,而故障开放胜过阻断真正的工作。

计数器存放在以 agent 对象为键的 WeakMap 中。子代理的状态随子代理一起消失,并行批次中的代理之间也永远不会相互干扰。

捆绑的补丁将守卫挂载在宿主平面上,因此它覆盖后台子代理。正是这些子代理在无人值守的情况下运行足够长的时间以至于陷入循环,我的两个子代理就是这样存活了半个小时。如果你希望它只作用于单个代理,请通过代理自身的上下文来注册它。

拒绝不等于终止

守卫会拒绝调用;它无法终止代理,因为 DSH 的守卫 API 没有中止钩子。这会把结果分成两类:

- 仍能读取信息的模型会看到拒绝,停下来并报告。它的完成结果会通过正常渠道到达启动它的那一方。duplicateLimit 层级会提前触发(六次重复),正是因为那是最有可能仍能联系上模型的窗口。
- 处于真正退化性重复中的模型会无视拒绝,就像它无视自己的简报一样。关于重复吸引子的研究与我所看到的情况相符:上下文中的重复模式会自我强化,并且它会持续存在于添加的随机性以及更改的提示词之中。这样的代理会不断发出相同的调用,并永远收集拒绝。它现在是无害的,每次调用在执行前都被拒绝,但它永远不会完成,因此永远不会向其父级返回任何内容,而在单槽模型服务器上,它仍会争抢推理资源,直到有外部因素将其停止。

incidentLog 就是为第二种情况而存在的。在上面的补丁层中将其设置为一个可写路径。对于每个代理每种类型的首次拒绝,插件会追加一行 JSON:

{"time":"2026-09-01T23:10:07Z","kind":"cap","tool":"bash","count":300,"limit":300,"agent":"agent-007"}

agent 是代理的 uuid。已在 dsh 0.1.1-rc.2 上实时验证:它与 list_agents 报告以及 interrupt_agent 接受的 id 相同,并且它也是代理会话目录的名称,因此一次事件可以直接映射到监督者已经拥有的工具上。如果未来的 dsh 改变了代理对象的形状,并且没有识别出任何 id 字段,那么该条目会改为携带 agentKeys,以便你自己进行映射。写入是仅追加的并且故障开放:错误的路径永远不会影响守卫。

这所启用的监督模式,被构建此功能的机器上的研究技能所使用:已启动子代理的父级会在其每一轮中读取事件文件。一个已触发但几分钟内未交付的子代理会收到 interrupt_agent,其工作会被重新分派一次,并告知替代者该重复的调查路线已耗尽。同一工作项第二次触发意味着该工作项本身很可能无法满足(在我的循环中,是一个幻觉前提),因此停止重试并记录该缺口。

限制

这限制了损害。它并不能修复模型。循环仍然意味着
agent 在任务中失败了。变化在于,失败是快速且可见的,而不是缓慢且无声的。

一个每次都会改变其参数的循环将绕过重复检测,只有调用上限才能阻止它。DSH 防护是同步的,因此防护路径除了可选的一行事件追加之外不进行任何 I/O,并且不跨进程保留任何状态。

许可证

MIT

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群