tool-deadline-guard:为未声明 timeoutMs 的工具调用补上部署级 deadline

前言

在 DeepSeek Harness(dsh)里跑长任务时,常见一种现象:界面上的 turn 一直显示 running,日志和输出里却没有任何报错。模型侧有流式 idle 超时(DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000,由 idleWatchdog 按 chunk 重载),因此「长时间无输出」往往指向工具调用卡住,而不是模型卡住。社区讨论 #7339 描述的就是这类 long-horizon 任务静默挂起。

根因之一在于工具定义:ToolDefinition.timeoutMs 是可选字段,说明为「Cooperative tool-call timeout budget in milliseconds. Omit for no deadline」。核心策略包 @deepseek-ai/dsh-tool-call-timeout-policy 在 timeoutMs === undefined 时直接 return next(),不会挂定时器、不会发信号、也不会写诊断。在 dsh v0.1.6-alpha.2 中,调用 defineTool( 的 40 个非测试源文件里,有 26 个完全没有 timeoutMs,其中包括 tool-subagent、tool-terminal、mcp-resources 以及若干 fs 与工作流相关工具——恰恰是委托子进程、跨网络 MCP 等容易长时间阻塞的路径。

下面介绍社区 Cordis 插件 @argszero/cordis-plugin-tool-deadline-guard(GitHub:argszero/cordis-plugin-tool-deadline-guard),它在 tools/execute 与 tools/pre-execute 瀑布上为「未自行声明 deadline」的调用补上可配置的部署级预算,超时后把静默挂起变成模型可见的结构化错误,便于重试或改策略。

这是什么

@argszero/cordis-plugin-tool-deadline-guard 由维护者 argszero 发布,分类属于 DSH 生态中的工作流向插件(SkillHub 目录:argszero/cordis-plugin-tool-deadline-guard)。它不替换工具自身实现的 timeoutMs,而是在 harness 未武装任何 deadline 时为工具执行体与派发前阶段分别计时;到期则返回插件自有错误码,与核心策略的 TOOL_TIMEOUT 区分开,便于区分「工具声明了预算却超时」与「部署策略补上的预算超时」。

核心功能

执行体阶段(tools/execute)

插件挂在与核心 timeout policy 相同的公开 seam tools/execute 上。对每个 dispatch:

  1. 通过 ctx.tools.get(exec.name, exec.agent) 解析工具定义,决定是否武装 deadline;未知工具仍由 registry 处理(UNKNOWN_TOOL)。
  2. 武装一个 timer 与一个 AbortController,经 exec.signal 向工具提供 AbortSignal.any([caller, own]),与核心 policy 的做法一致。
  3. 在 finally 中恢复调用方 signal,避免后续 wrapper 看到本 mount 可能已 abort 的 signal。
  4. 仅当本 mount 的 timer 先触发时才替换结果;在预算内正常结束则原样返回工具结果,不二次 dispatch。
  5. 超时结果形态为 isError: true,且 error.info.code === 'TOOL_DEADLINE_EXCEEDED'(非核心的 TOOL_TIMEOUT),模型侧可据此行动。README 中的示例如:对 subagent 的调用在超过 600000ms 且工具未声明 timeoutMs 时,返回明确说明 deadline 来源的错误文本。

默认情况下,凡未在工具定义里写 timeoutMs 的调用,会被 bound 在 10 分钟(600000 ms);已声明 timeoutMs 的工具在默认 applyTo: undeclared 下仍交给核心 policy,本插件不改动。

派发前阶段(tools/pre-execute,0.2.0)

timeoutMs 只约束工具体;tools/pre-execute 瀑布在核心 tools/execute wrapper 之前完成。若某 listener 在此阶段 await 无界工作,整个 session 的工具调用都会堵在后面,而只挂在 tools/execute 上的 deadline structurally 看不到该阶段。README 以 @deepseek-ai/dsh-workspace-changes 在 pre-execute 中等待 turn recorder 快照队列为例,并指向讨论 #9515。

0.2.0 起,同一插件也挂载 tools/pre-execute。该阶段超时使用不同错误码 TOOL_PRE_EXECUTE_DEADLINE_EXCEEDED,形态为 deny(调用未被 dispatch),与执行体超时区分:便于判断是「跑太久」还是「根本没跑起来」。

配置项

无配置时:对所有未声明 timeoutMs 的工具使用 10 分钟默认预算,其余调用原样放行。

field default meaning
mode auto auto 武装 deadline;observe 只报告将会 bound 的内容并原样 dispatch;off 不注册
defaultTimeoutMs 600000 未声明 timeout 的工具的预算(10 分钟)
perTool {} 按解析后的工具名覆盖预算,优先于 defaultTimeoutMs
exempt [] 永不 bound 的工具名
applyTo undeclared undeclared 不碰已声明 timeoutMs 的工具;all 对所有已知工具 bound
onExpiry cooperative 到期行为;README 指向 Honest limits 小节
preExecute true 是否 bound 派发前阶段
preExecuteTimeoutMs defaultTimeoutMs 派发前阶段预算
warnLimit 5 每个 mount 发出的诊断条数;0 关闭

预算须为正有限数且 ≤ 2147483647(setTimeout 饱和上限);更大值会立即触发,因此「无 deadline」应通过 exempt 或 mode: off 表达,而非设极大数值。

安装与启用

先在项目中安装 npm 包(版本以 registry 为准;package.json 中当前为 0.2.0):

npm install @argszero/cordis-plugin-tool-deadline-guard

在 cordis.yml 或 profile bundle 中增加一行(也可合并包内自带的 cordis.patch.yml,内容等价):

- insert:
    - id: tool-deadline-guard
      name: '@argszero/cordis-plugin-tool-deadline-guard'

安装后无需额外配置即可生效:未声明 timeoutMs 的工具默认 10 分钟上限。需要更长或更短等待时,在 Cordis 插件配置里调整 defaultTimeoutMs 或 perTool;对必须允许长时间阻塞的工具(例如特定 MCP),将其名加入 exempt。

上线前可用 mode: observe 观察本 mount 将会 bound 哪些调用,确认名单后再切回 auto。

典型用法示例

仅收紧 subagent 与 terminal,其余仍用默认 10 分钟:

在插件实例上配置(字段名以 README 为准):

defaultTimeoutMs: 600000
perTool:
  subagent: 900000
  terminal: 300000
exempt:
  - ask-user

关闭派发前阶段 bound(只 guard 执行体):

preExecute: false

对某第三方 MCP 工具完全豁免:

exempt:
  - mcp_some_server_long_poll

超时后模型会看到类似 README 中的 tool_result [error],其中说明 exceeded 的毫秒数与 deadline 来自 tool-deadline-guard mount,而不是工具自带的 timeoutMs。

适用场景与注意

适合谁: 在生产或长时间自动化任务中使用 dsh,且依赖 subagent、终端、MCP、工作流类工具;希望 turn 不会因单个 wedged 调用无限 running,又需要与核心 TOOL_TIMEOUT 区分运维语义(部署策略 vs 工具自声明预算)的团队。

协作式默认: onExpiry 默认为 cooperative;README 强调插件不会强行抛弃工具本身不愿结束的调用,并在可选 replace 模式下明确说明限制。需要更激进行为时应阅读 README 的 Honest limits。

权限与安全: 插件随当前 dsh 进程权限运行,挂载在 harness 公开 waterfall 上,不单独降权。安装前请阅读 GitHub 仓库 源码与 MIT 许可证,确认与您的合规要求一致。peer 依赖范围在 package 说明中审计为 dsh 0.1.2-rc.1 至 0.2.1-alpha.2 的 npm/pnpm prerelease 读法;与您实际 dsh 版本不匹配时应在测试环境验证。

生态说明: DSH 采用「一切皆插件」的 Cordis 组合方式;SkillHub 等社区目录独立于 DeepSeek / 幻方,并非官方应用商店。本插件星标数为 0(截至目录抓取),选用时以 README 与 issue 跟踪维护状态即可。

结尾

tool-deadline-guard 解决的是 harness 对「未声明 timeoutMs」工具默认不设 deadline 的空白:把静默 wedged 的工具调用转成 TOOL_DEADLINE_EXCEEDED 或 TOOL_PRE_EXECUTE_DEADLINE_EXCEEDED,让模型和运维都能看见原因。目录页:https://www.skillhub.cn/plugins/argszero/cordis-plugin-tool-deadline-guard;源码与完整配置说明:https://github.com/argszero/cordis-plugin-tool-deadline-guard。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜