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