Location: /inc/Engine/AI/ConversationManager.php
Since: v0.2.0 (Universal Engine Architecture)
Namespace: DataMachine\Engine\AI
ConversationManager provides standardized message formatting utilities for all AI agents (Pipeline and Chat). All methods are static with no state management, enabling consistent conversation structure across the Data Machine ecosystem.
Centralizes conversation message building, tool call formatting, result messaging, and duplicate detection logic used by AIConversationLoop, RequestBuilder, and both Pipeline and Chat agents.
Build standardized conversation message structure.
Signature:
public static function buildConversationMessage(string $role, string $content): arrayParameters:
$role(string) - Role identifier:user,assistant,system$content(string) - Message content
Returns: Array with role and content keys
Example:
$message = ConversationManager::buildConversationMessage('user', 'Create a pipeline');
// Returns: ['role' => 'user', 'content' => 'Create a pipeline']Usage: Foundation for all message formatting methods
Format tool call as conversation message with turn tracking.
Signature:
public static function formatToolCallMessage(
string $tool_name,
array $tool_parameters,
int $turn_count
): arrayParameters:
$tool_name(string) - Tool identifier (e.g.,local_search,wordpress_publish)$tool_parameters(array) - Tool call parameters$turn_count(int) - Current conversation turn (0 = no turn display)
Returns: Formatted assistant message with tool call details
Message Format:
AI ACTION (Turn {$turn_count}): Executing {Tool Display Name} with parameters: key: value, key2: value2
Parameter Truncation: String values over 50 characters are truncated to 50...
Example:
$message = ConversationManager::formatToolCallMessage(
'local_search',
['query' => 'WordPress best practices', 'num_results' => 5],
1
);
// Returns: ['role' => 'assistant', 'content' => 'AI ACTION (Turn 1): Executing Local Search with parameters: query: WordPress best practices, num_results: 5']Integration: Used by AIConversationLoop during multi-turn conversation execution
Format tool execution result as conversation message.
Signature:
public static function formatToolResultMessage(
string $tool_name,
array $tool_result,
array $tool_parameters,
bool $is_handler_tool = false,
int $turn_count = 0
): arrayParameters:
$tool_name(string) - Tool identifier$tool_result(array) - Tool execution result withsuccess,data,errorkeys$tool_parameters(array) - Original tool parameters$is_handler_tool(bool) - Whether tool is handler-specific (affects data inclusion)$turn_count(int) - Current conversation turn (0 = no turn display)
Returns: Formatted user message with tool result
Message Format:
TOOL RESPONSE (Turn {$turn_count}): {Success Message}
{JSON-encoded data for non-handler tools}
Data Inclusion:
- Handler tools: Success message only (no raw data)
- Non-handler tools: Success message + JSON-encoded
datafield
Example:
$result = [
'success' => true,
'data' => ['id' => 123, 'title' => 'New Post']
];
$message = ConversationManager::formatToolResultMessage(
'wordpress_publish',
$result,
['content' => 'Post content'],
true, // is_handler_tool
2
);
// Returns user message with success message only (no raw data)
$search_result = [
'success' => true,
'data' => ['results' => [...]]
];
$message2 = ConversationManager::formatToolResultMessage(
'local_search',
$search_result,
['query' => 'test'],
false, // not a handler tool
3
);
// Returns user message with success message + JSON dataGenerate success or failure message from tool result with filter-based customization.
Signature:
public static function generateSuccessMessage(
string $tool_name,
array $tool_result,
array $tool_parameters
): stringParameters:
$tool_name(string) - Tool identifier$tool_result(array) - Tool execution result$tool_parameters(array) - Original tool parameters
Returns: Human-readable success/failure message
Default Success Message:
SUCCESS: {Tool Display Name} completed successfully. The requested operation has been finished as requested.
Failure Message:
TOOL FAILED: {tool_name} execution failed - {error}
Filter Hook: datamachine_tool_success_message allows handlers to customize success messages
Example:
// Success
$result = ['success' => true, 'data' => []];
$message = ConversationManager::generateSuccessMessage('twitter_publish', $result, []);
// Returns: "SUCCESS: Twitter Publish completed successfully. The requested operation has been finished as requested."
// Failure
$result = ['success' => false, 'error' => 'Invalid credentials'];
$message = ConversationManager::generateSuccessMessage('twitter_publish', $result, []);
// Returns: "TOOL FAILED: twitter_publish execution failed - Invalid credentials"
// Custom via filter
add_filter('datamachine_tool_success_message', function($message, $tool_name, $result, $params) {
if ($tool_name === 'wordpress_publish' && isset($result['data']['url'])) {
return "SUCCESS: Post published at {$result['data']['url']}";
}
return $message;
}, 10, 4);Generate standardized failure message.
Signature:
public static function generateFailureMessage(string $tool_name, string $error_message): stringParameters:
$tool_name(string) - Tool identifier$error_message(string) - Error details
Returns: Formatted failure message with guidance
Format:
TOOL FAILED: {Tool Display Name} execution failed - {error_message}. Please review the error and adjust your approach if needed.
Example:
$message = ConversationManager::generateFailureMessage(
'local_search',
'Search index unavailable'
);
// Returns: "TOOL FAILED: Local Search execution failed - Search index unavailable. Please review the error and adjust your approach if needed."Validate if a tool call is a duplicate of the previous tool call in conversation history.
Signature:
public static function validateToolCall(
string $tool_name,
array $tool_parameters,
array $conversation_messages
): arrayParameters:
$tool_name(string) - Tool name to validate$tool_parameters(array) - Tool parameters to validate$conversation_messages(array) - Full conversation history
Returns: Array with is_duplicate (bool) and message (string) keys
Detection Logic:
- Search conversation history backwards for most recent assistant message with "AI ACTION" prefix
- Extract tool name and parameters from that message
- Compare with current tool call
- Return duplicate status and correction message if duplicate
Example:
$conversation = [
['role' => 'user', 'content' => 'Search for WordPress'],
['role' => 'assistant', 'content' => 'AI ACTION (Turn 1): Executing Local Search with parameters: query: WordPress, num_results: 5'],
['role' => 'user', 'content' => 'TOOL RESPONSE (Turn 1): ...']
];
$validation = ConversationManager::validateToolCall(
'local_search',
['query' => 'WordPress', 'num_results' => 5],
$conversation
);
// Returns: ['is_duplicate' => true, 'message' => 'You just called the local_search tool with the exact same parameters...']
$validation2 = ConversationManager::validateToolCall(
'local_search',
['query' => 'WordPress plugins', 'num_results' => 5],
$conversation
);
// Returns: ['is_duplicate' => false, 'message' => '']Integration: Used by AIConversationLoop to prevent infinite loops from duplicate tool calls
Extract tool call details from a conversation message.
Signature:
public static function extractToolCallFromMessage(array $message): ?arrayParameters:
$message(array) - Conversation message to parse
Returns: Array with tool_name and parameters keys, or null if not a tool call message
Recognition Pattern: Messages matching AI ACTION (Turn N): Executing {Tool} with parameters: ...
Parameter Parsing:
- Splits on
,delimiter - Parses
key: valuepairs - JSON-decodes values where possible
- Handles truncated values (appends
_truncated_{timestamp}to prevent false duplicates)
Example:
$message = [
'role' => 'assistant',
'content' => 'AI ACTION (Turn 1): Executing Local Search with parameters: query: WordPress, num_results: 5'
];
$extracted = ConversationManager::extractToolCallFromMessage($message);
// Returns: [
// 'tool_name' => 'local_search',
// 'parameters' => ['query' => 'WordPress', 'num_results' => 5]
// ]Usage: Internal utility for duplicate detection
Generate a tool-result message for duplicate tool call prevention. The correction message is mode-aware so the AI receives continuation guidance appropriate to the current execution mode.
Signature:
public static function generateDuplicateToolCallMessage(
string $tool_name,
int $turn_count = 0,
string $mode = 'chat'
): arrayParameters:
$tool_name(string) - Tool name that was duplicated$turn_count(int) - Current conversation turn (0 = no turn display)$mode(string) - Execution mode ('chat','pipeline','bridge', ...). Defaults to'chat'so existing callers retain prior behavior.
Returns: Formatted tool-result envelope (success: false, error: <mode-shaped guidance>)
Mode behavior:
'chat'(default) — instructs the AI to end the conversation. The tool's success IS the answer to the user's question.'pipeline'— instructs the AI to call the publish handler tool now to complete the step. Pipeline conversations have downstream work after the duplicated tool call.'bridge'— instructs the AI to continue its response to the user using the result it already has.- Unknown modes fall back to chat semantics.
Example:
// Chat: AI is told the task is done.
$msg = ConversationManager::generateDuplicateToolCallMessage('local_search');
// Pipeline: AI is told to move on to the publish handler.
$msg = ConversationManager::generateDuplicateToolCallMessage(
'queue_validator',
$turn_count,
'pipeline'
);Background: see data-machine #1441 — chat-shaped guidance was previously emitted for every mode, which caused pipeline AI steps to silently end mid-conversation after a duplicate tool call instead of finishing the publish step.
AIConversationLoop uses ConversationManager for:
- Tool Call Messages: Format each tool call with turn tracking
- Tool Result Messages: Format tool execution results with success/failure details
- Duplicate Detection: Validate tool calls before execution to prevent infinite loops
Conversation Flow:
// 1. Format tool call message
$tool_call_msg = ConversationManager::formatToolCallMessage($tool_name, $params, $turn_count);
$conversation[] = $tool_call_msg;
// 2. Execute tool via ToolExecutor
$result = ToolExecutor::executeTool(...);
// 3. Format tool result message
$result_msg = ConversationManager::formatToolResultMessage($tool_name, $result, $params, $is_handler_tool, $turn_count);
$conversation[] = $result_msg;
// 4. Validate next tool call for duplicates
$validation = ConversationManager::validateToolCall($next_tool, $next_params, $conversation);
if ($validation['is_duplicate']) {
$conversation[] = ConversationManager::buildConversationMessage('user', $validation['message']);
}RequestBuilder uses ConversationManager for:
- System Messages: Build directive messages with proper role assignment
- User Context: Format user-provided context as conversation messages
Publish/Update handlers use ConversationManager indirectly through:
- Success Message Filter: Customize tool result messages via
datamachine_tool_success_messagefilter
Custom Success Message Example:
add_filter('datamachine_tool_success_message', function($message, $tool_name, $result, $params) {
if ($tool_name === 'twitter_publish' && isset($result['data']['url'])) {
$tweet_url = $result['data']['url'];
return "SUCCESS: Tweet published successfully at {$tweet_url}. The content is now live on Twitter.";
}
return $message;
}, 10, 4);All methods are static with no instance state:
- Benefit: No dependency injection required, simple utility usage
- Pattern: Pure functions for message transformation
- Thread Safety: No state mutations, safe for concurrent usage
Consistent message structure across all agents:
- Role:
user,assistant,system - Content: String message content
- Turn Tracking: Explicit turn numbers in tool messages
- Result Clarity: Clear SUCCESS/FAILED prefixes
Handlers can customize messages via WordPress filters:
datamachine_tool_success_message: Override default success messages- Parameters:
$message,$tool_name,$tool_result,$tool_parameters - Use Cases: Platform-specific messaging, URL inclusion, custom formatting
Sophisticated duplicate detection prevents infinite loops:
- Backward Search: Finds most recent tool call in conversation history
- Exact Matching: Compares tool name and parameters
- Truncation Handling: Prevents false duplicates from truncated parameters
- Guidance Messaging: Provides clear correction instructions to AI
// Simple user message
$msg = ConversationManager::buildConversationMessage('user', 'Create a pipeline');
// System directive
$directive = ConversationManager::buildConversationMessage('system', 'You are a helpful assistant');// 1. Format tool call
$tool_call = ConversationManager::formatToolCallMessage(
'wordpress_publish',
['title' => 'My Post', 'content' => 'Post content'],
1
);
// 2. Execute tool (via ToolExecutor)
$result = ToolExecutor::executeTool(...);
// 3. Format result
$result_msg = ConversationManager::formatToolResultMessage(
'wordpress_publish',
$result,
['title' => 'My Post', 'content' => 'Post content'],
true, // is_handler_tool
1
);$conversation = [...]; // Full conversation history
// Validate before execution
$validation = ConversationManager::validateToolCall(
'local_search',
['query' => 'test'],
$conversation
);
if ($validation['is_duplicate']) {
$correction = ConversationManager::buildConversationMessage(
'user',
$validation['message']
);
$conversation[] = $correction;
// Skip tool execution, continue conversation with correction
}- AIConversationLoop - Multi-turn conversation execution using ConversationManager
- ToolExecutor - Tool discovery and execution
- RequestBuilder - AI request construction
- Parameter Systems - Tool parameter building and architecture
- Universal Engine Architecture - Shared AI infrastructure
Location: /inc/Engine/AI/ConversationManager.php
Namespace: DataMachine\Engine\AI
Type: Static utility class
Since: v0.2.0