Skip to content

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
mainfrom
fix/ai-tool-call-limit
Open

feat(ai-chat): make the agent tool call limit configurable and pause instead of failing (#1987)#1992
datlechin wants to merge 2 commits into
mainfrom
fix/ai-tool-call-limit

Conversation

@datlechin

Copy link
Copy Markdown
Member

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:

  1. The limit was hardcoded at 10 (AIChatViewModel+Streaming.swift:11). Every comparable tool defaults to 20-25 (VS Code Copilot chat.agent.maxRequests 25, Cursor 25, Zed ~25). 10 was the lowest anywhere.
  2. Hitting the limit was treated as a failure and threw the answer away. The check fired after round 10 had already streamed, so a paid LLM round was discarded and only 9 rounds ever executed. failTooManyRoundtrips then 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_turn all 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. failTooManyRoundtrips is replaced by pauseAtToolLimit, which keeps every tool call and result, persists the conversation, and posts a VoiceOver announcement.

State. New StreamingState.pausedAtToolLimit(count:). lastMessageFailed deliberately 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. beginStreamingTurn is extracted from startStreaming() and shared with a new continueToolLoop(). After a pause the transcript already ends in a user turn of tool results, so the existing messages.dropLast() wire rebuild resumes with full context and a fresh budget.

Chat UI. An inline pause.circle row 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 existing WindowOpener.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. An Optional<Int> was rejected because decodeIfPresent cannot tell "key absent in old JSON" from "user chose unlimited". hardToolRoundtripCeiling = 500 backstops the no-limit case so a stuck agent still stops.

Notes

  • Copilot is unaffected. It runs its own tool loop and never populates toolUseOrder, so the limit never applied to it and still doesn't. Documented rather than guarded, since a guard would do nothing.
  • The new loop tests persist conversations to disk. AppServices has no injectable settings double, so AIChatViewModel() uses .live and pauseAtToolLimit calls persistCurrentConversation() exactly as the normal completion path does. A full AppServices test double was out of scope here.

Tests

New AIChatViewModelToolLoopTests with a scripted ChatTransport double, 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. Plus AISettings default/decode/clamp tests and a migration assertion.

Verification

  • Writing-style gate clean.
  • swiftlint is not installed on the machine this was written on, so the lint gate did not run.
  • Not yet built locally.

https://claude.ai/code/session_01AMvg6WkK8Ngn3Wo2dx7erA

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mintlify

mintlify Bot commented Jul 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 29, 2026, 1:12 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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.

[Request] Option for AI Agent mode to extend or remove limit of tool calls

1 participant