十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DeepSeek Harness 重复工具调用守卫(repeat-tool-reminder)深度解析:循环卫生插件的设计、配置与源码实现

DeepSeek Harness 重复工具调用守卫(repeat-tool-reminder)深度解析:循环卫生插件的设计、配置与源码实现 DeepSeek Harness 重复工具调用守卫repeat-tool-reminder深度解析循环卫生插件的设计、配置与源码实现【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness模型在长时间自主工作中陷入循环——用字节级相同的参数反复发起同一条失败的 grep、反复读取一个未变化的文件、反复轮询一条已经给出答案的命令——是 Agent 系统最典型也最昂贵的失败模式之一每一轮往返都消耗 token、挂钟时间与对付费 API 而言金钱却不产生任何新信息。DeepSeek HarnessEverything is a Plugin以deepseek-ai/dsh-repeat-tool-reminder插件给出了原生解法它统计同一工具以相同规范化参数发起的连续调用次数在配置阈值处向模型注入建议性提醒。读完本文你将掌握该守卫的设计定位、检测语义、完整配置方式以及基于 源码 的逐行实现原理。背景harness 为什么需要循环守卫模型陷入循环时harness 自身没有任何机制能察觉这一点循环没有步骤预算也没有插件追踪调用重复。模型只有在碰巧改变自身行为时才能跳出。这种失败模式真实存在且检测成本极低——社区中的 pi coding-agent 已经以扩展形式提供此功能统计连续相同调用次数超过阈值后追加一条system-reminder告诉模型停止重复并换个方向。关键在于harness 已经具备 pi 扩展所需的全部 seam而且做得更完整拦截 seam 赋予了tools/post-execute一种经过认可的方式将面向模型的上下文附加到已完成的调用上循环缓冲并注入该上下文同时保持调用/结果的邻接关系注入的上下文是一条已记录的context/message因此原生守卫无需新增会话事件即可满足「模型可见 ⇔ 已记录」的规则。缺的只是插件本身——这也是 Agent Note 记录这一决策的背景。该 Note 中的包名repeat-tool-guard后来按仓库命名契约改名为repeat-tool-reminder当前实现见 包目录。设计定位循环卫生插件而非面向模型的工具守卫是一个循环卫生插件而不是模型可调用的工具。它统计对同一工具以相同规范化参数发起的连续调用次数并在配置的阈值处注入建议性提醒。它从不延迟、阻止或改写调用——模型自行决定是换种方式重试还是结束任务。这一仅建议、不否决的姿态贯穿整个实现。从 源码 可以看到post-execute 监听器始终通过next()委托下游决策随后把提醒折叠到返回结果的additionalContexts上——无论下游是accept还是block提醒都能送达而阻止仍是后续监听器的事。插件注册两个监听器将状态保存在以存活Agent对象为键的WeakMap中chains new WeakMapAgent, Chain()。这一选择有两个深层原因按 agent 分键是正确性要求工具注册表是上下文级别的单例其 waterfall 事件会交错所有 agent 的调用subagent 运行在同一个上下文上。如果不按 agent 分键一个 agent 的循环会误触发另一个 agent 的提醒。弱对象键免除了 disposal 监听器当 agent 对象被回收时其链条目随之消失纯清理用途的 dispose 监听器不再必要。两个监听器分工如下监听器角色说明tools/post-executewaterfall唯一的检测点同时接收(exec, result)计数与提醒投递无需跨事件的 pending map始终通过next()委托agent/prompt-submitwaterfall纯重置钩子用户介入改变了上下文跨越介入的重复不是循环清除提交 agent 的链检测语义规范化、透明与隔离链键与规范化链的键是(tool name, canonical arguments)。与前一个被追踪调用相同的调用递增该 agent 的连续计数器不同的被追踪调用将其重置为 1。规范化方式为深度键排序加JSON.stringifysortJsonValue与canonicalize。这里有个值得注意的实现事实ToolExecution.arguments按构造就是循环中JSON.parse的输出或格式错误参数 JSON 的原始字符串回退其本身也是可比较的值因此 JSON 的值域就是全部输入域——pi 原版对 bigint、循环引用、undefined的防御性处理在此没有输入路径能产生被有意去除。测试也验证了深度规范化canonicalization ignores property order, deeply断言{a: 1, nested: {x: [1, 2], y: null}}与键序打乱的同值对象在链中视为相同。两条刻意的规则以下两条规则记录在 包 README 中因为它们是读者否则只能猜测的行为未追踪的调用对链透明。被include/exclude排除的调用既不递增也不重置计数器因此grep X → todo_write → grep X在todo_write被排除时仍计为两次连续的grep X。这正是排除功能有用的原因——穿插在循环中的簿记工具不得为循环洗白。这是 pi 扩展的未文档化的语义本实现有意保留并明确写下。没有 agent 的调用被忽略。直接调用ctx.tools.execute()的调用方测试、非循环消费方没有可提醒的模型也没有可作键的存活 agent 对象observe中的if (!exec.agent) return undefined。计数位置post-execute 而非 pre-execute计数放在tools/post-execute而非tools/pre-execute因为post-execute 也会为被拒绝的调用触发ToolRegistry.execute将 deny 路由到同一条流水线。模型反复敲击一个被拒绝的调用恰恰是值得打破的循环——单元测试 用pre-execute返回deny的方式验证了这一行为。一个监听器、无跨事件状态即可覆盖严格更多的尝试场景。提醒投递additionalContexts 与升级阈值提醒作为独立条目搭载在additionalContexts上source 为{ kind: plugin, plugin: repeat-tool-reminder, form: notice, summary: tool × count }{kind: plugin}标签是承载语义的——未打标签的上下文在派生历史中会渲染为普通用户提示词。提醒绝不替换contenttool/result事件仍是工具自身的审计输出循环则在步骤结果之后把缓冲的上下文追加为context/message会话将其渲染为带标签的合成 user 信封并由派生历史回放。阈值逐级升级observe中的分派第一个配置阈值获得简短温和提醒——你正在用相同参数重复完全相同的工具调用请先仔细分析上一次结果若任务未完成请换一种方法或换一组参数而不是重复调用。后续各阈值获得详细提醒包含工具名、重复计数和规范化参数在头部截断到argumentsPreviewChars默认 500 字符并以… (N more chars)标注省略量。参数预览截断只约束模型可见文本链键始终比较完整规范化字符串previewArguments——循环中的write级大 payload 不得无界地进入下一次请求但检测的完整性不受影响。对应的测试用 400 字符载荷验证了截断只发生在展示层。一个移植细节值得记录pi 原版把温和文本硬编码为字面计数 3本守卫以thresholds[0]为键修复了这一 bug。测试keys the gentle text to thresholds[0], not the literal 3见测试验证了自定义阈值[4, 2]故意无序加载时归一化为升序下温和提醒出现在第 2 次、详细提醒出现在第 4 次。下游钩子桥贡献仍是独立的数组条目因此两个插件都保留各自的 source、信封与元数据。配置指南插件随dshbase 组合默认启用见 base 组合的 cordis.patch.yml默认在 3、5、8 次重复时提醒。需要调优时通过配置挂载- id: repeat-tool-reminder name: deepseek-ai/dsh-repeat-tool-reminder config: thresholds: [3, 5, 8] # 触发提醒的连续重复次数 include: [] # 只跟踪这些工具空 ⇒ 跟踪所有工具 exclude: [todo_write] # 对链透明的工具既不计数也不重置 argumentsPreviewChars: 500 # 详细提醒中引用的参数长度上限字段默认值含义thresholds[3, 5, 8]触发提醒的重复次数加载时校验空列表、非整数、小于 2 的值或重复项都会抛出异常include[]只跟踪这些工具空表示跟踪所有工具exclude[]绝不跟踪这些工具对它们的调用既不计数也不重置argumentsPreviewChars500详细提醒中显示多少字符的重复参数校验语义配置错误快速失败thresholds在加载时校验validateThresholds空列表、非整数、小于 2 的值或重复项都会抛出异常——配置错误快速失败取代 pi 原版的静默回退到默认值。argumentsPreviewChars同样要求正整数否则抛错测试覆盖。通配符语义模式是调用时的谓词include/exclude条目支持*通配符wildcardToRegExp将通配符编译为锚定正则其余正则元字符按字面量转义。模式是对调用时实际存在的工具的谓词而非对注册表条目的引用——因此匹配不到当前已注册工具的条目不是错误与toolOrder的引用检查不同exclude: [mcp_*]在未加载 MCP 工具的部署中也必须保持有效。测试escapes regex metacharacters in patterns见测试验证了pr.be不会被当作正则的任意字符匹配。源码实现细读核心实现集中在 src/index.ts约 230 行可拆解为四层配置层Configschema 由deepseek-ai/schemastery定义L45-L50apply中做 fail-loud 二次校验。规范化层sortJsonValue深度键排序 canonicalize生成链键。链管理层WeakMapAgent, Chain持有每个 agent 的{key, count}observe单函数完成推进链 命中阈值时生成提醒L189-L207。事件层tools/post-execute观察并丰富先计数、后委托、再折叠agent/prompt-submit源码中为agent/pre-step检测 user 消息纯重置。其中折叠到下游决策的实现值得展开L213-L224监听器先observe无论下游结果如何状态都已推进再await next()委托下游最后按block与普通决策两种变体分别把提醒前置到additionalContexts。prependContext保证提醒排在下游上下文之前同时完整保留下游条目的 source 与元数据。测试folds the reminder onto a downstream block and keeps its feedback见测试验证了被阻止的调用依然收到提醒、block 的 feedback 原样到达工具结果。包还附带一个不变的伴生插件 src/invariant.ts由于重复链私有于单个 post-execute 监听器、不暴露任何包级事件或快照该伴生插件没有运行时不变式可观察仅保留包所有权注册。测试验证单元测试repeat-tool-reminder.spec.ts403 行使用脚本化 MockAdapter 驱动真实 agent 循环逐文件 100% 覆盖率。覆盖场景包括计数与重置规则、未追踪透明性、dispose 清理复用同一 session id 的新 agent 从计数 1 重新开始、按 agent 隔离两个 agent 同屏一个触发提醒另一个不触发、规范化参数键序、阈值升级含thresholds[0]温和文本规则、被拒绝的调用仍计数、无 agent 执行不崩溃、通配符转义、无效配置拒绝以及下游 block/replacement 决策时的折叠行为。快照测试keyless 场景发起五次相同的todo_write调用在 ACP 输出和会话日志中固定第三次调用的温和提醒与第五次调用的详细提醒。该插件在实时示例中加载但在其他场景中保持静默。E2e 测试无——该插件是确定性的且与提供方无关其 seam 契约由各自的所有者tools 子系统、agent-loop覆盖。曾考虑的替代方案与取舍Agent Note 完整记录了六个被否决的替代方案每个都揭示了设计意图将提醒追加到工具结果中替换content的accept——pi 扩展的机制否决。这会让已记录的tool/result对工具实际返回的内容撒谎而additionalContexts是 post-execute 评注的独立认可通道循环级缓冲保持了调用/结果的邻接关系。在tools/pre-execute中计数并使用 pending-reminder mappi 的两阶段形态否决。post-execute 单独就能同时看到(exec, result)且也为被拒绝的调用触发一个监听器、无跨事件状态即可覆盖严格更多的尝试。在最高阈值升级为block在初始范围内否决。阻止会惩罚合法的相同重复轮询长时间运行的终端、重新检查预期会变化的文件建议性提醒让模型保持控制权。PostToolDecision已支持此选项待有证据后重新审视。通过 CC/Codex 桥接的逐部署外部钩子一个PostToolUse脚本否决作为最终答案。它对单个部署有效但一个已发布、有单元测试、可通过cordis.yml配置的插件才是 harness 原生的形式且没有逐调用的子进程开销。在agent-loop中设置循环级步骤或重复预算否决。「用插件不改循环」硬性步骤预算是一种更粗粒度的正交控制。模糊/近似相同检测路径归一化、相似但不完全相同的参数否决。规范化后的精确匹配成本低、确定性强、且可向模型解释相似度阈值引入误报风险需要证据才能换取复杂度。将包放在core/否决。core 是产品主干行为守卫是可选的叶子插件todo/分组是先例。守卫因此独立成guard/分组与 timeout-policy 同属循环卫生家族见 guard 组 README。后果、已知限制与延后事项设计上的后果值得使用者留意提醒在设计上是建议性的有意重复相同调用的幂等轮询模式仍会在超过阈值后收到提示。减压阀是配置thresholds、exclude加上明确允许「在已收集足够证据时结束」的提醒文本。每次触发在下一次请求中增加提醒 token 的开销阈值限制了触发频率。链状态仅存于内存从持久化恢复的会话以全新的链开始因此跨越恢复的循环比实时循环更晚收到提醒——守卫是启发式提示而非已记录的不变式持久化计数器状态带来的收益不值得其复杂度。多个 post-execute 生产者共存当多个监听器在同一次调用上附加上下文时每项贡献保持为独立的HookContext顺序遵循 waterfall 嵌套关系每个条目保留自己的溯源信息。压缩compaction不重置链压缩后的历史改变了模型所见的内容但重复风险通常在压缩后仍然存在。subagent 的链按 agent 隔离父 agent 与其 subagent 重复相同调用也绝不合并在出现具体用例之前不提供共享机制。精确匹配的边界仅精确重复同一工具、同一参数、与属性顺序无关会被检测近似变体会绕过链——这是当前的包约束不是任务积压。对维护者而言Agent Note 还记录了一个测试基建的意外收获实现快照层时暴露了 suite kit 的一项隐藏假设——fixture guard 把「撰写的模型场景」等同于「由 override 驱动」。Scenario表现在携带显式的overridden标志且 sidecar 是否存在会以双向方式与其核对使得 suite kit 比该插件出现前更严格。结语deepseek-ai/dsh-repeat-tool-reminder是一个体量极小单文件 ~230 行源码、403 行测试却语义精确的循环卫生插件。它演示了 DeepSeek Harness 插件体系的一条核心路径在既有 seam 上做观察与丰富而不是侵入循环本体——用tools/post-execute的 waterfall 契约、additionalContexts的注入通道与{kind: plugin}的来源标签以近乎零机制成本解决了 Agent 系统中最昂贵的失败模式之一。对使用者而言它默认启用、开箱即得唯一需要动手的是按自己的工具集调优thresholds与include/exclude对插件作者而言它是学习 harness 事件驱动插件设计的一份高质量范本。【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表