return_directly tool results are missing tool_id
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3585054. -->
Reported by: [tim bozeman](https://www.drupal.org/user/2241356)
Related to !259
>>>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>In <code>AiAgentEntityWrapper::determineSolvability()</code>, when a tool has <code>return_directly=true</code>, the tool result <code>ChatMessage</code> is created without setting the <code>tool_id</code>:</p>
<pre>// return_directly path (line ~497) — tool_id is NOT set:<br>$this->chatHistory[] = new ChatMessage('tool', $output);<br><br>// Normal path (lines ~501-502) — tool_id IS set:<br>$message = new ChatMessage('tool', $output);<br>$message->setToolsId($tool->getToolsId());<br>$this->chatHistory[] = $message;</pre><p>This causes two problems when the agent is later resumed from tempstore:</p>
<ol>
<li><strong>LLM API validation errors:</strong> The API requires every <code>tool_use</code> block to have a corresponding <code>tool_result</code> block that references its ID. Because the <code>return_directly</code> path omits the ID, the API returns a 400 error:<br>
<pre>messages.2: `tool_use` ids were found without `tool_result` blocks immediately after:<br>toolu_bdrk_01DMVVSnJ3TxFESjsYbNLeb8. Each `tool_use` block must have a corresponding<br>`tool_result` block in the next message.</pre></li>
<li><strong>Breaks resume-state tool tracking:</strong> When an agent is resumed, the runner needs to determine whether there are unresolved tool calls (tool_use without matching tool_result) to decide whether to inject the user's new message or let the agent continue processing. Without <code>tool_id</code> on <code>return_directly</code> results, resolved tools appear unresolved, preventing the user's message from being appended and causing the agent to re-process stale state.</li>
</ol>
<p>The agent is saved to tempstore whenever it is not marked as finished. This occurs in two common scenarios: verbose/streaming mode (where <code>setLooped(FALSE)</code> causes the agent to save between iterations) and <code>return_directly</code> tools (where the early return prevents <code>finished=true</code> from being set). In both cases, the missing <code>tool_id</code> causes failures on the next user message.</p>
<h4 id="summary-steps-reproduce">Steps to reproduce (required for bugs, but not feature requests)</h4>
<ol>
<li>Configure an orchestration agent with a sub-agent tool that has <code>return_directly</code> enabled (e.g. a routing chatbot with a <code>page_builder</code> sub-agent).</li>
<li>Send a message that triggers the <code>return_directly</code> tool. The agent saves to tempstore because <code>return_directly</code> returns <code>JOB_SOLVABLE</code> without setting <code>finished=true</code>.</li>
<li>Send a follow-up message in the same thread so the agent is restored from tempstore.</li>
<li>Observe either: (a) a 400 error from the LLM API about missing <code>tool_result</code> blocks, or (b) incorrect resume behavior where the agent treats resolved tools as unresolved.</li>
</ol>
<p>This also reproduces in verbose/streaming mode where the agent saves state between loop iterations.</p>
<p>Environment: Drupal AI Agents module 1.2.x, any LLM provider that validates tool call/result pairing (e.g. Anthropic Claude API).</p>
<p><strong>Testing with Entity Blueprint:</strong> The <a href="https://www.drupal.org/project/entity_blueprint">Entity Blueprint</a> contrib module provides a concrete setup for reproducing this. Its <code>entity_blueprint_ai</code> sub-module exposes a <code>page_builder</code> agent with entity blueprint tools. Configure a routing chatbot with the page builder as a <code>return_directly</code> sub-agent, then trigger a multi-turn flow (e.g. content creation requiring workspace selection). On the second message, the missing <code>tool_id</code> causes either API errors or incorrect resume behavior.</p>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>Set the <code>tool_id</code> on the <code>ChatMessage</code> in the <code>return_directly</code> path, matching the existing normal tool result handling:</p>
<pre>if ($this->toolShouldReturnDirectly($tool)) {<br> $message = new ChatMessage('tool', $output);<br> $message->setToolsId($tool->getToolsId());<br> $this->chatHistory[] = $message;<br> $this->question = $output;<br> return PluginInterfacesAiAgentInterface::JOB_SOLVABLE;<br>}</pre><p>This is a two-line change that aligns the <code>return_directly</code> path with lines 501-502 directly below it.</p>
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
<ul>
<li>Review and commit the patch.</li>
</ul>
issue