resources/boost/skills/neuron-workflow-architect/SKILL.md
Build custom Neuron AI workflows with nodes, events, middleware, and human-in-the-loop patterns. Use this skill whenever the user mentions workflows, orchestration, event-driven systems, custom agents, complex multi-step processes, human-in-the-loop patterns, or wants to build a custom agentic system from scratch. Also trigger for tasks involving node creation, event routing, workflow middleware, persistence, or interruption patterns.
npx skillsauth add neuron-core/neuron-laravel neuron-workflow-architectInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill helps you build custom event-driven workflows in Neuron AI. Workflows are the foundation of the entire framework - Agent and RAG are built on top of Workflow.
Workflows operate through events flowing between nodes:
StartEvent → Node1 → Event2 → Node2 → Event3 → Node3 → StopEvent
Each node:
EventEvent (or StopEvent to complete)Nodes extend the Node base class:
use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\Event;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;
use NeuronAI\Workflow\WorkflowState;
class ValidationNode extends Node
{
// The __invoke signature determines which event this node handles
public function __invoke(StartEvent $event, WorkflowState $state): ProcessEvent
{
$input = $state->get('input');
$validated = $this->validate($input);
$state->set('validated', $validated);
return new ProcessEvent($validated);
}
private function validate(mixed $input): array
{
// Validation logic
return ['valid' => true, 'data' => $input];
}
}
Key Pattern: The workflow automatically maps events to nodes based on the first parameter type of __invoke().
use NeuronAI\Workflow\Event;
class UserValidatedEvent implements Event
{
public function __construct(
public readonly string $userId,
public readonly array $userData
) {}
}
class ProcessCompleteEvent implements Event
{
public function __construct(
public readonly string $result
) {}
}
Events should:
Event interfaceuse NeuronAI\Workflow\Workflow;
use NeuronAI\Workflow\WorkflowState;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;
$state = new WorkflowState([
'input' => $userData,
]);
$workflow = Workflow::make($state)
->addNodes([
new ValidationNode(),
new ProcessingNode(),
new OutputNode(),
]);
$handler = $workflow->start();
$finalState = $handler->run();
$result = $finalState->get('result');
class MyWorkflow extends Workflow
{
/**
* @return NodeInterface[]
*/
protected function nodes(): array
{
return [
new ValidationNode(),
new ProcessingNode(),
];
}
}
WorkflowState is a shared state container that persists across all nodes:
$state = new WorkflowState();
// Set values
$state->set('user_id', 123);
$state->set('data', ['key' => 'value']);
// Get values
$userId = $state->get('user_id');
$default = $state->get('missing_key', 'default_value');
// Check existence
if ($state->has('data')) {
// Data exists
}
// Get subset of state
$subset = $state->only(['user_id', 'data']);
// Delete value
$state->delete('data');
// Get all state
$all = $state->all();
Workflows support interruption for human intervention at any point.
use NeuronAI\Workflow\Interrupt\ApprovalRequest;
use NeuronAI\Workflow\Interrupt\Action;
class DangerousOperationNode extends Node
{
public function __invoke(ProcessEvent $event, WorkflowState $state): ResultEvent
{
// Interrupt for approval
$resumeRequest = $this->interrupt(new ApprovalRequest(
actions: [
new Action(
id: 'delete_files',
name: 'Delete Files',
description: 'Delete all files in /tmp/uploads'
),
new Action(
id: 'send_email',
name: 'Send Notification',
description: 'Send email to [email protected]'
),
],
message: 'These operations require approval'
));
foreach ($resumeRequest->actions as $action) {
if ($action->decision === ActionDecision::Approved) {
$this->executeAction($action->id);
}
}
return new ResultEvent(...);
}
}
public function __invoke(ProcessEvent $event, WorkflowState $state): ResultEvent
{
$cost = $state->get('estimated_cost');
// Only interrupt if cost exceeds threshold
$resumeRequest = $this->interruptIf(
$cost > 1000,
new ApprovalRequest(
actions: [/* ... */],
message: "Operation costs $${cost}. Approval required."
)
);
return new ResultEvent(...);
}
use NeuronAI\Workflow\Persistence\FilePersistence;
$persistence = new FilePersistence('/tmp/workflows');
$workflow = Workflow::make($persistence)
->addNodes([...]);
try {
$handler = $workflow->start();
$result = $handler->run();
} catch (WorkflowInterrupt $interrupt) {
// Present to user
$request = $interrupt->getRequest();
$workflowId = $interrupt->getWorkflowId();
// After user makes decisions:
$resumeRequest = $this->getUserDecisions($request);
$result = $workflow->init($resumeRequest)->run();
}
Nodes can use checkpoints to cache operations happening before the interruption point.
class DataProcessingNode extends Node
{
public function __invoke(ProcessEvent $event, WorkflowState $state): ResultEvent
{
// When resumed,
// $data is retrieved from checkpoint
$data = $this->checkpoint('fetch_data', function() {
return $this->fetchExpensiveData();
});
// Might interrupt here
$resumeRequest = $this->interruptIf($needsApproval, new ApprovalRequest(...));
if ($resumeRequest->getAction('check')->isApproved()) {
return new ResultEvent($data);
}
return new AnotherEvent();
}
}
Middleware wraps node execution for cross-cutting concerns.
use NeuronAI\Workflow\Middleware\WorkflowMiddleware;
use NeuronAI\Workflow\NodeInterface;
use NeuronAI\Workflow\Event;
class LoggingMiddleware implements WorkflowMiddleware
{
public function __construct(private \Psr\Log\LoggerInterface $logger) {}
public function before(NodeInterface $node, Event $event, WorkflowState $state): void
{
$this->logger->info("Executing: " . $node::class);
}
public function after(NodeInterface $node, Event $event, Event|Generator $result, WorkflowState $state): void
{
$this->logger->info("Completed: " . $node::class);
}
}
// Node-specific middleware
$workflow->middleware(ProcessingNode::class, new LoggingMiddleware($logger));
// Multiple middleware on one node
$workflow->middleware(ProcessingNode::class, [
new ValidationMiddleware(),
new LoggingMiddleware(),
]);
// Global middleware (runs on all nodes)
$workflow->globalMiddleware(new PerformanceMiddleware());
before() calls → Node execution → after() calls
All before() methods execute in registration order, then the node, then all after() methods.
Nodes can return Generator to yield intermediate results.
class ProcessingNode extends Node
{
public function __invoke(ProcessEvent $event, WorkflowState $state): \Generator
{
yield new ProgressEvent("Starting process...");
$result = $this->longRunningOperation();
yield new ProgressEvent("Completed!");
return new ResultEvent($result);
}
}
$handler = $workflow->start();
foreach ($handler->events() as $event) {
if ($event instanceof ProgressEvent) {
echo $event->message . PHP_EOL;
}
}
$finalState = $handler->run();
Checkpoint cache operation results across interruptions:
class DataProcessingNode extends Node
{
public function __invoke(ProcessEvent $event, WorkflowState $state): ResultEvent
{
// When resumed, $data is retrieved from checkpoint
$data = $this->checkpoint('fetch_data', function() {
return $this->fetchExpensiveData();
});
// Might interrupt here
$resumeRequest = $this->interruptIf($needsApproval, new ApprovalRequest(...));
if (!$resumeRequest->isApproved()) {
// ...
}
// $data is retrieved from checkpoint
$result = $this->process($data);
return new ResultEvent($result);
}
}
Export workflows to diagram formats for visualization.
use NeuronAI\Workflow\Exporter\MermaidExporter;
$workflow->setExporter(new MermaidExporter());
$diagram = $workflow->export();
// Produces Mermaid flowchart showing event→node flow
vendor/bin/neuron make:workflow DataProcessingWorkflow
class SequentialWorkflow extends Workflow
{
/**
* @return NodeInterface[]
*/
protected function nodes(): array
{
return [
new ValidationNode(),
new ProcessingNode(),
new OutputNode(),
];
}
}
class RouterNode extends Node
{
public function __invoke(ProcessEvent $event, WorkflowState $state): Event
{
if ($state->get('priority') === 'high') {
return new HighPriorityEvent($event->data);
}
return new LowPriorityEvent($event->data);
}
}
class LoopNode extends Node
{
public function __invoke(ProcessEvent $event, WorkflowState $state): Event
{
$items = $state->get('items');
$current = $state->get('current_index', 0);
if ($current < count($items)) {
$state->set('current_item', $items[$current]);
$state->set('current_index', $current + 1);
return new ProcessItemEvent($items[$current]);
}
return new StopEvent();
}
}
When a node needs to run multiple sub-tasks concurrently (e.g. extracting structured data from an image while also generating a description), use ParallelEvent to fork execution into parallel branches.
ForkNode → ParallelEvent([branch1 => EventA, branch2 => EventB])
├─ BranchA → NodeA → StopEvent(resultA)
└─ BranchB → NodeB → StopEvent(resultB)
→ JoinNode (reads results from ParallelEvent) → StopEvent
ParallelEvent subclass with branch-starting events.StopEvent.StopEvent::getResult() is collected into the ParallelEvent.__invoke() accepts the ParallelEvent subclass) reads the results.use NeuronAI\Workflow\Events\ParallelEvent;
class ImageAnalysisParallelEvent extends ParallelEvent {}
use NeuronAI\Workflow\Events\Event;
class ExtractStructuredDataEvent implements Event
{
public function __construct(public readonly string $imageUrl) {}
}
class GenerateDescriptionEvent implements Event
{
public function __construct(public readonly string $imageUrl) {}
}
use NeuronAI\Workflow\Events\StartEvent;
use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;
class AnalyzeImageForkNode extends Node
{
public function __invoke(StartEvent $event, WorkflowState $state): ImageAnalysisParallelEvent
{
$imageUrl = $state->get('image_url');
return new ImageAnalysisParallelEvent([
'structured' => new ExtractStructuredDataEvent($imageUrl),
'description' => new GenerateDescriptionEvent($imageUrl),
]);
}
}
Branch IDs come from the array keys ('structured', 'description'). If you pass a sequential array, IDs are auto-derived from each event's short class name.
use NeuronAI\Agent;
use NeuronAI\Providers\OpenAI\OpenAI;
use NeuronAI\HttpClient\AmpHttpClient;
use NeuronAI\Workflow\Events\StopEvent;
use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;
class ExtractStructuredDataNode extends Node
{
public function __invoke(ExtractStructuredDataEvent $event, WorkflowState $state): StopEvent
{
$agent = Agent::make()
->setProvider(
(new OpenAI(getenv('OPENAI_API_KEY'), 'gpt-4o'))
->setHttpClient(new AmpHttpClient())
)
->setTools([/* ... */])
->addSystemTip('Extract structured data from the image.');
$result = $agent->structured(/* your structured output class */);
return new StopEvent(result: $result);
}
}
class GenerateDescriptionNode extends Node
{
public function __invoke(GenerateDescriptionEvent $event, WorkflowState $state): StopEvent
{
$agent = Agent::make()
->setProvider(
(new OpenAI(getenv('OPENAI_API_KEY'), 'gpt-4o'))
->setHttpClient(new AmpHttpClient())
)
->addSystemTip('Describe the image in detail.');
$description = $agent->chat($event->imageUrl);
return new StopEvent(result: $description);
}
}
class MergeAnalysisNode extends Node
{
public function __invoke(ImageAnalysisParallelEvent $event, WorkflowState $state): StopEvent
{
$structuredData = $event->getResult('structured');
$description = $event->getResult('description');
$state->set('analysis', [
'data' => $structuredData,
'description' => $description,
]);
return new StopEvent();
}
}
$workflow = Workflow::make()
->addNodes([
new AnalyzeImageForkNode(),
new ExtractStructuredDataNode(),
new GenerateDescriptionNode(),
new MergeAnalysisNode(),
]);
$state = $workflow->init(new WorkflowState(['image_url' => 'https://example.com/photo.jpg']));
$result = $state->run();
By default, WorkflowExecutor runs branches sequentially (one after another). For true concurrency, use AsyncExecutor:
use NeuronAI\Workflow\Executor\AsyncExecutor;
use NeuronAI\Workflow\Workflow;
$workflow = Workflow::make()
->setExecutor(new AsyncExecutor())
->addNodes([
new AnalyzeImageForkNode(),
new ExtractStructuredDataNode(),
new GenerateDescriptionNode(),
new MergeAnalysisNode(),
]);
AsyncExecutor is a drop-in replacement — it runs branches as concurrent Amp futures while keeping linear (non-parallel) nodes sequential as usual.
For fully asynchronous execution where branches make HTTP calls to AI providers concurrently, combine AsyncExecutor with AmpHttpClient:
AsyncExecutor runs parallel branches as concurrent Amp fibers (non-blocking).AmpHttpClient is the async HTTP client built on amphp/http-client. Inject it on the provider via ->setHttpClient(new AmpHttpClient()) to ensure HTTP calls inside each branch are non-blocking.Without AmpHttpClient, each branch's HTTP call would block its fiber, negating the concurrency benefit. With it, all branches make their API calls truly in parallel — a workflow that extracts structured data and generates a description simultaneously completes in the time of the slower branch, not the sum of both.
use NeuronAI\HttpClient\AmpHttpClient;
use NeuronAI\Providers\OpenAI\OpenAI;
$provider = (new OpenAI(getenv('OPENAI_API_KEY'), 'gpt-4o'))
->setHttpClient(new AmpHttpClient());
Parallel branches fully support human-in-the-loop. If any branch calls $this->interrupt(), the executor throws a WorkflowInterrupt with parallel context:
use NeuronAI\Workflow\Interrupt\WorkflowInterrupt;
try {
$result = $workflow->init()->run();
} catch (WorkflowInterrupt $interrupt) {
if ($interrupt->isParallelInterrupt()) {
// $interrupt->getBranchId() — which branch interrupted
// $interrupt->getCompletedBranchResults() — results from branches that finished
// Present interrupt to user...
}
}
// After user responds:
$handler = $workflow->init($interrupt->getRequest());
$result = $handler->run();
// Resuming skips already-completed branches, only re-runs the interrupted one.
Use Checkpoint inside branch nodes for expensive operations that should not re-run after resume:
class ExtractStructuredDataNode extends Node
{
public function __invoke(ExtractStructuredDataEvent $event, WorkflowState $state): StopEvent
{
$data = $this->checkpoint('fetch_image', fn() => $this->fetchExpensiveImageData());
$resumeRequest = $this->interruptIf(
$this->needsApproval($data),
new ApprovalRequest(actions: [...], message: 'Review extracted data')
);
return new StopEvent(result: $data);
}
}
Use Workflow when:
Use Agent when:
tools
Create custom tools, toolkits, and MCP integrations for Neuron AI agents. Use this skill when the user mentions creating tools, building toolkits, extending Tool class, defining tool properties, implementing tool execution, MCP server integration, Model Context Protocol, connecting external tools, or tool guidelines. Also trigger for any task involving ToolProperty, ArrayProperty, ObjectProperty, AbstractToolkit, McpConnector, or StdioTransport/SseHttpTransport/StreamableHttpTransport.
tools
Write tests for Neuron AI agents, RAG systems, workflows, and tools using the built-in testing utilities. Use this skill when the user mentions testing agents, writing unit tests, mocking AI providers, testing tool execution, verifying RAG retrieval, testing workflow behavior, or creating test cases for Neuron AI components. Also trigger for any task involving PHPUnit tests, fake providers, test assertions, or quality assurance in Neuron AI projects.
data-ai
Design and implement structured output classes for Neuron AI agents using SchemaProperty attributes and validation rules. Use this skill when the user mentions structured output, JSON schema extraction, data validation, output classes, DTOs for AI responses, extracting structured data from LLM, or configuring property schemas. Also trigger for any task involving SchemaProperty attribute, validation rules like NotBlank/Email/Url, nested objects, arrays of objects, enums, polymorphic types with anyOf, or the Validator class.
development
Implement RAG (Retrieval-Augmented Generation) with Neuron AI including vector stores, embeddings providers, document loaders, and retrieval strategies. Use this skill whenever the user mentions RAG, retrieval, vector search, document retrieval, semantic search, knowledge bases, chat with documents, or wants to build AI systems that can query and understand external documents. Also trigger for tasks involving vector databases, embeddings, document chunking, or retrieval strategies.