Add pre-execute and post-execute events to ToolBase::execute() to alter inputs and results
### Problem/Motivation Tool execution is currently a closed pipeline. `ToolBase::execute()` (`src/Tool/ToolBase.php`) does, in order: 1. `$args = $this->getExecutableValues();` — collect input values from the tool's input definitions, 2. `$this->result = $this->doExecute($args);` — run the concrete tool, 3. on success, map `$this->result->getContextValues()` onto the output definitions via `setOutputValue()`. There is no extension point anywhere in this flow. Once a tool is defined, a third party (another module) cannot: - adjust the **input definitions / input values** before the tool runs — e.g. inject a default, constrain or remap an argument, add context (current user, language, site), or enforce a policy on the incoming arguments; or - adjust the **result** after the tool runs — e.g. filter/redact output values, reshape the returned data, add metadata, or fail a result that violated a policy. Today the only way to influence either side is to alter the tool plugin itself. As the Tool API moves toward beta and broader inclusion in AI (#3558327), other modules and runners (agents, the AI connector, ECA, automators) increasingly need to wrap tool execution without owning the tool. A symmetric event around `execute()` is the idiomatic Drupal way to provide that. Note: `InputDefinitionRefinerInterface` already lets a *tool* refine its own inputs. This proposal is different — it provides a **cross-cutting** hook so modules other than the tool can participate in every tool's execution. ### Proposed resolution Dispatch two events from `ToolBase::execute()` via the event dispatcher (injected through `ToolBase::create()`): 1. **Pre-execute event** — dispatched after `getExecutableValues()` and before `doExecute()`. Subscribers receive the tool instance, its input definitions and the resolved input values, and may alter the definitions/values before execution. The (possibly modified) values are what gets passed to `doExecute()`. 2. **Post-execute event** — dispatched after `doExecute()` returns, before (or alongside) the output-mapping step. Subscribers receive the tool instance and the `ExecutableResultInterface`, and may alter the result — its message, success status, and/or context values — before output definitions are populated. Sketch: ```php public function execute(): static { try { $args = $this->getExecutableValues(); // Pre-execute: allow altering input definitions / values. $pre = new ToolPreExecuteEvent($this, $args); $this->eventDispatcher->dispatch($pre, ToolEvents::PRE_EXECUTE); $args = $pre->getValues(); $this->result = $this->doExecute($args); } catch (\Throwable $e) { // ... existing failure handling ... } // Post-execute: allow altering the result before output mapping. $post = new ToolPostExecuteEvent($this, $this->result); $this->eventDispatcher->dispatch($post, ToolEvents::POST_EXECUTE); $this->result = $post->getResult(); if ($this->result->isSuccess()) { // ... existing output mapping ... } return $this; } ``` New classes: - `Drupal\tool\Event\ToolEvents` — constants `PRE_EXECUTE`, `POST_EXECUTE`. - `Drupal\tool\Event\ToolPreExecuteEvent` — carries the tool, input definitions and mutable input values. - `Drupal\tool\Event\ToolPostExecuteEvent` — carries the tool and the mutable `ExecutableResultInterface`. Open questions for discussion: - Should the pre-execute event expose the **input definitions** (`getInputDefinitions()`) for mutation in addition to the resolved values, or only the values? The issue title asks for input *definitions*; exposing both is more powerful but widens the contract. - Should post-execute also fire on the **failure** paths (caught `\Throwable`), so subscribers can observe/transform errors, or only on the result returned by `doExecute()`? - Does the event need to allow **short-circuiting** execution (a subscriber supplying a result so `doExecute()` is skipped), or is altering inputs/results enough for now? - `execute()` is not declared `final`; if events are added here, subclasses overriding `execute()` would bypass them — should `execute()` become `final` with a `doExecute()`-only extension contract? ### Alternatives considered - **Per-tool refinement only** (`InputDefinitionRefinerInterface`) — already exists but only lets a tool refine *its own* inputs; it does not give other modules a cross-cutting hook, and does nothing for results. - **Wrapping tools at the call site** (each consumer — agent runner, AI connector, ECA — implements its own before/after logic) — duplicates logic, is inconsistent, and only covers the call sites that opt in rather than every `execute()`. - **A plugin/decorator layer over `ToolManager`** — heavier than needed; events are the lighter, more conventional Drupal mechanism and compose well with existing subscribers. ### AI usage (if applicable) - [x] **AI Assisted Issue:** This issue was generated with AI assistance, but was reviewed and refined by the creator. - [ ] **AI Assisted Code:** This code was mainly generated by a human, with AI autocompleting or parts AI generated, but under full human supervision. - [ ] **AI Generated Code:** This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human. - [ ] **Vibe Coded:** This code was generated by an AI and has only been functionally tested.
issue