What happened
Agents whose tools emit large arguments (long MCP query SQL, multi-line bash_tool/openpyxl scripts) intermittently fail a run with:
Tool call changed after eager execution started; refusing to re-run the tool to avoid duplicate side effects.
The model treats the guard error as a failed tool call and retries. Because the tool arguments are large, the eager-prestarted arguments and the finally-assembled arguments differ again on the retry, re-triggering the same guard. The loop consumes the recursion limit and the run ends with Recursion limit of 50 reached after a long wall-clock time.
Root cause
eagerEventToolExecution prestarts a tool as soon as its arguments parse from the stream. stream.ts's isEagerToolExecutionEnabledForBatch gates the whole batch on graph.eagerEventToolExecution?.enabled === true, and ToolNode compares the prestarted args to the final args with recordArgsEqual; any difference returns the "changed after eager execution started" error instead of running the tool.
main already excludes the highest-risk tools via excludeToolNames (create_file, edit_file, execute_code, bash_tool, ask_user_question). That covers file/code tools, but not custom or MCP tools that also emit large arguments — e.g. a run_select_query-style MCP tool with a long SQL string still trips the guard and loops.
Proposal
Make eager tool execution disablable per agent. Some agents (customer-analytics/QA agents that build large SQL and spreadsheets) benefit from turning it off entirely; most agents should keep it on for the latency win.
Concretely: an optional eager_execution boolean on the agent (mirroring the existing recursion_limit field), read when building the run config:
eagerEventToolExecution: {
enabled: agents[0]?.eager_execution !== false,
excludeToolNames: [ /* unchanged */ ],
},
- Unset (default) →
enabled: true — no behavioural change for any existing agent.
false → eager prestart skipped for the run; tools run once full arguments are assembled.
This composes with the existing excludeToolNames list rather than replacing it, and gives operators a switch for agents whose custom/MCP tools aren't (and can't easily be) enumerated in excludeToolNames.
Happy to open a PR (schema → validation → data-provider → run config → an "Eager Tool Execution" toggle in the agent Advanced panel), following the recursion_limit field as the pattern.
Environment
- Repro seen on agents backed by Bedrock (Claude Sonnet) at temperature 1 with an MCP query tool and the bash/create_file code tools.
- Higher temperature makes the eager-vs-final argument mismatch more frequent, which is why it presents as "worked before, fails now" after a temperature change.
What happened
Agents whose tools emit large arguments (long MCP query SQL, multi-line
bash_tool/openpyxl scripts) intermittently fail a run with:The model treats the guard error as a failed tool call and retries. Because the tool arguments are large, the eager-prestarted arguments and the finally-assembled arguments differ again on the retry, re-triggering the same guard. The loop consumes the recursion limit and the run ends with
Recursion limit of 50 reachedafter a long wall-clock time.Root cause
eagerEventToolExecutionprestarts a tool as soon as its arguments parse from the stream.stream.ts'sisEagerToolExecutionEnabledForBatchgates the whole batch ongraph.eagerEventToolExecution?.enabled === true, andToolNodecompares the prestarted args to the final args withrecordArgsEqual; any difference returns the "changed after eager execution started" error instead of running the tool.mainalready excludes the highest-risk tools viaexcludeToolNames(create_file,edit_file,execute_code,bash_tool,ask_user_question). That covers file/code tools, but not custom or MCP tools that also emit large arguments — e.g. arun_select_query-style MCP tool with a long SQL string still trips the guard and loops.Proposal
Make eager tool execution disablable per agent. Some agents (customer-analytics/QA agents that build large SQL and spreadsheets) benefit from turning it off entirely; most agents should keep it on for the latency win.
Concretely: an optional
eager_executionboolean on the agent (mirroring the existingrecursion_limitfield), read when building the run config:enabled: true— no behavioural change for any existing agent.false→ eager prestart skipped for the run; tools run once full arguments are assembled.This composes with the existing
excludeToolNameslist rather than replacing it, and gives operators a switch for agents whose custom/MCP tools aren't (and can't easily be) enumerated inexcludeToolNames.Happy to open a PR (schema → validation → data-provider → run config → an "Eager Tool Execution" toggle in the agent Advanced panel), following the
recursion_limitfield as the pattern.Environment