Tool Execution Architecture

Tool execution is split into a generic execution core and a Data Machine product wrapper.

ComponentFileResponsibility
ToolPolicyResolverinc/Engine/AI/Tools/ToolPolicyResolver.phpBuilds the visible tool set for a request.
ToolExecutionCoreinc/Engine/AI/Tools/Execution/ToolExecutionCore.phpValidates required parameters, builds complete parameters, executes ability-only or class/method tools.
ToolExecutorinc/Engine/AI/Tools/ToolExecutor.phpApplies action policy, stages preview actions, runs direct execution, records post-origin tracking.
ToolParametersinc/Engine/AI/Tools/ToolParameters.phpMerges AI arguments with payload, data packet, handler config, and tool metadata.
ActionPolicyResolverinc/Engine/AI/Actions/ActionPolicyResolver.phpDecides whether one invocation is direct, preview, or forbidden.
PendingActionHelperinc/Engine/AI/Actions/PendingActionHelper.phpStores approval-required invocations and returns the Agents API approval envelope.

Runtime Flow

text
AI request setup
    |
    v
ToolPolicyResolver::resolve()
    |
    v
model tool call
    |
    v
ToolExecutor::executeTool()
    |
    +--> ToolExecutionCore::prepareToolCall()
    |       - tool exists in available tools
    |       - required parameters are present
    |       - ToolParameters::buildParameters()
    |
    +--> ActionPolicyResolver::resolveForTool()
    |       - direct / preview / forbidden
    |
    +--> forbidden: return error
    |
    +--> preview: PendingActionHelper::stage()
    |
    +--> direct: ToolExecutionCore::executePreparedTool()
            - ability-only tool: WP_Ability permissions + execute()
            - class/method tool: instantiate class and call method

Resolving Tools Before Execution

All runtime tool sets should come from ToolPolicyResolver::resolve().

Pipeline example:

php
use DataMachineEngineAIToolsToolPolicyResolver;

$resolver = new ToolPolicyResolver();

$tools = $resolver->resolve( array(
    'mode'                 => ToolPolicyResolver::MODE_PIPELINE,
    'agent_id'             => $agent_id,
    'previous_step_config' => $previous_step_config,
    'next_step_config'     => $next_step_config,
    'pipeline_step_id'     => $flow_step_id,
    'engine_data'          => $engine_data,
    'allow_only'           => $policy_args['allow_only'] ?? array(),
    'deny'                 => $policy_args['deny'] ?? array(),
) );

Chat example:

php
$tools = $resolver->resolve( array(
    'mode'           => ToolPolicyResolver::MODE_CHAT,
    'agent_id'       => $agent_id,
    'client_context' => array(
        'session_id' => $session_id,
    ),
) );

ToolExecutor expects the already-resolved $available_tools array. It does not rediscover tools.

Executing a Tool

php
use DataMachineEngineAIActionsActionPolicyResolver;
use DataMachineEngineAIToolsToolExecutor;

$result = ToolExecutor::executeTool(
    $tool_name,
    $tool_parameters,
    $available_tools,
    array(
        'job_id'           => $job_id,
        'data'             => $data_packets,
        'flow_step_id'     => $flow_step_id,
        'flow_step_config' => $flow_step_config,
    ),
    ActionPolicyResolver::MODE_PIPELINE,
    $agent_id
);

Signature:

php
public static function executeTool(
    string $tool_name,
    array $tool_parameters,
    array $available_tools,
    array $payload,
    string $mode = ActionPolicyResolver::MODE_CHAT,
    int $agent_id = 0,
    array $client_context = array()
): array

The $mode, $agent_id, and $client_context values only affect action policy. Tool visibility has already been resolved before execution.

Generic Execution Core

ToolExecutionCore::prepareToolCall() returns either a normalized error or a prepared invocation:

php
array(
    'ready'      => true,
    'tool_def'   => $tool_def,
    'parameters' => $complete_parameters,
)

Preparation checks that the tool exists in $available_tools, validates required parameters declared in parameters, and calls ToolParameters::buildParameters().

ToolExecutionCore::executePreparedTool() supports two executable shapes:

ShapeRequired keysExecution path
Ability-onlyability, no class, no methodResolve ability from WP_Abilities_Registry, check permissions, call execute().
Class/methodclass, methodInstantiate class and call the method with complete parameters and tool definition.

Ability-only tools are useful when the WordPress Ability is already the source of truth for permission and behavior. Class/method tools remain valid for Data Machine handler tools and product-specific adapters.

Parameter Building

ToolParameters::buildParameters() merges model-provided arguments with Data Machine payload context.

Common payload values include:

Payload keyPurpose
job_idJob currently running.
dataData packets from previous steps.
flow_step_idCurrent flow-step identifier.
flow_step_configCurrent flow-step snapshot.
handler_configHandler-specific runtime configuration.
session_idChat session identifier when called from chat.

The AI’s tool parameters win over defaults extracted from the payload. Handler tools also receive handler_config, engine data such as source_url and image_url, and the resolved tool_definition.

Action Policy

Visibility and execution are separate. A tool can be visible to the model and still be prevented from executing directly.

ActionPolicyResolver::resolveForTool() returns the Agents API vocabulary:

PolicyEffect
directExecute immediately.
previewStage the invocation and return an approval-required envelope.
forbiddenReturn a refusal error without running the tool.

Resolution inputs include tool_name, tool_def, mode, agent_id, client_context, and optional deny.

Resolution order is:

  1. Explicit deny list.
  2. Per-agent agent_config.action_policy.tools[tool_name].
  3. Per-agent agent_config.action_policy.categories[category].
  4. Tool-declared default, including per-mode keys such as action_policy_chat.
  5. Mode preset from DataMachineModeActionPolicyProvider.
  6. Global default from Agents API.
  7. datamachine_tool_action_policy filter.

Chat can stage publish-family tools for preview because a user is present. Pipeline and system modes default to direct for automation unless a tool or policy says otherwise.

Pending Actions

When action policy resolves to preview, ToolExecutor stages the invocation if the tool declares action_kind. Staging uses PendingActionHelper::stage().

php
$staged = PendingActionHelper::stage( array(
    'kind'         => 'socials_publish_instagram',
    'summary'      => 'Publish Instagram post: Spring menu is live',
    'apply_input'  => $complete_parameters,
    'preview_data' => array(
        'caption' => $complete_parameters['caption'] ?? '',
    ),
    'agent_id'     => $agent_id,
    'context'      => array(
        'mode'      => 'chat',
        'tool_name' => 'publish_instagram',
        'session_id' => $session_id,
    ),
) );

The result is an Agents API approval_required message with:

FieldPurpose
payload.pending_action.action_idIdentifier to resolve later.
payload.pending_action.summaryHuman-readable preview summary.
payload.pending_action.previewRenderable preview data.
payload.resolve_withTool name to call for resolution, currently resolve_pending_action.
payload.resolve_paramsRequired resolution arguments.
top-level staged and action_idData Machine compatibility fields for internal callers.

Tools should not hand-roll this envelope. Use PendingActionHelper so the store shape, approval message, and compatibility fields stay aligned.

Post-Origin Tracking

After direct execution succeeds, ToolExecutor calls PostTracking::extractPostId() and records origin metadata for results that created or modified a WordPress post. This applies to handler tools and ability-only tools when their result exposes an extractable post ID.

Error Shapes

Common normalized failures:

php
array(
    'success'   => false,
    'error'     => "Tool 'my_tool' not found",
    'tool_name' => 'my_tool',
)
php
array(
    'success'       => false,
    'error'         => 'Tool "publish_instagram" is not permitted in the current context (action_policy=forbidden).',
    'tool_name'     => 'publish_instagram',
    'action_policy' => 'forbidden',
)

Ability-only failures include the linked ability slug when ability registration, permission, or execution fails.