Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 96 additions & 1 deletion data-machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,102 @@
define( 'DATAMACHINE_PATH', plugin_dir_path( __FILE__ ) );
define( 'DATAMACHINE_URL', plugin_dir_url( __FILE__ ) );

/**
* Read the Agents API plugin header version without loading the plugin file.
*
* @param string $plugin_file Plugin file path.
* @return string|null Version string, or null when unavailable.
*/
function datamachine_read_agents_api_plugin_version( string $plugin_file ): ?string {
if ( ! is_readable( $plugin_file ) ) {
return null;
}

if ( function_exists( 'get_file_data' ) ) {
$headers = get_file_data( $plugin_file, array( 'version' => 'Version' ), 'plugin' );
$version = trim( (string) ( $headers['version'] ?? '' ) );

return '' !== $version ? $version : null;
}

return null;
}

/**
* Load bundled Agents API unless another copy is already active.
*
* @return array{loaded:string,bundled_version:?string,active_version:?string,active_file:?string,warning:?string}
*/
function datamachine_load_bundled_agents_api(): array {
$bundled_file = __DIR__ . '/vendor/wordpress/agents-api/agents-api.php';
$bundled_version = datamachine_read_agents_api_plugin_version( $bundled_file );

if ( defined( 'AGENTS_API_LOADED' ) ) {
$active_file = defined( 'AGENTS_API_PLUGIN_FILE' ) ? (string) constant( 'AGENTS_API_PLUGIN_FILE' ) : null;
$active_version = defined( 'AGENTS_API_VERSION' ) ? (string) constant( 'AGENTS_API_VERSION' ) : null;
if ( null === $active_version && null !== $active_file ) {
$active_version = datamachine_read_agents_api_plugin_version( $active_file );
}

$warning = null;
if ( null !== $bundled_version && null !== $active_version && $bundled_version !== $active_version ) {
$warning = sprintf(
'Data Machine is using an already-loaded Agents API version %1$s instead of its bundled version %2$s. Deactivate the standalone Agents API plugin or align versions to avoid runtime substrate skew.',
$active_version,
$bundled_version
);
}

return array(
'loaded' => 'external',
'bundled_version' => $bundled_version,
'active_version' => $active_version,
'active_file' => $active_file,
'warning' => $warning,
);
}

require_once $bundled_file;

return array(
'loaded' => 'bundled',
'bundled_version' => $bundled_version,
'active_version' => $bundled_version,
'active_file' => defined( 'AGENTS_API_PLUGIN_FILE' )
? (string) constant( 'AGENTS_API_PLUGIN_FILE' )
: $bundled_file,
'warning' => null,
);
}

/**
* Emit any deferred Agents API load warning through the Data Machine logger.
*
* @return void
*/
function datamachine_log_agents_api_load_warning(): void {
global $datamachine_agents_api_load_state;

if ( ! is_array( $datamachine_agents_api_load_state ) || empty( $datamachine_agents_api_load_state['warning'] ) ) {
return;
}

do_action(
'datamachine_log',
'warning',
(string) $datamachine_agents_api_load_state['warning'],
array(
'component' => 'agents-api',
'loaded' => $datamachine_agents_api_load_state['loaded'] ?? null,
'active_version' => $datamachine_agents_api_load_state['active_version'] ?? null,
'bundled_version' => $datamachine_agents_api_load_state['bundled_version'] ?? null,
'active_file' => $datamachine_agents_api_load_state['active_file'] ?? null,
)
);
}

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/vendor/wordpress/agents-api/agents-api.php';
$datamachine_agents_api_load_state = datamachine_load_bundled_agents_api();

// WP-CLI integration
// @phpstan-ignore-next-line Runtime constant may be defined false outside PHPStan's configured CLI context.
Expand Down Expand Up @@ -103,6 +197,7 @@ function () {
datamachine_register_admin_filters();
datamachine_register_oauth_system();
datamachine_register_core_actions();
datamachine_log_agents_api_load_warning();

// Load step types - they self-register via constructors
datamachine_load_step_types();
Expand Down
2 changes: 1 addition & 1 deletion docs/core-system/request-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Data Machine has two separate runtime layers:
- **wp-ai-client** owns provider/model prompt execution.
- **RequestBuilder** is the Data Machine adapter from product payloads to `wp-ai-client` provider requests.

Pipeline AI steps and Data Machine chat turns use `RequestBuilder::build()` so directives, metadata, request inspection, tool declarations, and timeout behavior stay consistent. Plugins that only need one-shot provider calls may call `wp-ai-client` directly; they do not need to route through Data Machine or Agents API.
Pipeline AI steps and Data Machine chat turns use `RequestBuilder::build()` so directives, metadata, request inspection, tool declarations, and timeout behavior stay consistent. Plugins that only need one-shot AI operations may call `wp-ai-client` directly; they do not need to route through Data Machine or Agents API.

## Flow

Expand Down
52 changes: 26 additions & 26 deletions docs/development/agents-api-standalone-skeleton-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,36 +122,36 @@ Move contracts/value objects before services. The first extraction PR should be

| Move first | Current in-repo location | Why first |
|---|---|---|
| `WP_Agent`, `WP_Agents_Registry`, and registration helpers | `agents-api/inc/class-wp-agent.php`, `class-wp-agents-registry.php`, `register-agents.php` | Public registration facade; no Data Machine product import. |
| Agent package artifact contracts/helpers | `agents-api/inc/class-wp-agent-package*.php`, `register-agent-package-artifacts.php` | Bundle/package contract is already backend-only. |
| `WP_Agent_Message` and `WP_Agent_Conversation_Result` | `agents-api/inc/AI/` | Generic run/message value contracts. |
| `WP_Agent_Tool_Declaration` | `agents-api/inc/AI/Tools/` | Generic run-scoped tool declaration validation. |
| `WP_Agent_Conversation_Store` | `agents-api/inc/Core/Database/Chat/` | Narrow transcript CRUD contract; does not require Data Machine chat UI. |
| Memory store value objects/interfaces | `agents-api/inc/Core/FilesRepository/` | Generic memory seam; Data Machine default store remains an adapter. |
| `WP_Agent`, `WP_Agents_Registry`, and registration helpers | `agents-api/src/Registry/class-wp-agent.php`, `class-wp-agents-registry.php`, `register-agents.php` | Public registration facade; no Data Machine product import. |
| Agent package artifact contracts/helpers | `agents-api/src/Packages/class-wp-agent-package*.php`, `register-agent-package-artifacts.php` | Bundle/package contract is already backend-only. |
| `WP_Agent_Message` and `WP_Agent_Conversation_Result` | `agents-api/src/Runtime/` | Generic run/message value contracts. |
| `WP_Agent_Tool_Declaration` | `agents-api/src/Tools/` | Generic run-scoped tool declaration validation. |
| `WP_Agent_Conversation_Store` | `agents-api/src/Transcripts/` | Narrow transcript CRUD contract; does not require Data Machine chat UI. |
| Memory store value objects/interfaces | `agents-api/src/Memory/` | Generic memory seam; Data Machine default store remains an adapter. |

Current in-repo source checklist for the first move:

- `agents-api.php`
- `inc/class-wp-agent.php`
- `inc/class-wp-agents-registry.php`
- `inc/register-agents.php`
- `inc/class-wp-agent-package.php`
- `inc/class-wp-agent-package-artifact.php`
- `inc/class-wp-agent-package-artifact-type.php`
- `inc/class-wp-agent-package-artifacts-registry.php`
- `inc/class-wp-agent-package-adoption-diff.php`
- `inc/class-wp-agent-package-adoption-result.php`
- `inc/class-wp-agent-package-adopter.php`
- `inc/register-agent-package-artifacts.php`
- `inc/AI/WP_Agent_Message.php`
- `inc/AI/WP_Agent_Conversation_Result.php`
- `inc/AI/Tools/WP_Agent_Tool_Declaration.php`
- `inc/Core/Database/Chat/WP_Agent_Conversation_Store.php`
- `inc/Core/FilesRepository/WP_Agent_Memory_Scope.php`
- `inc/Core/FilesRepository/WP_Agent_Memory_List_Entry.php`
- `inc/Core/FilesRepository/WP_Agent_Memory_Read_Result.php`
- `inc/Core/FilesRepository/WP_Agent_Memory_Write_Result.php`
- `inc/Core/FilesRepository/WP_Agent_Memory_Store.php`
- `src/Registry/class-wp-agent.php`
- `src/Registry/class-wp-agents-registry.php`
- `src/Registry/register-agents.php`
- `src/Packages/class-wp-agent-package.php`
- `src/Packages/class-wp-agent-package-artifact.php`
- `src/Packages/class-wp-agent-package-artifact-type.php`
- `src/Packages/class-wp-agent-package-artifacts-registry.php`
- `src/Packages/class-wp-agent-package-adoption-diff.php`
- `src/Packages/class-wp-agent-package-adoption-result.php`
- `src/Packages/class-wp-agent-package-adopter.php`
- `src/Packages/register-agent-package-artifacts.php`
- `src/Runtime/class-wp-agent-message.php`
- `src/Runtime/class-wp-agent-conversation-result.php`
- `src/Tools/class-wp-agent-tool-declaration.php`
- `src/Transcripts/class-wp-agent-conversation-store.php`
- `src/Memory/class-wp-agent-memory-scope.php`
- `src/Memory/class-wp-agent-memory-list-entry.php`
- `src/Memory/class-wp-agent-memory-read-result.php`
- `src/Memory/class-wp-agent-memory-write-result.php`
- `src/Memory/class-wp-agent-memory-store.php`

## Keep In Data Machine For Now

Expand Down
1 change: 0 additions & 1 deletion inc/Engine/AI/Actions/PendingActionInspectionAbility.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ public static function normalize_action_row( array $action ): array {
$context = isset( $datamachine['context'] ) && is_array( $datamachine['context'] ) ? $datamachine['context'] : array();
$workspace = isset( $action['workspace'] ) && is_array( $action['workspace'] ) ? $action['workspace'] : array();

$action['preview_data'] = $action['preview'] ?? $action['preview_data'] ?? array();
$action['agent_id'] = isset( $datamachine['agent_id'] ) ? (int) $datamachine['agent_id'] : self::id_from_canonical_ref( $action['agent'] ?? null, 'agent' );
$action['created_by'] = isset( $datamachine['created_by'] ) ? (int) $datamachine['created_by'] : self::id_from_canonical_ref( $action['creator'] ?? null, 'user' );
$action['context'] = $context;
Expand Down
31 changes: 17 additions & 14 deletions tests/agents-api-package-contract-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,12 @@ static function () use ( $validate_callback, $diff_callback ): void {
}
);

$artifact_types = wp_get_agent_package_artifact_types();
$artifact_type = wp_get_agent_package_artifact_type( 'DATAMACHINE/FLOW' );
agents_api_smoke_assert_equals( array( 'datamachine/flow', 'intelligence/brain' ), array_keys( $artifact_types ), 'artifact type slugs are normalized and collected', $failures, $passes );
$artifact_types = wp_get_agent_package_artifact_types();
$artifact_type = wp_get_agent_package_artifact_type( 'DATAMACHINE/FLOW' );
$artifact_type_slugs = array_keys( $artifact_types );
sort( $artifact_type_slugs );
agents_api_smoke_assert_equals( true, in_array( 'datamachine/flow', $artifact_type_slugs, true ), 'artifact type registry includes datamachine/flow', $failures, $passes );
agents_api_smoke_assert_equals( true, in_array( 'intelligence/brain', $artifact_type_slugs, true ), 'artifact type registry includes intelligence/brain', $failures, $passes );
agents_api_smoke_assert_equals( true, $artifact_type instanceof WP_Agent_Package_Artifact_Type, 'artifact type getter returns object', $failures, $passes );
agents_api_smoke_assert_equals( 'Package flow artifact', $artifact_type ? $artifact_type->get_label() : '', 'artifact type label is preserved', $failures, $passes );
agents_api_smoke_assert_equals( true, is_callable( $artifact_type ? $artifact_type->get_validate_callback() : null ), 'validate callback is preserved', $failures, $passes );
Expand All @@ -185,13 +188,13 @@ static function () use ( $validate_callback, $diff_callback ): void {
agents_api_smoke_assert_equals( array( 'owner' => 'example' ), $artifact_type ? $artifact_type->get_meta() : array(), 'artifact type meta is preserved', $failures, $passes );
agents_api_smoke_assert_equals( true, wp_has_agent_package_artifact_type( 'datamachine/flow' ), 'wp_has_agent_package_artifact_type reports registered slug', $failures, $passes );
agents_api_smoke_assert_equals( false, wp_has_agent_package_artifact_type( 'broken' ), 'invalid unnamespaced type is rejected', $failures, $passes );
agents_api_smoke_assert_equals( 3, count( $GLOBALS['__agents_api_smoke_wrong'] ), 'duplicate and invalid type registrations emit notices', $failures, $passes );
$removed = wp_unregister_agent_package_artifact_type( 'intelligence/brain' );
agents_api_smoke_assert_equals( true, $removed instanceof WP_Agent_Package_Artifact_Type, 'unregister returns removed artifact type', $failures, $passes );
agents_api_smoke_assert_equals( false, wp_has_agent_package_artifact_type( 'intelligence/brain' ), 'unregister removes artifact type', $failures, $passes );
$GLOBALS['__agents_api_smoke_wrong'] = array();
wp_register_agent_package_artifact_type( 'outside/hook', array( 'label' => 'Outside Hook' ) );
agents_api_smoke_assert_equals( 1, count( $GLOBALS['__agents_api_smoke_wrong'] ), 'outside-hook direct artifact type registration is rejected', $failures, $passes );
$outside_hook_result = wp_register_agent_package_artifact_type( 'outside/hook', array( 'label' => 'Outside Hook' ) );
agents_api_smoke_assert_equals( null, $outside_hook_result, 'outside-hook direct artifact type registration is rejected', $failures, $passes );
agents_api_smoke_assert_equals( false, wp_has_agent_package_artifact_type( 'outside/hook' ), 'outside-hook artifact type is not registered', $failures, $passes );

echo "\n[4] Adopter contracts stay runtime-neutral:\n";
class Agents_API_Package_Smoke_Adopter implements WP_Agent_Package_Adopter {
Expand Down Expand Up @@ -224,14 +227,14 @@ public function adopt( WP_Agent_Package $package, array $options = array() ): WP

echo "\n[5] Public package contract has no product vocabulary:\n";
$contract_files = array(
'inc/class-wp-agent-package.php',
'inc/class-wp-agent-package-artifact.php',
'inc/class-wp-agent-package-artifact-type.php',
'inc/class-wp-agent-package-artifacts-registry.php',
'inc/class-wp-agent-package-adopter.php',
'inc/class-wp-agent-package-adoption-diff.php',
'inc/class-wp-agent-package-adoption-result.php',
'inc/register-agent-package-artifacts.php',
'src/Packages/class-wp-agent-package.php',
'src/Packages/class-wp-agent-package-artifact.php',
'src/Packages/class-wp-agent-package-artifact-type.php',
'src/Packages/class-wp-agent-package-artifacts-registry.php',
'src/Packages/class-wp-agent-package-adopter.php',
'src/Packages/class-wp-agent-package-adoption-diff.php',
'src/Packages/class-wp-agent-package-adoption-result.php',
'src/Packages/register-agent-package-artifacts.php',
);
$forbidden = array( 'DataMachine\\', 'pipeline', 'flow', 'job', 'handler', 'Intelligence', 'wpcom', 'Dolly', 'Odie' );
$matches = array();
Expand Down
42 changes: 21 additions & 21 deletions tests/agents-api-standalone-skeleton-plan-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,27 @@
}

$current_module_files = array(
'agents-api.php',
'inc/class-wp-agent.php',
'inc/class-wp-agents-registry.php',
'inc/register-agents.php',
'inc/class-wp-agent-package.php',
'inc/class-wp-agent-package-artifact.php',
'inc/class-wp-agent-package-artifact-type.php',
'inc/class-wp-agent-package-artifacts-registry.php',
'inc/class-wp-agent-package-adoption-diff.php',
'inc/class-wp-agent-package-adoption-result.php',
'inc/class-wp-agent-package-adopter.php',
'inc/register-agent-package-artifacts.php',
'inc/AI/WP_Agent_Message.php',
'inc/AI/WP_Agent_Conversation_Result.php',
'inc/AI/Tools/WP_Agent_Tool_Declaration.php',
'inc/Core/Database/Chat/WP_Agent_Conversation_Store.php',
'inc/Core/FilesRepository/WP_Agent_Memory_Scope.php',
'inc/Core/FilesRepository/WP_Agent_Memory_List_Entry.php',
'inc/Core/FilesRepository/WP_Agent_Memory_Read_Result.php',
'inc/Core/FilesRepository/WP_Agent_Memory_Write_Result.php',
'inc/Core/FilesRepository/WP_Agent_Memory_Store.php',
'agents-api.php',
'src/Registry/class-wp-agent.php',
'src/Registry/class-wp-agents-registry.php',
'src/Registry/register-agents.php',
'src/Packages/class-wp-agent-package.php',
'src/Packages/class-wp-agent-package-artifact.php',
'src/Packages/class-wp-agent-package-artifact-type.php',
'src/Packages/class-wp-agent-package-artifacts-registry.php',
'src/Packages/class-wp-agent-package-adoption-diff.php',
'src/Packages/class-wp-agent-package-adoption-result.php',
'src/Packages/class-wp-agent-package-adopter.php',
'src/Packages/register-agent-package-artifacts.php',
'src/Runtime/class-wp-agent-message.php',
'src/Runtime/class-wp-agent-conversation-result.php',
'src/Tools/class-wp-agent-tool-declaration.php',
'src/Transcripts/class-wp-agent-conversation-store.php',
'src/Memory/class-wp-agent-memory-scope.php',
'src/Memory/class-wp-agent-memory-list-entry.php',
'src/Memory/class-wp-agent-memory-read-result.php',
'src/Memory/class-wp-agent-memory-write-result.php',
'src/Memory/class-wp-agent-memory-store.php',
);


Expand Down
Loading
Loading