12 KiB
description, kind
| description | kind |
|---|---|
| Advisory loop-hygiene guard that nudges the model out of identical tool-call loops, for users and maintainers choosing, configuring, or debugging the plugin. | package-reference |
@deepseek-ai/dsh-repeat-tool-reminder
English | 中文
Summary
A model can get stuck calling the same tool with the same arguments — re-running a failing command, re-reading an unchanged file — burning time and tokens without making progress. dsh-repeat-tool-reminder notices the pattern and tells the model to stop: at chosen repeat counts it delivers a reminder to analyze the last result and either try a different approach or finish. The reminder is advice, never a block: a legitimate repeated call is delayed by nothing, and the decision to continue, change approach, or stop stays with the model. It tracks each agent separately, so one agent's loop never disturbs another's work, and a new user message clears the count. It ships enabled in the dsh base bundle with reminders at 3, 5, and 8 repeats.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
Mount this plugin when the model should catch itself looping on identical tool calls. There is nothing to learn or wire: the dsh base bundle already runs it, and the defaults work for most sessions — tune the thresholds and tool scope below when you want the nudge sooner, later, or on fewer tools.
When to choose it
Choose it when the model works autonomously for long stretches and a stuck loop is the failure you want to break with advice rather than force. Avoid it when identical repeats are legitimate and must run undisturbed — the guard only reminds, and a reminder is a small extra message after the repeated call — and when near-identical variants must be caught, because only exact repeats (same tool, same arguments regardless of property order) are detected.
Setting the thresholds and scope
When you want to change when reminders fire or which tools they cover, mount the plugin with configuration:
- name: '@deepseek-ai/dsh-repeat-tool-reminder'
config:
thresholds: [3, 5, 8] # remind at 3, 5, and 8 consecutive repeats
include: [] # track every tool; list patterns to track only some
exclude: [todo_write] # never track these tools
argumentsPreviewChars: 500 # cap on arguments shown in the detailed reminder
| Field | Default | Meaning |
|---|---|---|
thresholds |
[3, 5, 8] |
Repeat counts that trigger a reminder |
include |
[] |
Only these tools are tracked; empty means every tool |
exclude |
[] |
These tools are never tracked; calls to them neither count nor reset |
argumentsPreviewChars |
500 |
How many characters of the repeated arguments the detailed reminder shows |
Invalid configuration fails at startup with a clear error — an empty thresholds list, a repeat count below 2, or a duplicate — never a silent change of behavior. The generated configuration catalog documents every accepted value.
What you get
With the defaults, a model that repeats the same call with identical arguments receives a short reminder on the third repeat — to analyze the previous result before calling again — and detailed reminders on the fifth and eighth, naming the tool and the repeated arguments so it can decide whether to change approach, gather more evidence, or finish. A new user message clears the count, so a fresh instruction is never treated as a loop. Reminders appear in the conversation after the repeated call's result, attributed to the plugin, so the model reads them like any other message.
Understand the implementation
Implementation internals — click to expand
This section explains how the guard detects repeats and delivers reminders, and points at the code that realizes it; the observable behavior is fully covered in Use this package.
Design philosophy
The guard is built on four commitments:
- Advisory, not veto. The guard enriches post-execute decisions with model context; it never blocks or rewrites a call, so
PostToolDecisionblocking stays a later listener's job. - Count in post-execute. Detection runs on
tools/post-execute, which also fires for denied calls; counting there lets one listener cover every attempt with no cross-event state. - Exact-match canonicalization. Arguments reach the guard as the loop's
JSON.parseoutput (or its raw-string fallback), so JSON's value domain is the whole input domain and a deep key-sort plusJSON.stringifyis a complete, deterministic identity — no bigint, cycle, orundefinedhandling exists because no input path can produce them. - Fail loud at load.
thresholdsandargumentsPreviewCharsvalidate inapplyand throw, never falling back to defaults.
Detection: the repeat chain
Each agent's chain is keyed by (tool name, canonical arguments) — two calls with the same tool and canonically identical arguments (property order ignored) count as consecutive, and a different tracked call resets the count to 1. The chain lives in a WeakMap<Agent, Chain>.
- Untracked calls are transparent to the chain. A call excluded by
include/excludeneither increments nor resets the counter, sogrep X → todo_write → grep Xstill counts as two consecutivegrep Xwhentodo_writeis excluded — bookkeeping tools interleaved into a loop do not launder it. - Denied calls count. Detection sits on
tools/post-execute, which also runs for calls atools/pre-executelistener denied; a model hammering a denied call is exactly the loop worth breaking. - Calls without an agent are ignored. A direct
ctx.tools.execute()caller has no model to remind and no live agent object to key on. - Per-agent keying, reset on user prompts. One agent's repetition never trips another's reminder; a user prompt (
agent/pre-step) deletes the submitting agent's chain, and object lifetime bounds the weak entry without a disposal listener. - In-memory only. A session resumed from persistence starts with a fresh chain — the guard is a heuristic nudge, not a logged invariant, so reminders after a resume are the accepted cost.
Reminder delivery
Reminders ride the post-execute decision's additionalContexts (source {kind: 'plugin', plugin: 'repeat-tool-reminder', form: 'notice', summary: '<tool> × <count>'}), never a content replacement: the tool/result event stays the tool's own output for audit. The loop buffers the context and appends it as an injected user/message after the step's tool results, which the session renders as a plain synthetic user message — model-visible, source-attributed, and reconstructable from the session log with no new session event. The guard always delegates via next() and prepends its reminder to the downstream decision's context array, so both decision variants (a blocked call included) still get the nudge while every entry retains its own source and metadata.
Source map
| File | Role |
|---|---|
src/index.ts |
Plugin entry: Config schema, fail-loud validation, chain listeners |
src/invariant.ts |
Invariant companion (no runtime invariant: the chain is private to one post-execute listener) |
Further Exploration
Read these pages when the package-level contract is not enough. They move from the tools waterfall to exhaustive configuration and the guard group map.
- Tools subsystem reference — the
tools/executewaterfall,additionalContexts, and decision shapes this guard consumes. - Generated configuration catalog — every accepted config field and its source declaration.
- guard group map — the sibling guard packages and the loop-hygiene family.
Model Experience
First-threshold context message
What the model sees
At the first configured consecutive-repeat threshold, that agent receives the reminder below. No tool schema or normal-call text is added.
First-threshold reminder
You are repeating the exact same tool call with identical arguments. Carefully analyze the previous result before calling again: if the task is not complete, try a different approach or different arguments instead of repeating the call.
Token effect
Zero tokens before the threshold. The reminder is retained history for that agent.
KV Cache effect
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
Later-threshold context message
What the model sees
A later threshold receives the detailed reminder template below. A capped argument preview ends exactly … (+<omitted> more chars).
Later-threshold reminder
Repeated tool call detected:
- tool: <toolName>
- consecutive_calls: <count>
- arguments: <canonicalArguments>
The repeated calls are not making progress. Do not call this tool with these exact arguments again. Inspect the latest result and choose a different action, different arguments, or finish the task if enough evidence has been gathered.
Token effect
Each reminder is retained history; argumentsPreviewChars bounds its data-dependent argument text, while agents keep independent counters.
KV Cache effect
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
Known Limitations and Deferred Work
These limits define when the guard is a poor fit. They are current package constraints, not a task backlog.
- Exact-match detection only — canonicalization is a deep key-sort, so near-identical variants (a tweaked path, extra whitespace inside a value) evade the chain; fuzzy matching is rejected pending evidence of need.
- Compaction does not reset chains — a chain spanning a compaction checkpoint keeps counting.
- Advisory only — escalating to a blocking form at a high threshold is not implemented, though
PostToolDecisionalready supports blocking. - No subagent chain-sharing — chains stay isolated per agent; a parent and its subagent repeating the same call never combine.
- Legitimate idempotent polling still draws nudges past the thresholds — the pressure valves are the
thresholds/excludeconfig. - Past the highest threshold a chain goes silent — reminders fire only at exact configured counts, never beyond them.
Dev Note
Working context for maintainers — click to expand
This Dev Note is working context for maintainers: open questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
The repeat-tool-guard feature note records the original design and alternatives under the former package name; the naming ledger records the rename to repeat-tool-reminder and its reason.