feat(ai-chat): make the agent tool call limit configurable and pause instead of failing (#1987) - #1992
Open
datlechin wants to merge 2 commits into
Open
feat(ai-chat): make the agent tool call limit configurable and pause instead of failing (#1987)#1992datlechin wants to merge 2 commits into
datlechin wants to merge 2 commits into
Conversation
…instead of failing (#1987) Claude-Session: https://claude.ai/code/session_01AMvg6WkK8Ngn3Wo2dx7erA
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
…already in the panel Claude-Session: https://claude.ai/code/session_01AMvg6WkK8Ngn3Wo2dx7erA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1987.
Problem
Agent mode aborted long investigations with "AI made too many tool calls in one response. Try simplifying the request."
Two defects behind it:
AIChatViewModel+Streaming.swift:11). Every comparable tool defaults to 20-25 (VS Code Copilotchat.agent.maxRequests25, Cursor 25, Zed ~25). 10 was the lowest anywhere.failTooManyRoundtripsthen deleted the assistant turn and set.failed(nil), which surfaced a Retry button that restarted from round 0 into the same wall.No comparable product does this. Cursor, VS Code, Zed, Windsurf, Roo, and Anthropic's own
pause_turnall treat the budget as a pause that preserves the transcript and offers one-click continue. Apple's HIG agrees: don't alert merely to inform, integrate status feedback near the content it describes, and avoid unnecessary limits on retryable work.Change
Loop.
while true, counting only executed rounds and checking the budget before streaming the next one, so no round is consumed and discarded.failTooManyRoundtripsis replaced bypauseAtToolLimit, which keeps every tool call and result, persists the conversation, and posts a VoiceOver announcement.State. New
StreamingState.pausedAtToolLimit(count:).lastMessageFaileddeliberately does not match it, so the from-scratch Retry button no longer appears on a pause.startStreaming's guard now admits the paused state, so typing a new message while paused works without per-caller patching.Resume.
beginStreamingTurnis extracted fromstartStreaming()and shared with a newcontinueToolLoop(). After a pause the transcript already ends in a user turn of tool results, so the existingmessages.dropLast()wire rebuild resumes with full context and a fresh budget.Chat UI. An inline
pause.circlerow on the last assistant message, following the existing Retry/Regenerate accessory pattern: "Paused after 25 tool calls." plus Continue and Adjust Limit (deep-links via the existingWindowOpener.shared.openSettings(tab: .ai)).Setting. New Agent section in Settings > AI, mirroring the existing toggle-gates-stepper pattern.
maxToolRoundtripsEnabled: Bool+maxToolRoundtrips: Int, range 5-200, default 25. AnOptional<Int>was rejected becausedecodeIfPresentcannot tell "key absent in old JSON" from "user chose unlimited".hardToolRoundtripCeiling = 500backstops the no-limit case so a stuck agent still stops.Notes
toolUseOrder, so the limit never applied to it and still doesn't. Documented rather than guarded, since a guard would do nothing.AppServiceshas no injectable settings double, soAIChatViewModel()uses.liveandpauseAtToolLimitcallspersistCurrentConversation()exactly as the normal completion path does. A fullAppServicestest double was out of scope here.Tests
New
AIChatViewModelToolLoopTestswith a scriptedChatTransportdouble, covering: pauses after exactly N executed rounds, round N+1 is never streamed (the regression test for the wasted round), pause is not a failure state, pause preserves the transcript and ends on a tool result turn, no-limit runs to completion, and Continue resumes. PlusAISettingsdefault/decode/clamp tests and a migration assertion.Verification
swiftlintis not installed on the machine this was written on, so the lint gate did not run.https://claude.ai/code/session_01AMvg6WkK8Ngn3Wo2dx7erA