← Back to home@zhourenke

dsh-tool-call-limit

No description

Stars
1
Language
JavaScript
Created
Sep 2, 2026
Updated
Sep 12, 2026
GitHub repo

Introduction

English | 中文

@zhourenke/dsh-tool-call-limit

给 DSH 的每个 step 加上工具调用配额:同一个 step 内超过配额的调用被直接拒绝。

DSH 的 Agent 在一个 step 里可能并行发出多个工具调用,也可能在同一个工具上反复重试。本插件在工具真正执行之前按注册名检查配额:还有名额就放行,超了就拒绝——只限流,不改工具本身的行为。装好即用,无需改动 DSH 源码。

它解决什么问题

  • 同一个 step 里反复调用同一个工具:给 web_search: 1 之后,一个 step 内第二次调用会被拒绝,而不是让 Agent 继续消耗
  • 想彻底禁用某个工具:配 0,该 step 内所有调用都被拒绝
  • 并行调用不会超额:配额在调用前同步预占,两个并行的 web_search 只有一个能通过
  • 父子 Agent 各自独立:子 Agent 有自己的配额,不会吃掉父 Agent 的额度
  • 随时可卸:作为 profile 层插入,不修改 DSH 本体

安装

dsh plugin --profile web add "github:zhourenke/dsh-tool-call-limit"

安装后必须重启 DSH——bundle 集合是进程启动时的快照,重启前新插件不会被加载,刷新页面无效。

卸载:

dsh plugin --profile web remove @zhourenke/dsh-tool-call-limit

快速上手

本插件默认不限制任何工具(limits: {})。要启用限制,在 profile 补丁里覆盖插件那一行:

# ~/.dsh/profiles/web/cordis.patch.yml
- id: tool-call-limit
  name: '@zhourenke/dsh-tool-call-limit'
  config:
    limits:
      web_search: 1

上面的配置表示:同一个 Agent 在同一个 step 里最多调用一次 web_search;下一个 step 重新获得配额,其他 Agent 有各自的配额。

没有写进 limits 的工具完全不受限——web_fetch 之所以不限,只是因为它没被列出来;需要时单独配它即可。

改配置保存即生效,不需要重启 DSH。 profile 补丁是热重载的;只有安装或卸载插件才需要重启。这两件事经常被混为一谈,结论正好相反。

确认配置已被加载:

dsh --profile web --dump-config

在输出里能看到 tool-call-limit 与预期的 limits 即已生效。

⚠️ 配置必须用 - id: 覆盖的写法,不要写成 - insert:。 插件自带的 bundle patch 已经用 insert 把这一行插进去了;在 profile 里再 insert 一次不报错,而是多出一个同 id 的实例——插件跑两遍、配额算两遍。两者的区别只在动作:- id: 按 id 查表覆盖已有条目,- insert: 无条件追加。

  • name 可以省略(覆盖只看 id);但一旦写了就必须与上面完全一致,写错一个字母会静默不生效,只在日志里留一行 warning。
  • config: 是整体替换,不是逐字段合并。 本插件的 config 只有 limits 一个字段,写全即可。

配置

字段类型默认说明
limitsobject{}工具注册名 → 该工具每个 step 允许的最大调用次数。未列出的工具不受限。

取值规则:

  • 0 表示在该 step 内拒绝该工具的所有调用;
  • 正整数表示每个 step 允许的最大调用次数;
  • 负数、小数、字符串、NaN、Infinity、超出 JavaScript 安全整数范围的数字、数组都会被拒绝;
  • limits: null、省略 limits、省略整个 config 都按 {} 处理(即不限);
  • 不支持 * 通配符,必须逐个工具名配置;
  • 不认识的配置字段会被拒绝,不会静默忽略。

例如:

limits:
  web_search: 1
  grep: 8
  write: 0

计数范围:Agent × turn × step × 工具名

配额按四个维度分别计算,任一维度不同就是一份独立的配额:

维度说明
Agent父 Agent 与 subagent 创建的子 Agent 是不同的 live Agent 对象,各自独立计数,不会合并成一个总预算
turn一个 turn 内的多个 step 各自计数
step配额的重置单位——进入新 step 时计数清零,重新获得全部名额
工具名每个工具名单独计数,web_search 的调用不占用 web_fetch 或 grep 的配额

配额怎么消耗

  • 调用前同步预占:名额在调用 next() 之前就扣掉,所以同一个 step 里的并行调用不会同时看到同一个剩余名额。web_search: 1 时,两个并行的 web_search 只有一个能继续进入后续管线。
  • 通过即消耗,不退还:调用通过限制器后立刻消耗一个名额。之后即使工具失败、被取消、超时,或被后续的其他策略拒绝,也不会退还。
  • 超限的调用不再消耗:已经被拒绝的调用不会继续扣名额。

被拒绝时会看到什么

拒绝使用三条稳定的英文原因文本:

tool <name> exceeded its per-step limit of <n>
per-step tool limit requires an agent context
per-step tool limit has no active agent step

第一条是配额用尽;后两条是缺少 Agent 上下文或该 Agent 没有有效的 step,此时采取 fail-closed(拒绝而非放行)。读到第一条时不要重试同一个工具——配额要到下一个 step 才会恢复。

给 Agent 的要点

  • 本插件没有提供任何工具、也没有模型可见的接口,对模型完全透明:它约束的是你本来就要调用的那些 DSH 工具
  • 被拒绝时表现为工具调用失败(返回上面三条原因之一),而不是静默变慢
  • 同一个 step 内不要对同一个工具重复重试,配额不会在中途恢复
  • 配置里用的必须是 DSH 的注册名,如 web_search、web_fetch、grep、write、bash、run_code

它管不到什么(限制边界)

本插件限制的是进入 DSH ToolRuntime 的调用次数,不是工具实现内部发生的操作次数。它不会限制:

  • 一次 web_search 调用内部发出的多个 query;
  • Web provider 内部的 HTTP 请求或原生 server-tool uses;
  • 一次 bash 调用内部执行的多条 shell 命令;
  • MCP 或其他自定义工具内部自行发起的多个 API 请求。

要限制单次 web_search 的 query 数量,需要另外配置 Web 工具的 searchMaxQueries;provider 提供 maxUses 时也要单独配置。它们与本插件的 ToolRuntime 调用配额属于不同层级。

maxParallelToolCalls 管的是并发数量,不是每 step 的总调用次数——本插件负责后者,两者互补。

已知限制(实测确认)

  • 按进程独立计数:状态保存在当前 DSH 进程内存中,不写入 session transcript,也不在进程重启或多个 DSH 实例之间共享。
  • 父子 Agent 不合并预算:需要在整体上限制父子 Agent 的合计调用数时,本插件不提供这个能力。
  • 只限入口,不限内部:见上一节的边界说明。
  • 工具名写错不会报错:配置里写一个不存在的工具名不会触发任何校验错误,只是那条规则永远不生效(因为没有任何调用会用它匹配)。工具名必须与 DSH 注册名逐字一致。

兼容性

在 DSH v0.1.5-rc.1(2026-09)下测试通过。

许可证

MIT