AI workflow orchestration with state machines 
Edit on GitHub
This guide shows you how to build multi-step, multi-agent AI workflows using Spryker state machines. By combining state machines with the AiFoundation module, you can orchestrate complex AI processes where agents execute tasks, make decisions based on results, and trigger subsequent steps.
Overview
Spryker state machines provide a powerful framework for orchestrating AI workflows:
- States: Represent stages in your workflow (for example, “analysis_done”, “awaiting_review”)
- Events: Trigger state transitions (for example, “analyze_agent” executes an AI call)
- Commands: Execute AI operations, tool calls, or business logic
- Conditions: Evaluate workflow item data to decide the next state (for example, “is_successful?”)
- Context data: Passed through the workflow to accumulate results and drive decisions
This creates a directed graph where conditions act as edges determining the next state based on AI-generated outputs.
Key concepts
- Context data: A key-value array that flows through the state machine, allowing commands to read input and write results for conditions to evaluate
- Structured response: AI responses converted to structured data, enabling deterministic decision-making
- Command plugins: Execute AI calls and update context with results
- Condition plugins: Evaluate context data to determine workflow progression
- Process name: Links XML definition to PHP configuration and state machine execution
Create a workflow process example
1. Define the state machine XML
Example: IntelligentTask01
Create config/Zed/StateMachine/AiWorkflow/IntelligentTask01.xml:
<?xml version="1.0"?>
<statemachine
xmlns="spryker:state-machine-01"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="spryker:state-machine-01 http://static.spryker.com/state-machine-01.xsd">
<process name="IntelligentTask01" main="true">
<states>
<state name="new"/>
<state name="analysis done"/>
<state name="analysis successful"/>
<state name="analysis failed"/>
</states>
<events>
<!-- onEnter="true" triggers the command automatically when entering source state -->
<event name="analyze agent" onEnter="true" command="OryxAgent/Analyze"/>
<event name="pass" onEnter="true"/>
<event name="restart" manual="true"/>
</events>
<transitions>
<!-- Initial transition: execute command on entry -->
<transition happy="true">
<source>new</source>
<target>analysis done</target>
<event>analyze agent</event>
</transition>
<!-- Happy path: condition passes -->
<transition happy="true" condition="OryxAgent/Success">
<source>analysis done</source>
<target>analysis successful</target>
<event>pass</event>
</transition>
<!-- Unhappy path: condition fails (no condition = default) -->
<transition>
<source>analysis done</source>
<target>analysis failed</target>
<event>pass</event>
</transition>
<!-- Recovery: manual restart from failed state -->
<transition>
<source>analysis failed</source>
<target>new</target>
<event>restart</event>
</transition>
</transitions>
</process>
</statemachine>

2. Create command plugins
Command plugin example: Analyze data with Oryx Agent
<?php
use Generated\Shared\Transfer\AiWorkflowItemCollectionRequestTransfer;
use Generated\Shared\Transfer\StateMachineItemTransfer;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\StateMachine\Dependency\Plugin\CommandPluginInterface;
class OryxAgentAnalyzeCommandPlugin extends AbstractPlugin implements CommandPluginInterface
{
public function run(StateMachineItemTransfer $stateMachineItemTransfer): void
{
$aiWorkflowItemTransfer = $this->loadWorkflowItem($stateMachineItemTransfer);
$this->executeAnalysis($aiWorkflowItemTransfer);
$request = (new AiWorkflowItemCollectionRequestTransfer())
->setIsTransactional(true)
->addAiWorkflowItem($aiWorkflowItemTransfer);
$this->getAiFoundationFacade()->updateAiWorkflowItemCollection($request);
}
protected function loadWorkflowItem(
StateMachineItemTransfer $stateMachineItemTransfer
): AiWorkflowItemTransfer {
$idAiWorkflowItem = $stateMachineItemTransfer->getIdentifierOrFail();
$aiWorkflowItemConditionsTransfer = (new AiWorkflowItemConditionsTransfer())
->addAiWorkflowItemId($idAiWorkflowItem);
$aiWorkflowItemCriteriaTransfer = (new AiWorkflowItemCriteriaTransfer())
->setAiWorkflowItemConditions($aiWorkflowItemConditionsTransfer);
$aiWorkflowItemCollection = $this->getAiFoundationFacade()
->getAiWorkflowItemCollection($aiWorkflowItemCriteriaTransfer);
return $aiWorkflowItemCollection->getAiWorkflowItems()->offsetGet(0);
}
protected function executeAnalysis(AiWorkflowItemTransfer $aiWorkflowItemTransfer): void
{
$contextData = $aiWorkflowItemTransfer->getContextData();
$promptMessage = $contextData['prompt'] ?? 'Please analyze this data.';
$promptRequestTransfer = (new PromptRequestTransfer())
->setPromptMessage($promptMessage);
$promptResponseTransfer = $this->getAiFoundationFacade()->prompt($promptRequestTransfer);
$contextData['analysis_result'] = $promptResponseTransfer->getMessage();
$contextData['success'] = $promptResponseTransfer->getIsSuccessful();
$contextData['analyzed_at'] = time();
$aiWorkflowItemTransfer->setContextData($contextData);
}
}
3. Create condition plugins
Condition plugin example: Check Oryx Agent success flag
<?php
namespace Pyz\Zed\AiFoundation\Communication\Plugin\StateMachine\Condition;
use Generated\Shared\Transfer\StateMachineItemTransfer;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\StateMachine\Dependency\Plugin\ConditionPluginInterface;
class OryxAgentSuccessConditionPlugin extends AbstractPlugin implements ConditionPluginInterface
{
public function check(StateMachineItemTransfer $stateMachineItemTransfer): bool
{
// Load workflow item
$aiWorkflowItemTransfer = $this->loadWorkflowItem($stateMachineItemTransfer);
// Read success flag set by command
$contextData = $aiWorkflowItemTransfer->getContextData();
return $contextData['success'] ?? false;
}
}
4. Register plugins in AiFoundation dependency provider
<?php
namespace Pyz\Zed\AiFoundation;
use Pyz\Zed\AiFoundation\Communication\Plugin\StateMachine\Command\OryxAgentAnalyzeCommandPlugin;
use Pyz\Zed\AiFoundation\Communication\Plugin\StateMachine\Condition\OryxAgentSuccessConditionPlugin;
use Spryker\Zed\AiFoundation\AiFoundationDependencyProvider as SprykerAiFoundationDependencyProvider;
class AiFoundationDependencyProvider extends SprykerAiFoundationDependencyProvider
{
protected function getAiWorkflowCommandPlugins(): array
{
return [
'OryxAgent/Analyze' => new OryxAgentAnalyzeCommandPlugin(),
];
}
protected function getAiWorkflowConditionPlugins(): array
{
return [
'OryxAgent/Success' => new OryxAgentSuccessConditionPlugin(),
];
}
}
5. Configure active processes
Create or update src/Pyz/Zed/AiFoundation/AiFoundationConfig.php:
<?php
namespace Pyz\Zed\AiFoundation;
use Spryker\Zed\AiFoundation\AiFoundationConfig as SprykerAiFoundationConfig;
class AiFoundationConfig extends SprykerAiFoundationConfig
{
public function getAiWorkflowActiveProcesses(): array
{
return [
'IntelligentTask01',
];
}
protected function getAiWorkflowInitialStateMapForProcess(): array
{
return [
'IntelligentTask01' => 'new',
];
}
}
6. Enable state machine handler
Update src/Pyz/Zed/StateMachine/StateMachineDependencyProvider.php:
<?php
use Pyz\Zed\StateMachine\Communication\Plugin\StateMachine\AiWorkflowStateMachineHandlerPlugin;
protected function getStateMachineHandlerPlugins(Container $container): array
{
return [
new AiWorkflowStateMachineHandlerPlugin(),
];
}
7. Set up cron jobs to process state machine events
Update config/Zed/cronjobs/jenkins.php:
$jobs[] = [
'name' => 'check-ai-workflow-conditions',
'command' => '$PHP_BIN vendor/bin/console state-machine:check-condition AiWorkflow',
'schedule' => '* * * * *',
'enable' => true,
];
$jobs[] = [
'name' => 'check-ai-workflow-timeouts',
'command' => '$PHP_BIN vendor/bin/console state-machine:check-timeout AiWorkflow',
'schedule' => '* * * * *',
'enable' => true,
];
$jobs[] = [
'name' => 'clear-state-machine-locks',
'command' => '$PHP_BIN vendor/bin/console state-machine:clear-locks',
'schedule' => '0 6 * * *',
'enable' => true,
];
8. Trigger a workflow with state machine
To start a workflow, create a workflow item with initial context data and trigger the state machine for it.
Create a workflow item and trigger the state machine:
<?php
use Generated\Shared\Transfer\AiWorkflowItemCollectionRequestTransfer;
use Generated\Shared\Transfer\AiWorkflowItemTransfer;
use Generated\Shared\Transfer\StateMachineProcessTransfer;
use Spryker\Shared\AiFoundation\AiFoundationConstants;
// 1. Create a workflow item with initial context data
$request = (new AiWorkflowItemCollectionRequestTransfer())
->setIsTransactional(true)
->addAiWorkflowItem(
(new AiWorkflowItemTransfer())->setContextData([
'prompt' => 'Analyze this data.',
'status' => 'initialized',
]),
);
$response = $this->aiFoundationFacade->createAiWorkflowItemCollection($request);
$workflowItem = $response->getAiWorkflowItems()->offsetGet(0);
// 2. Trigger the state machine for the newly created item
$processTransfer = (new StateMachineProcessTransfer())
->setProcessName('IntelligentTask01')
->setStateMachineName(AiFoundationConstants::AI_WORKFLOW_STATE_MACHINE_NAME);
$this->stateMachineFacade->triggerForNewStateMachineItem($processTransfer, $workflowItem->getIdAiWorkflowItem());
Trigger a manual event:
<?php
use Generated\Shared\Transfer\StateMachineItemTransfer;
$stateMachineItemTransfer = (new StateMachineItemTransfer())
->setIdentifier($workflowItem->getIdAiWorkflowItem())
->setStateMachineName(AiFoundationConstants::AI_WORKFLOW_STATE_MACHINE_NAME)
->setProcessName('IntelligentTask01');
$this->stateMachineFacade->triggerEvent('restart', $stateMachineItemTransfer);
Multi-agent orchestration patterns
There are many possible patterns for multi-agent orchestration. The following are a couple of examples implemented using the Spryker AiWorkflow state machine.
Multi-agent routing workflow

Multi-agent supervision workflow

Best practices
- Keep context simple: Store only the data that conditions need to evaluate the next step and that subsequent commands need to process
- Use structured responses: Convert AI responses to structured data before storing in context
- Name explicitly: Use descriptive command and condition names matching the process name
- Document context schema: Define what keys each command and condition expects in context data
Thank you!
For submitting the form