snailium/dsh-repeat-tool-breaker0

dsh-repeat-tool-breaker

Hard break on repeated identical tool calls: a synchronous ctx.tools.guard monotonic gate that denies the 2nd identical (tool name + canonical arguments) call per agent, with an optional same-path read guard and advisory first-repeat warning.

包名
dsh-repeat-tool-breaker
版本
0.1.2
许可证
MIT
最近更新
2026年9月11日

安装

此插件尚未提供可验证的 bundle,或兼容性检查未通过。请先阅读仓库说明。 阅读完整 README ↗

Configuration

Mount via a --patch overlay or a profile's cordis.patch.yml. The plugin exports an object form ({ name, inject: ['tools'], apply }); inject: ['tools'] defers apply until the real ToolRuntime service is live, at which point ctx.tools.guard is the genuine method.

- insert:
    - id: repeat-tool-breaker
      name: /ABSOLUTE/PATH/dsh-repeat-tool-breaker/index.js
      config:
        denyAfter: 2          # identical (tool + canonical args) call #2 is denied (>=2)
        warnAfter: 2          # advisory tier; inert unless 2 <= warnAfter < denyAfter
        registerAdvisory: true
        exclude: [todo_write] # never count/reset these tools (include/exclude are *-wildcards)
        include: []           # non-empty = ONLY these tools are tracked
        readTools: [read, Read, read_file, read-file, file-read, fs-read]
        matchReadBySubstring: true
        pathAliases: [path, filePath, file, target_file]
        maxSamePath: 3
        previewChars: 400
        resultPreviewChars: 800

(The - insert: list is required to add a new plugin; a flat - id: entry is a reconfig of an already-present id and fails with "entry not found" for a plugin that isn't yet in the composed tree.)

Here name is an absolute POSIX path to this directory's index.js (dev/overlay loop). When the package is installed into a profile it can instead be the package specifier dsh-repeat-tool-breaker.

About the advisory tier

The warnAfter notice is deliberately inert unless 2 <= warnAfter < denyAfter:

  • warnAfter must be at least 2, because a notice only makes sense once a repeat has actually happened;
  • it must be below denyAfter, because at denyAfter the call is blocked and the deny reason already explains why.

With the default denyAfter: 2 the gate blocks on the very first repeat, so there is no room for a separate pre-block nudge and nothing is emitted. Set denyAfter: 3, warnAfter: 2 to get one warning after the first repeat and the block on the second.

This matters: an advisory keyed only on count === warnAfter would fire on every ordinary tool call (every fresh call starts a new run at count 1), attaching a misleading "you repeated this" message to each one.