Skip to content

Count failed rounds, not calls, and ask at the failure limit - #153

Merged
fuseraft merged 1 commit into
mainfrom
feat/repl-failure-limit-per-round
Oct 18, 2026
Merged

fuseraft merged 1 commit into
mainfrom
feat/repl-failure-limit-per-round

Conversation

@fuseraft

Copy link
Copy Markdown
Owner

The consecutive-failure cutoff counted each failing tool result and stopped the turn at 3, which cut off legitimate work:

  • A model that made three calls at once in one round was stopped by that one round.
  • A model trying three different queries a server refused was stopped before it could answer. This came up testing sql-mcp: SQL work often needs a few attempts.

This PR brings the cutoff in line with Cline's MistakeTracker (sdk/packages/core/src/runtime/safety/mistake-tracker.ts).

Count failed rounds, not calls

A round is the set of tool calls from one model reply. It counts once, and only if every call in it failed; any call that works resets the count. ReplToolLoopGuard counts the same way, using FunctionCallIndex/FunctionCount to tell when a round has finished, so its "one more round will stop this turn" notice still lands one round before the cutoff. The default stays at 3 (repl.maxConsecutiveToolFailures): 3 rounds in which everything failed is about as lenient as Cline's 6 mistakes.

Ask at the cutoff when someone can answer

As Cline does on its mistake limit, the REPL asks instead of stopping:

3 rounds of tool calls in a row failed; the last: 'DELETE' is not permitted in a read-only query.
› Keep going
  Keep going, with a note for the model
  Stop the turn
  • The note goes to the model as an interjection, which ReplInterjectingChatClient adds before its next call.
  • The plain terminal asks Keep going? (y/N).
  • Keeping going restarts the count.
  • Plan steps still stop at the cutoff and are handled by their own step logic, as before.

When nobody can answer, stop and then reply once without tools

Under --yolo, in the VS Code panel, or when input isn't a terminal, the turn stops as before. The model then gets one reply with ToolMode.None, so the turn ends with it explaining what it tried, what went wrong and what to do next, instead of ending on a failed tool result. The tools stay declared for that reply, because some providers reject a history containing tool calls when no tools are declared. The prompt for that reply uses an internal correction prefix, so it isn't shown or replayed as user input.

Testing

  • New tests:
    • three parallel failures count as one round;
    • a round with one success resets the count;
    • the guard's notice lands on a round's last result and comes back one round short of the next cutoff;
    • the reply with tools off sets ToolMode.None and keeps the tools declared;
    • an unattended turn stops and then gets exactly one reply without tools.
  • Five existing tests updated for per-round counting.
  • Full suite: 3,330 passed.
  • Tried live with sql-mcp refusals:
    • TUI: keep going with a note; the model got the note, ran the remaining query and reported all four.
    • --plain: answering y let the turn finish.
    • --yolo: the turn stopped, then the model gave its summary with no tools.

The failure cutoff counted each failing result, so a model that made three calls
at once in one round was stopped by that round alone, and one trying three
different queries a server refused was stopped before it could answer. It now
counts as Cline's MistakeTracker does: a round in which every call failed counts
once, and any call that works resets it. ReplToolLoopGuard counts the same way,
so its one-round-short notice still lands on the round before the cutoff.

At the cutoff, someone at the REPL is now asked whether to keep going, as Cline
asks on its mistake limit: the full-screen REPL offers to keep going, keep going
with a note for the model (read before its next call, as an interjection), or
stop; the plain terminal asks y/N. With no one to ask (--yolo, the VS Code panel,
input that isn't a terminal) the turn stops as before, then gets one reply with
tools off, so it ends with the model saying what it tried and what went wrong
rather than on a failed tool result.
@fuseraft
fuseraft merged commit 712f4d7 into main Oct 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant