Add a per-tool setting to restrict a tool from being called more than once per response
## Summary Add a per-tool agent setting — "Restrict model from calling this tool more than once in a response." When enabled for a tool, if a single model response contains more than one call to that tool, execute none of the response's tool calls and return a configurable error message so the agent retries on the next step. Tools without the setting are unaffected and may still be called multiple times. ## Why A step-wise, externally-driven agent needs certain tools to run at most once per step, so each call can build on the previous call's result. Canvas's client-side orchestration loop requires this for `place_components`: it places one component per step and references the returned UUID when placing the next, so two `place_components` calls in one response cannot work — while other tools (e.g. `edit_components`) may legitimately be called several times in one response. Because it applies to specific tools only, it is a per-tool setting. - Roadmap: https://git.drupalcode.org/project/canvas/-/work_items/3591777 - Consumer (POC): https://git.drupalcode.org/project/canvas/-/merge_requests/1214 ## Current behavior `AiAgentEntityWrapper::determineSolvability()` parks every tool call in `$response->getTools()` (`src/PluginBase/AiAgentEntityWrapper.php:680`, loop at 683–690); all execute. ai_agents already carries a per-tool `tool_settings` map (`require_usage`, `use_artifacts` — lines 1147, 1183) that this extends. No per-tool call-count guard exists. ## Proposed - Add `restrict_multiple_calls` (bool, default `FALSE` = current behavior) and `multiple_call_error_message` (string, with a sensible default) to per-tool `tool_settings` — config schema + agent form + runtime-overridable via `functionsOverride`, matching the existing keys. - In `determineSolvability()`, before parking tools (line 683): count calls per plugin id in the response. If any tool whose `tool_settings` has `restrict_multiple_calls` appears more than once, park nothing, append the configured error message to `chatHistory` (like line 700), and return so the agent retries. ## Tests - Flagged tool called twice → nothing parked, error message in `chatHistory`, execution defers. - Flagged tool ×2 + a second (unflagged) tool ×1 → all rejected. - Flagged tool ×1 + an unflagged tool ×2 → all execute. - No flagged tool present → all execute (locks backward compatibility). --- _Issue generated with AI assistance._
issue