Bound a turn's tool-call cycles and notice repeated identical calls
- Status: Todo
- Kind: Feature
- Authors: jp
- Date: 2026-09-18
- Label: domain=conversation
- Label: domain=tooling
- Label: package=jp_cli
- Label: type=feature
The turn loop cycles streaming to executing with no cap. run_turn_loop breaks only when the execution plan is empty, the user intervenes, or a stream error is fatal. Nothing counts the cycles, and nothing notices a repeated identical tool call.
TurnState::request_count is incremented at turn_loop.rs:373 and read nowhere. Its doc comment describes a check that does not exist: "Every retry increments this counter, until a maximum number of retries is reached, after which the turn ends in an error."
The guards today are a per-response byte ceiling (assistant.request.max_response_bytes), a per-stream idle timeout, and Ctrl-C. All three are per-request. None bounds a turn.
What it costs
A model that reissues the same failing call grinds until the user stops it, on the user's own API key. Unattended runs (run = "unattended", a persona driving a long task) have no user watching.
The harness study in T-0n0nwz3 caps each task at 300 steps and adds streak detection: a reminder once a streak reaches five identical calls, and early termination at eight consecutive identical failing calls. A streak is calls sharing a tool name and byte-identical arguments, so a paginated read at a new offset ends it, and permission denials do not count as failures. The machinery is small and the thresholds are theirs to borrow.
Shape
Two counters on TurnState, which already exists and already half-holds one:
- A cycle cap, configurable,
0meaning unbounded. - A streak counter over
(tool_name, arguments)byte-identity, excluding permission denials.
Where JP should diverge
Do not inject a reminder into the model input. That is the paper's only option; JP has better ones. In an attended session a streak should reach the user through the inquiry or interrupt surface, where they can redirect rather than watch. In an unattended one it should abort. RFD 104's promptability signal is how the loop tells those apart.
Picking either threshold is guesswork until per-turn usage is recorded (T-0n0pgw6), so the counters are worth landing before the numbers are argued about.
Findings and the rest of the proposals: T-0n0nwz3.