Tool Manager

ToolManager is the Data Machine adapter for the AI tool registry. It owns Data Machine’s datamachine_tools compatibility registry, lazy definition resolution, handler-tool expansion, configuration checks, and global enablement checks. It does not decide the final per-request tool set by itself. Runtime visibility is resolved by ToolPolicyResolver through ToolSourceRegistry.

Current Shape

text
datamachine_tools filter
        |
        v
ToolManager
  - get_all_tools()
  - get_raw_tools()
  - resolveHandlerTools()
  - is_tool_available()
        |
        v
ToolSourceRegistry
  - static_registry
  - adjacent_handlers
        |
        v
ToolPolicyResolver::resolve()

Use ToolManager when code needs to read Data Machine’s registered tool definitions or resolve handler-owned tools. Use ToolPolicyResolver::resolve() when code needs the tool set an agent may actually see for a chat, pipeline, or system request.

Registry Contract

All product and extension tools register through datamachine_tools.

php
add_filter( 'datamachine_tools', function ( array $tools ): array {
    $tools['my_plugin/search'] = array(
        '_callable'     => array( MyToolDefinitions::class, 'search' ),
        'modes'         => array( 'chat' ),
        'ability'       => 'my-plugin/search',
        'access_level'  => 'admin',
    );

    return $tools;
} );

The _callable wrapper defers the full definition until a resolver needs it. The resolved definition must include the model-facing shape:

php
array(
    'description'     => 'Search the My Plugin index.',
    'parameters'      => array(
        'query' => array(
            'type'        => 'string',
            'required'    => true,
            'description' => 'Search terms.',
        ),
    ),
    'class'           => MySearchTool::class,
    'method'          => 'handle_tool_call',
    'modes'           => array( 'chat' ),
    'requires_config' => false,
)

Ability-only tools omit class and method and execute through the linked WordPress Ability:

php
array(
    'description' => 'Create a wiki note.',
    'parameters'  => array( /* JSON-schema-like parameter map */ ),
    'ability'     => 'intelligence/create-wiki-note',
    'modes'       => array( 'chat' ),
)

ToolExecutionCore treats a definition with ability and no class/method as ability-only. It checks ability permissions and calls WP_Ability::execute() directly.

Handler Tools

Pipeline handler tools are dynamic because their parameter schemas depend on the adjacent flow-step handler config. Register them as _handler_callable entries in datamachine_tools, usually through HandlerRegistrationTrait::registerHandler().

php
add_filter( 'datamachine_tools', function ( array $tools ): array {
    $tools['__handler_tools_wordpress_publish'] = array(
        '_handler_callable' => function ( string $handler_slug, array $handler_config, array $engine_data ): array {
            return array(
                'wordpress_publish' => array(
                    'class'          => WordPressPublishTool::class,
                    'method'         => 'handle_tool_call',
                    'handler'        => $handler_slug,
                    'handler_config' => $handler_config,
                    'description'    => 'Publish the processed item to WordPress.',
                    'parameters'     => build_parameters_from_handler_config( $handler_config ),
                ),
            );
        },
        'handler'      => 'wordpress_publish',
        'modes'        => array( 'pipeline' ),
        'access_level' => 'admin',
    );

    return $tools;
} );

ToolManager::resolveHandlerTools() supports two matching shapes:

Entry keyMeaning
handlerExact adjacent handler slug match.
handler_typesMatch any adjacent handler whose registered type is in the list. Used by cross-cutting tools such as skip_item.

Resolved handler tools automatically receive handler, handler_config, inherited access_level, inherited ability, and modes when the callback omits them.

Source Registry

ToolSourceRegistry composes named sources before policy filtering:

SourceClassUsed by default in
static_registryDataMachineToolRegistrySourcechat, pipeline, system, custom modes
adjacent_handlersAdjacentHandlerToolSourcepipeline

DataMachineToolRegistrySource reads ToolManager::get_all_tools(), filters by each tool’s modes, applies global enablement/configuration checks, and adapts policy-gated pipeline tools.

AdjacentHandlerToolSource reads the previous and next step configs, gathers configured handler slugs through FlowStepConfig, and asks ToolManager::resolveHandlerTools() for each adjacent handler’s dynamic tool definitions.

The generic extension hooks are agents_api_tool_sources and agents_api_tool_sources_for_mode. Data Machine’s legacy datamachine_tool_sources filters are not mirrored.

Modes

Tool definitions declare the request modes that may see them:

ModeConstantUse
pipelineToolPolicyResolver::MODE_PIPELINEAutomated AI pipeline steps. Includes adjacent handler tools plus static registry tools explicitly marked for pipeline.
chatToolPolicyResolver::MODE_CHATUser-present chat sessions. Runs the chat access gate and ability permission checks.
systemToolPolicyResolver::MODE_SYSTEMBackground/system jobs with a small explicit tool set.

Runtime requests may activate multiple modes at once, for example ['pipeline', 'world']. A tool is eligible when its declared modes intersects with any active mode. Powerful tools can add requires_opt_in => true; those tools must also appear in allow_only, usually from enabled_tools in the flow-step snapshot.

Availability Checks

ToolManager::is_tool_available( $tool_id, $context_id ) combines global enablement and configuration state. It is a source-level gate, not the full policy resolver.

php
$manager = new ToolManager();

if ( $manager->is_tool_available( 'local_search' ) ) {
    // The static registry source may include it, subject to mode and policy.
}

requires_config => true tools must pass datamachine_tool_configured:

php
add_filter( 'datamachine_tool_configured', function ( bool $configured, string $tool_id ): bool {
    if ( 'my_plugin/search' !== $tool_id ) {
        return $configured;
    }

    $settings = get_option( 'my_plugin_settings', array() );
    return ! empty( $settings['api_key'] );
}, 10, 2 );

Tools without requires_config still pass through the global enablement check. The old public enablement filter is not part of the current runtime path.

Pipeline Tool Policy Inputs

Pipeline AI steps build resolver policy args from the workflow snapshot through PipelineToolPolicyArgs::fromConfigs():

Config keyResolver argEffect
enabled_tools on the flow stepallow_onlyNarrows optional/static tools to the listed names and grants matching requires_opt_in tools.
disabled_tools on the pipeline stepdenyRemoves listed tools.
disabled_tools on the flow stepdenyRemoves listed tools.

The snapshot is authoritative. Runtime pipeline resolution does not re-read persisted pipeline rows because direct workflows can have synthetic IDs and historical jobs must use the policy captured when they ran.

Adjacent handler tools are mandatory flow plumbing. They are preserved through allow/category policy unless explicitly denied by the high-precedence deny list.