pub struct RepeatToolLimitMiddleware { /* private fields */ }Expand description
Break poll loops — repeated identical tool calls — hard.
Session 20260914_50cf809d: a root spawned two sub-agents, then polled
list_agents 124 times over 15 minutes (57% of wall clock, interleaved
with real work) instead of ending its turn to receive the pushed reports.
The system prompt forbade polling; the model did it anyway, and no
mechanism stopped it. This middleware is that mechanism.
Counting is cumulative per run, not consecutive: the incident’s polls
were interleaved with read_file etc., so a consecutive-counter would
reset forever and never fire. The fingerprint is tool name + canonical
(key-sorted) arguments — a model legitimately reading 51 different
files never accumulates a fingerprint.
Two-stage response, mirroring existing middleware precedents:
- at
nudge_after(once per fingerprint):follow_up_messagenudge (soft, likeMaxTurnsNudgeMiddleware); - at
block_after(and every attempt beyond): pendingtool_callsare discarded and a follow-up forces a summary (hard, likeTurnToolLimitMiddleware).
State is per-session and reset by on_user_message — a run’s counts must
not leak into the next run (the middleware instance is process-long in
phimint). Clearing all pending calls on block (not just the offending
one) follows the TurnToolLimitMiddleware precedent: the breaker only
trips after block_after identical calls, so collateral is negligible.