@qilin-agent/repeat-tool-reminder 0.0.0-stage → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +85 -0
- package/README.md +184 -2
- package/README.zh.md +185 -0
- package/lib/index.js +2221 -0
- package/lib/types/index.d.ts +51 -0
- package/package.json +47 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/guard/repeat-tool-reminder/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 8eb3c6cc73da87aa
|
|
7
|
+
zh: 4059266a0486ff38
|
|
8
|
+
/qilin-repeat-tool-reminder:
|
|
9
|
+
en: f5f948e3fd82de70
|
|
10
|
+
zh: 6e020808b11b2865
|
|
11
|
+
/qilin-repeat-tool-reminder/summary:
|
|
12
|
+
en: 2ffc8eaa09944f94
|
|
13
|
+
zh: 217e126ac2ba7610
|
|
14
|
+
/qilin-repeat-tool-reminder/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/qilin-repeat-tool-reminder/use-this-package:
|
|
18
|
+
en: e20c419e5d4abf5b
|
|
19
|
+
zh: 2d0be96eb69df337
|
|
20
|
+
/qilin-repeat-tool-reminder/use-this-package/when-to-choose-it:
|
|
21
|
+
en: c6b47c1f0034c021
|
|
22
|
+
zh: 5a2097425754d8b0
|
|
23
|
+
/qilin-repeat-tool-reminder/use-this-package/setting-the-thresholds-and-scope:
|
|
24
|
+
en: 3b6521442e8a1a00
|
|
25
|
+
zh: 11b088ac94b9b5e5
|
|
26
|
+
/qilin-repeat-tool-reminder/use-this-package/what-you-get:
|
|
27
|
+
en: 5cf91a5809d2c68a
|
|
28
|
+
zh: 8f60231436b863b8
|
|
29
|
+
/qilin-repeat-tool-reminder/understand-the-implementation:
|
|
30
|
+
en: dd6d7a1571af5a9a
|
|
31
|
+
zh: eba00095dfc1c92a
|
|
32
|
+
/qilin-repeat-tool-reminder/understand-the-implementation/design-philosophy:
|
|
33
|
+
en: 7bca56e3cba01338
|
|
34
|
+
zh: 530fda764d226f39
|
|
35
|
+
/qilin-repeat-tool-reminder/understand-the-implementation/detection-the-repeat-chain:
|
|
36
|
+
en: b76a432aad14aa90
|
|
37
|
+
zh: b0cd916f7897fb42
|
|
38
|
+
/qilin-repeat-tool-reminder/understand-the-implementation/reminder-delivery:
|
|
39
|
+
en: ebd2a7d704e0a7be
|
|
40
|
+
zh: 438213ad0412a64e
|
|
41
|
+
/qilin-repeat-tool-reminder/understand-the-implementation/source-map:
|
|
42
|
+
en: ba2eb423e165510d
|
|
43
|
+
zh: db5155503a8504cb
|
|
44
|
+
/qilin-repeat-tool-reminder/further-exploration:
|
|
45
|
+
en: ab869e8b38320368
|
|
46
|
+
zh: e47e436439f15e83
|
|
47
|
+
/qilin-repeat-tool-reminder/model-experience:
|
|
48
|
+
en: 215e7ba838619b7b
|
|
49
|
+
zh: a311da3843709f90
|
|
50
|
+
/qilin-repeat-tool-reminder/model-experience/first-threshold-context-message:
|
|
51
|
+
en: 798ea2afb5efde7c
|
|
52
|
+
zh: 14fae1e31d882e8a
|
|
53
|
+
/qilin-repeat-tool-reminder/model-experience/first-threshold-context-message/what-the-model-sees:
|
|
54
|
+
en: cf01847a41f3fe3d
|
|
55
|
+
zh: aebeb78ac617999c
|
|
56
|
+
/qilin-repeat-tool-reminder/model-experience/first-threshold-context-message/what-the-model-sees/first-threshold-reminder:
|
|
57
|
+
en: 38c7616427fa569d
|
|
58
|
+
zh: fd443fab22a57894
|
|
59
|
+
/qilin-repeat-tool-reminder/model-experience/first-threshold-context-message/token-effect:
|
|
60
|
+
en: 111c6d779b73194b
|
|
61
|
+
zh: 09b0e7073308ba91
|
|
62
|
+
/qilin-repeat-tool-reminder/model-experience/first-threshold-context-message/kv-cache-effect:
|
|
63
|
+
en: f5d5f97a153f7c70
|
|
64
|
+
zh: 51155536aab03002
|
|
65
|
+
/qilin-repeat-tool-reminder/model-experience/later-threshold-context-message:
|
|
66
|
+
en: edfa41e2478fb579
|
|
67
|
+
zh: 76b87439f3bef43c
|
|
68
|
+
/qilin-repeat-tool-reminder/model-experience/later-threshold-context-message/what-the-model-sees:
|
|
69
|
+
en: cddb7a41c335141f
|
|
70
|
+
zh: 7ea33c6570bb2bd0
|
|
71
|
+
/qilin-repeat-tool-reminder/model-experience/later-threshold-context-message/what-the-model-sees/later-threshold-reminder:
|
|
72
|
+
en: 341114f2e5e9c05b
|
|
73
|
+
zh: 8938e7d0f7fc150b
|
|
74
|
+
/qilin-repeat-tool-reminder/model-experience/later-threshold-context-message/token-effect:
|
|
75
|
+
en: 2e92c0d5a90c482b
|
|
76
|
+
zh: 07ba3851b725f06d
|
|
77
|
+
/qilin-repeat-tool-reminder/model-experience/later-threshold-context-message/kv-cache-effect:
|
|
78
|
+
en: f5d5f97a153f7c70
|
|
79
|
+
zh: 51155536aab03002
|
|
80
|
+
/qilin-repeat-tool-reminder/known-limitations-and-deferred-work:
|
|
81
|
+
en: 59ffa89bf74f8da2
|
|
82
|
+
zh: 7652f49b9eac40d6
|
|
83
|
+
/qilin-repeat-tool-reminder/known-limitations-and-deferred-work/dev-note:
|
|
84
|
+
en: ef308dcf213685c7
|
|
85
|
+
zh: 5830b6bbeea9863f
|
package/README.md
CHANGED
|
@@ -1,3 +1,185 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Advisory loop-hygiene guard that nudges the model out of identical tool-call loops, for users and maintainers choosing, configuring, or debugging the plugin."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/repeat-tool-reminder
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
This package helps a model escape loops in which it calls the same tool with identical arguments without making progress. At configured repeat counts, it asks the model to inspect the previous result and change approach or finish. The reminder is advisory: it never blocks or delays a legitimate repeated call. Repeats are tracked separately for each agent and cleared by a new user message. The `qilin` base bundle enables the package with reminders at 3, 5, and 8 repeats.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Mount this plugin when the model should catch itself looping on identical tool calls. There is nothing to learn or wire: the `qilin` 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.
|
|
29
|
+
|
|
30
|
+
### When to choose it
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
### Setting the thresholds and scope
|
|
35
|
+
|
|
36
|
+
When you want to change when reminders fire or which tools they cover, mount the plugin with configuration:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@qilin-agent/repeat-tool-reminder'
|
|
40
|
+
config:
|
|
41
|
+
thresholds: [3, 5, 8] # remind at 3, 5, and 8 consecutive repeats
|
|
42
|
+
include: [] # track every tool; list patterns to track only some
|
|
43
|
+
exclude: [todo_write] # never track these tools
|
|
44
|
+
argumentsPreviewChars: 500 # cap on arguments shown in the detailed reminder
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Field | Default | Meaning |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `thresholds` | `[3, 5, 8]` | Repeat counts that trigger a reminder |
|
|
50
|
+
| `include` | `[]` | Only these tools are tracked; empty means every tool |
|
|
51
|
+
| `exclude` | `[]` | These tools are never tracked; calls to them neither count nor reset |
|
|
52
|
+
| `argumentsPreviewChars` | `500` | How many characters of the repeated arguments the detailed reminder shows |
|
|
53
|
+
|
|
54
|
+
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](../../../docs/config-catalog.md#qilin-agentrepeat-tool-reminder) documents every accepted value.
|
|
55
|
+
|
|
56
|
+
### What you get
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
60
|
+
-----
|
|
61
|
+
|
|
62
|
+
<a id="understand-the-implementation"></a>
|
|
63
|
+
## Understand the implementation
|
|
64
|
+
|
|
65
|
+
<details>
|
|
66
|
+
<summary>Implementation internals — click to expand</summary>
|
|
67
|
+
|
|
68
|
+
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](#use-this-package).
|
|
69
|
+
|
|
70
|
+
### Design philosophy
|
|
71
|
+
|
|
72
|
+
The guard is built on four commitments:
|
|
73
|
+
|
|
74
|
+
- **Advisory, not veto.** The guard enriches post-execute decisions with model context; it never blocks or rewrites a call, so `PostToolDecision` blocking stays a later listener's job.
|
|
75
|
+
- **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.
|
|
76
|
+
- **Exact-match canonicalization.** Arguments reach the guard as the loop's `JSON.parse` output (or its raw-string fallback), so JSON's value domain is the whole input domain and a deep key-sort plus `JSON.stringify` is a complete, deterministic identity — no bigint, cycle, or `undefined` handling exists because no input path can produce them.
|
|
77
|
+
- **Fail loud at load.** `thresholds` and `argumentsPreviewChars` validate in `apply` and throw, never falling back to defaults.
|
|
78
|
+
|
|
79
|
+
### Detection: the repeat chain
|
|
80
|
+
|
|
81
|
+
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>`.
|
|
82
|
+
|
|
83
|
+
- **Untracked calls are transparent to the chain.** A call excluded by `include`/`exclude` neither increments nor resets the counter, so `grep X → todo_write → grep X` still counts as two consecutive `grep X` when `todo_write` is excluded — bookkeeping tools interleaved into a loop do not launder it.
|
|
84
|
+
- **Denied calls count.** Detection sits on `tools/post-execute`, which also runs for calls a `tools/pre-execute` listener denied; a model hammering a denied call is exactly the loop worth breaking.
|
|
85
|
+
- **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.
|
|
86
|
+
- **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.
|
|
87
|
+
- **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.
|
|
88
|
+
|
|
89
|
+
### Reminder delivery
|
|
90
|
+
|
|
91
|
+
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.
|
|
92
|
+
|
|
93
|
+
### Source map
|
|
94
|
+
|
|
95
|
+
| File | Role |
|
|
96
|
+
|---|---|
|
|
97
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, fail-loud validation, chain listeners |
|
|
98
|
+
|
|
99
|
+
</details>
|
|
100
|
+
|
|
101
|
+
-----
|
|
102
|
+
|
|
103
|
+
<a id="further-exploration"></a>
|
|
104
|
+
## Further Exploration
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
- [Tools subsystem reference](../../../docs/subsystems/tools.md) — the `tools/execute` waterfall, `additionalContexts`, and decision shapes this guard consumes.
|
|
109
|
+
- [Generated configuration catalog](../../../docs/config-catalog.md#qilin-agentrepeat-tool-reminder) — every accepted config field and its source declaration.
|
|
110
|
+
- [guard group map](../README.md) — the sibling guard packages and the loop-hygiene family.
|
|
111
|
+
|
|
112
|
+
-----
|
|
113
|
+
|
|
114
|
+
<a id="model-experience"></a>
|
|
115
|
+
## Model Experience
|
|
116
|
+
|
|
117
|
+
### First-threshold context message
|
|
118
|
+
|
|
119
|
+
#### What the model sees
|
|
120
|
+
|
|
121
|
+
At the first configured consecutive-repeat threshold, that agent receives the reminder below. No tool schema or normal-call text is added.
|
|
122
|
+
|
|
123
|
+
##### First-threshold reminder
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
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.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Token effect
|
|
130
|
+
|
|
131
|
+
Zero tokens before the threshold. The reminder is retained history for that agent.
|
|
132
|
+
|
|
133
|
+
#### KV Cache effect
|
|
134
|
+
|
|
135
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
136
|
+
|
|
137
|
+
### Later-threshold context message
|
|
138
|
+
|
|
139
|
+
#### What the model sees
|
|
140
|
+
|
|
141
|
+
A later threshold receives the detailed reminder template below. A capped argument preview ends exactly `… (+<omitted> more chars)`.
|
|
142
|
+
|
|
143
|
+
##### Later-threshold reminder
|
|
144
|
+
|
|
145
|
+
```markdown
|
|
146
|
+
Repeated tool call detected:
|
|
147
|
+
- tool: <toolName>
|
|
148
|
+
- consecutive_calls: <count>
|
|
149
|
+
- arguments: <canonicalArguments>
|
|
150
|
+
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.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Token effect
|
|
154
|
+
|
|
155
|
+
Each reminder is retained history; `argumentsPreviewChars` bounds its data-dependent argument text, while agents keep independent counters.
|
|
156
|
+
|
|
157
|
+
#### KV Cache effect
|
|
158
|
+
|
|
159
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
160
|
+
|
|
161
|
+
## Known Limitations and Deferred Work
|
|
162
|
+
|
|
163
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
These limits define when the guard is a poor fit. They are current package constraints, not a task backlog.
|
|
167
|
+
|
|
168
|
+
- **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.
|
|
169
|
+
- **Compaction does not reset chains** — a chain spanning a compaction checkpoint keeps counting.
|
|
170
|
+
- **Advisory only** — escalating to a blocking form at a high threshold is not implemented, though `PostToolDecision` already supports blocking.
|
|
171
|
+
- **No subagent chain-sharing** — chains stay isolated per agent; a parent and its subagent repeating the same call never combine.
|
|
172
|
+
- **Legitimate idempotent polling still draws nudges** past the thresholds — the pressure valves are the `thresholds`/`exclude` config.
|
|
173
|
+
- **Past the highest threshold a chain goes silent** — reminders fire only at exact configured counts, never beyond them.
|
|
174
|
+
|
|
175
|
+
<a id="dev-note"></a>
|
|
176
|
+
### Dev Note
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
180
|
+
|
|
181
|
+
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.
|
|
182
|
+
|
|
183
|
+
The [repeat-tool-guard feature note](../../../.agents/notes/archived/feature/2026-07-08-repeat-tool-guard.md) records the original design and alternatives under the former package name; the [naming ledger](../../../.agents/notes/archived/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.md) records the rename to `repeat-tool-reminder` and its reason.
|
|
184
|
+
|
|
185
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "建议性循环卫生 guard:当 agent(智能体)重复完全相同的工具调用时提醒模型,供选择、配置或排查此插件的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/repeat-tool-reminder
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
本包帮助模型跳出以相同参数反复调用同一工具却没有进展的循环。达到配置的重复次数时,它会要求模型检查上一次结果并改变方法或结束任务。提醒只是建议,绝不会阻止或延迟合理的重复调用。每个 agent 的重复分别跟踪,新的用户消息会清除计数。`qilin` 基础组合包默认启用本包,并在重复 3、5、8 次时提醒。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
当模型应当自行发现自己在相同工具调用上循环时,挂载此插件。无需学习或接线:`qilin` 基础组合包已经运行它,默认值适用于大多数会话——想更早、更晚或在更少的工具上收到提醒时,调优下面的阈值与工具范围即可。
|
|
29
|
+
|
|
30
|
+
### 何时选择
|
|
31
|
+
|
|
32
|
+
当模型长时间自主工作、且卡住的循环是你想用建议而非强制来打破的失败模式时,选择它。当相同的重复是合理且必须不受打扰地运行时——guard 只会提醒,提醒只是重复调用之后的一条小消息——以及必须捕获近似变体时(因为只有精确重复——同一工具、同一参数且与属性顺序无关——才会被检测到),避免使用它。
|
|
33
|
+
|
|
34
|
+
### 设置阈值与范围
|
|
35
|
+
|
|
36
|
+
想改变提醒何时触发或覆盖哪些工具时,用配置挂载插件:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@qilin-agent/repeat-tool-reminder'
|
|
40
|
+
config:
|
|
41
|
+
thresholds: [3, 5, 8] # remind at 3, 5, and 8 consecutive repeats
|
|
42
|
+
include: [] # track every tool; list patterns to track only some
|
|
43
|
+
exclude: [todo_write] # never track these tools
|
|
44
|
+
argumentsPreviewChars: 500 # cap on arguments shown in the detailed reminder
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| 字段 | 默认值 | 含义 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `thresholds` | `[3, 5, 8]` | 触发提醒的重复次数 |
|
|
50
|
+
| `include` | `[]` | 只跟踪这些工具;空表示所有工具 |
|
|
51
|
+
| `exclude` | `[]` | 绝不跟踪这些工具;对它们的调用既不计数也不重置 |
|
|
52
|
+
| `argumentsPreviewChars` | `500` | 详细提醒中显示多少字符的重复参数 |
|
|
53
|
+
|
|
54
|
+
无效配置会在启动时以清晰错误失败——空的 `thresholds` 列表、小于 2 的重复次数或重复值——绝不会静默改变行为。生成的[配置目录](../../../docs/config-catalog.zh.md#qilin-agentrepeat-tool-reminder)记录每个受支持的值。
|
|
55
|
+
|
|
56
|
+
### 你会得到什么
|
|
57
|
+
|
|
58
|
+
按默认值,以相同参数重复同一调用的模型会在第三次重复时收到简短提醒——先分析上一次结果再调用——并在第五次和第八次收到详细提醒,列出工具与重复参数,使其决定改变方法、收集更多证据还是结束任务。新的用户消息会清零计数,因此全新指令绝不会被当作循环。提醒出现在重复调用的结果之后、归属于插件,模型像阅读任何其他消息一样阅读它。
|
|
59
|
+
|
|
60
|
+
-----
|
|
61
|
+
|
|
62
|
+
<a id="understand-the-implementation"></a>
|
|
63
|
+
## 理解实现
|
|
64
|
+
|
|
65
|
+
<details>
|
|
66
|
+
<summary>实现细节——点击展开</summary>
|
|
67
|
+
|
|
68
|
+
本节解释 guard 如何检测重复并投递提醒,并指出实现它的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
69
|
+
|
|
70
|
+
### 设计理念
|
|
71
|
+
|
|
72
|
+
guard 建立在四项承诺之上:
|
|
73
|
+
|
|
74
|
+
- **仅建议,不否决。** guard 用模型上下文丰富 post-execute 决策;它从不阻止或改写调用,因此 `PostToolDecision` 阻止仍是后续监听器的事。
|
|
75
|
+
- **在 post-execute 中计数。** 检测运行在 `tools/post-execute` 上,被拒绝的调用同样会经过它;在那里计数让一个监听器即可覆盖所有尝试,无需跨事件状态。
|
|
76
|
+
- **精确匹配规范化。** 参数以循环的 `JSON.parse` 输出(或畸形参数 JSON 的原始字符串回退)到达 guard,因此 JSON 的值域就是全部输入域,深度键排序加 `JSON.stringify` 是完整、确定性的同一性判定——不存在 bigint、循环引用或 `undefined` 处理,因为没有输入路径能产生它们。
|
|
77
|
+
- **加载时快速失败。** `thresholds` 与 `argumentsPreviewChars` 在 `apply` 中校验并抛出错误,绝不回退到默认值。
|
|
78
|
+
|
|
79
|
+
### 检测:重复链
|
|
80
|
+
|
|
81
|
+
每个 agent 的链以「`(tool name, canonical arguments)`」为键——同一工具且规范化后参数相同(忽略属性顺序)的两次调用计为连续,换成另一条受跟踪调用则把计数重置为 1。链保存在 `WeakMap<Agent, Chain>` 中。
|
|
82
|
+
|
|
83
|
+
- **不受跟踪的调用对链透明。** 被 `include`/`exclude` 排除的调用既不递增也不重置计数器,因此 `grep X → todo_write → grep X` 在 `todo_write` 被排除时仍算作连续两次 `grep X`——穿插进循环的记录类工具不能掩盖循环。
|
|
84
|
+
- **被拒绝的调用也计数。** 检测位于 `tools/post-execute`,被 `tools/pre-execute` 监听器拒绝的调用同样会经过它;模型反复尝试被拒绝的调用,恰恰是需要打破的循环。
|
|
85
|
+
- **忽略没有 agent 的调用。** 直接调用 `ctx.tools.execute()` 的调用方没有需要提醒的模型,也没有可作为键的活跃 agent 对象。
|
|
86
|
+
- **按 agent 分键,用户提示词时重置。** 一个 agent 的重复绝不会触发另一个 agent 的提醒;用户提示词(`agent/pre-step`)会删除提交该提示词的 agent 链,对象生命周期限制弱引用条目的寿命,无需 dispose(资源释放)监听器。
|
|
87
|
+
- **仅驻留内存。** 从持久化恢复的会话以全新链开始——guard 是启发式提醒,而非记录在案的不变量,因此恢复后的提醒延后是可接受的代价。
|
|
88
|
+
|
|
89
|
+
### 提醒传递
|
|
90
|
+
|
|
91
|
+
提醒随 post-execute 决策的 `additionalContexts`(来源为 `{kind: 'plugin', plugin: 'repeat-tool-reminder', form: 'notice', summary: '<tool> × <count>'}`)传递,绝不替换 `content`:用于审计的 `tool/result` 事件仍保留工具自己的输出。循环会缓冲这段上下文,并在该步骤的工具结果之后作为注入的 `user/message` 追加,会话将其渲染为普通的合成用户消息——模型可见、带有来源归属,且无需新会话事件即可从会话日志重建。guard 始终通过 `next()` 委派,并把提醒放在下游决策的上下文数组之前,因此两种决策变体(包括被阻止的调用)都会收到提醒,同时每个条目保留自己的来源与元数据。
|
|
92
|
+
|
|
93
|
+
### 源码地图
|
|
94
|
+
|
|
95
|
+
| 文件 | 职责 |
|
|
96
|
+
|---|---|
|
|
97
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、快速失败校验、链监听器 |
|
|
98
|
+
|
|
99
|
+
</details>
|
|
100
|
+
|
|
101
|
+
-----
|
|
102
|
+
|
|
103
|
+
<a id="further-exploration"></a>
|
|
104
|
+
## 进一步探索
|
|
105
|
+
|
|
106
|
+
当包级约定不够用时阅读以下页面。它们从工具 waterfall(瀑布式事件)逐步进入穷尽式配置与 guard 组映射。
|
|
107
|
+
|
|
108
|
+
- [工具子系统参考](../../../docs/subsystems/tools.zh.md)——本 guard 消费的 `tools/execute` waterfall、`additionalContexts` 与决策形态。
|
|
109
|
+
- [生成配置目录](../../../docs/config-catalog.zh.md#qilin-agentrepeat-tool-reminder)——每个受支持配置字段及其源声明。
|
|
110
|
+
- [guard 组映射](../README.zh.md)——同组的 guard 包与循环卫生家族。
|
|
111
|
+
|
|
112
|
+
-----
|
|
113
|
+
|
|
114
|
+
<a id="model-experience"></a>
|
|
115
|
+
## 模型体验
|
|
116
|
+
|
|
117
|
+
### 首个阈值的上下文消息
|
|
118
|
+
|
|
119
|
+
#### 模型看到什么
|
|
120
|
+
|
|
121
|
+
达到第一个配置的连续重复阈值时,对应 agent 会收到下面的提醒。不会添加工具 schema 或正常调用文本。
|
|
122
|
+
|
|
123
|
+
##### 首个阈值提醒
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
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.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Token 影响
|
|
130
|
+
|
|
131
|
+
达到阈值前为零 token。提醒会作为该 agent 的历史记录保留。
|
|
132
|
+
|
|
133
|
+
#### KV Cache 影响
|
|
134
|
+
|
|
135
|
+
仅追加;新出现的内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
136
|
+
|
|
137
|
+
### 后续阈值的上下文消息
|
|
138
|
+
|
|
139
|
+
#### 模型看到什么
|
|
140
|
+
|
|
141
|
+
达到后续阈值时,agent 会收到下面的详细提醒模板。受上限约束的参数预览严格以 `… (+<omitted> more chars)` 结尾。
|
|
142
|
+
|
|
143
|
+
##### 后续阈值提醒
|
|
144
|
+
|
|
145
|
+
```markdown
|
|
146
|
+
Repeated tool call detected:
|
|
147
|
+
- tool: <toolName>
|
|
148
|
+
- consecutive_calls: <count>
|
|
149
|
+
- arguments: <canonicalArguments>
|
|
150
|
+
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.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Token 影响
|
|
154
|
+
|
|
155
|
+
每条提醒都会作为历史记录保留;`argumentsPreviewChars` 限制随数据变化的参数文本长度,而各 agent 仍使用独立计数器。
|
|
156
|
+
|
|
157
|
+
#### KV Cache 影响
|
|
158
|
+
|
|
159
|
+
仅追加;新出现的内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
160
|
+
|
|
161
|
+
## 已知限制与延期工作
|
|
162
|
+
|
|
163
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
这些限制说明 guard 何时不合适。它们是当前包约束,不是任务积压。
|
|
167
|
+
|
|
168
|
+
- **仅精确匹配检测**——规范化是深度键排序,因此近似变体(稍作修改的路径、值内多余的空白)会绕过链;在没有需求证据前,不采用模糊匹配。
|
|
169
|
+
- **压缩(compaction)不会重置链**——跨越压缩检查点的链会继续计数。
|
|
170
|
+
- **仅提供建议**——尚未实现高阈值时升级为阻止形式,但 `PostToolDecision` 已支持阻止。
|
|
171
|
+
- **subagent 之间不共享链**——链始终按 agent 隔离;父 agent 与其 subagent 重复相同调用也绝不合并。
|
|
172
|
+
- **合理的幂等轮询超过阈值后仍会收到提醒**——可通过 `thresholds`/`exclude` 配置释放压力。
|
|
173
|
+
- **超过最高阈值后链不再提醒**——提醒只在精确达到所配置的次数时触发,超过后不会继续发送。
|
|
174
|
+
|
|
175
|
+
<a id="dev-note"></a>
|
|
176
|
+
### 开发备注
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
180
|
+
|
|
181
|
+
本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文、包代码和相关 Agent Note 为准。
|
|
182
|
+
|
|
183
|
+
[repeat-tool-guard Agent Note](../../../.agents/notes/archived/feature/2026-07-08-repeat-tool-guard.md) 以旧包名记录了原始设计与备选方案;[改名台账](../../../.agents/notes/archived/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.md) 记录了改名为 `repeat-tool-reminder` 及其原因。
|
|
184
|
+
|
|
185
|
+
</details>
|