Problem / Motivation
Agent-style tool use was handled by a separate, @internal AgentExecutorInterface and a dedicated execution
path (SynchronousOrchestrator::executeAgentNode()) routed to a nullable agent-executor service. This
path:
- bypassed the single node-execution path (NodeRuntimeService::executeNode()), so it did not share its logging, status updates, events, and interrupt handling;
- leaked compiler/runtime internals (DependencyGraph, WorkflowDTO) into the executor signature;
- treated "agent" as a special node type rather than a capability, so only one bespoke consumer could ever use tools.
Solution
Tool consumption is now a node-processor capability. Any processor can declare that it consumes the tools
wired to it by implementing ToolsAwareInterface (with ToolsAwareTrait for the standard implementation).
The runtime resolves a node's tool_availability edges into a ToolCollection and injects it via setTools()
before process() runs — the same pattern as ExecutionContextAwareInterface.
Tool invocation is mediated by the orchestrator: each ToolBinding::invoke() routes through a per-consumer,
allow-list-enforcing invoker (authorization is derived from the graph, not from the model's output), and
tools execute through the normal executeNode() path.
An "agent" is therefore just a processor that implements ToolsAwareInterface. AgentExecutorInterface,
AgentTraceInterface, and the executeAgentNode() special-casing are deprecated and will be removed.
Before
// A bespoke agent executor (provided by a separate agent module),
// receiving compiler/runtime internals, outside the single node path.
final class MyAgentExecutor implements AgentExecutorInterface {
public function execute(
string $executionId,
string $nodeId,
array $inputData,
array $config,
WorkflowDTO $workflow,
string $pipelineId,
DependencyGraph $dependencyGraph,
NodeExecutionContext $context,
): AgentTraceInterface {
// Manually discover tools from $dependencyGraph, invoke them,
// and re-implement logging/status/events here.
}
}After
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ToolsAwareInterface;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ToolsAwareTrait;
class MyAgentProcessor extends AbstractFlowDropNodeProcessor implements ToolsAwareInterface {
use ToolsAwareTrait;
public function process(ParameterBagInterface $params): array {
// Build the model's tool manifest from the wired tools.
$definitions = $this->getTools()->getDefinitions();
// ... call the model, get a tool call by name ...
$binding = $this->getTools()->get($toolName);
if ($binding !== NULL) {
$result = $binding->invoke($args); // authorized + executed by the orchestrator
// feed $result->getData() back into the loop
}
return ['output' => $finalAnswer];
}
}
The node runs through the normal execution path; no special agent node type, no
DependencyGraph/WorkflowDTO in plugin code, and authorization of every tool call is enforced by the
orchestrator.
How to update
- Replace any AgentExecutorInterface implementation with a FlowDropNodeProcessor that implements
ToolsAwareInterface (use ToolsAwareTrait).
- Access wired tools with getConnectedTools() / getTools(); invoke them with ToolBinding::invoke() — do
not execute nodes directly.
- Stop relying on executeAgentNode() / the agent-executor service; agent nodes execute via the standard
node path with tools injected.
- Tool names are derived from node labels and must be unique within a node's tool set (enforced at compile
time).
Remaining tasks / notes
- AgentExecutorInterface was marked @internal; this change is informational for anyone who built against
it.
- (If you ship a deprecate-and-bridge release: note that the old agent path now delegates to the new
capability and will be removed in 2.0.0.)