1. Go to this page and download the library: Download milpa/orchestrator library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
milpa / orchestrator example snippets
use Milpa\Orchestrator\ProcessDefinition;
use Milpa\Workflow\Entities\GateDefinition;
use Milpa\Workflow\Entities\StateDefinition;
use Milpa\Workflow\Entities\TransitionDefinition;
use Milpa\Workflow\Enums\ApprovalPolicy;
$draft = (new StateDefinition())->setDomain('publish_post')->setCode('draft')->setLabel('Draft')->setIsInitial(true);
$review = (new StateDefinition())->setDomain('publish_post')->setCode('review')->setLabel('In review');
$published = (new StateDefinition())->setDomain('publish_post')->setCode('published')->setLabel('Published')->setIsTerminal(true);
$gate = (new GateDefinition())
->setDomain('publish_post')->setCode('review_gate')->setName('Editorial review')
->setRequesterRole('author')->setApproverRole('editor')
->setApprovalPolicy(ApprovalPolicy::SINGLE);
$submit = (new TransitionDefinition())->setDomain('publish_post')->setCode('submit')->setFromState($draft)->setToState($review);
$approve = (new TransitionDefinition())->setDomain('publish_post')->setCode('approve')->setFromState($review)->setToState($published);
$reject = (new TransitionDefinition())->setDomain('publish_post')->setCode('reject')->setFromState($review)->setToState($draft);
$approve->addGateDefinition($gate); // both outcomes share the SAME gate: one checkpoint,
$reject->addGateDefinition($gate); // two options (approve | reject)
$definition = new ProcessDefinition([$draft, $review, $published], [$submit, $approve, $reject]);
use Milpa\EventStore\FileEventStore;
use Milpa\Eventing\EventDispatcher;
use Milpa\Orchestrator\HumanGate;
use Milpa\Orchestrator\ProcessDefinitionRegistry;
use Milpa\Orchestrator\ProcessRunner;
use Milpa\Orchestrator\Tools\ProcessInstantiateTool;
use Milpa\Orchestrator\Tools\ProcessListPendingApprovalsTool;
use Milpa\Orchestrator\Tools\ProcessSubmitDecisionTool;
use Milpa\ToolRuntime\Contracts\ToolContext;
use Psr\Log\NullLogger;
$store = new FileEventStore('/tmp/posts.jsonl'); // the append-only log
$dispatcher = new EventDispatcher(new NullLogger()); // milpa/events
$dispatcher->subscribe('process.terminal', function (string $name, array $payload): void {
// Reaching `published` runs the real domain effect HERE — the engine itself touches no
// domain entity. $payload = {instance_id, final_state, context}.
});
$registry = new ProcessDefinitionRegistry();
$registry->register('publish_post', $definition);
$gate = new HumanGate(new YourDecisionSurfaceFactory()); // a milpa/live surface, consumer-supplied
$runner = new ProcessRunner($dispatcher);
$instantiate = new ProcessInstantiateTool($store, $gate, $runner, $registry);
$instantiate->setCurrentContext(ToolContext::mcp('req-1', 'agent:author', ['*']));
$list = new ProcessListPendingApprovalsTool($store, $gate, $registry);
$submit = new ProcessSubmitDecisionTool($store, $gate, $runner, $registry);
use Milpa\Orchestrator\ProcessInstance;
// 1. Instantiate — auto-advances draft --submit--> review and PARKS at the human gate.
$started = $instantiate->instantiate('publish_post', ['post_id' => 42]);
$instanceId = $started->data['instance_id'];
$started->data['current_state']; // 'review' — the runner stopped at the gate, not past it
// 2. The gate is pending; its options are projected 1:1 from the process's OWN transitions.
$pending = $list->list()->data['pending'][0];
$pending['assignee']; // 'editor'
$pending['options']; // ['approve', 'reject']
$gateId = $pending['gate_id'];
// 3. An editor — NOT the author — resolves it. Self-approval is refused by construction:
// submitting as 'agent:author' here returns error SELF_APPROVAL_FORBIDDEN instead.
$done = $submit->submit($instanceId, $gateId, 'approve', 'human:editor');
$done->data['current_state']; // 'published' — auto-advanced past the gate to terminal;
// `process.terminal` fired exactly once.
// 4. State is a projection: a FRESH store + handle over the SAME log reconstructs it, no cache.
$replayed = new ProcessInstance($instanceId, $definition);
$replayed->currentState(new FileEventStore('/tmp/posts.jsonl')); // 'published'
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.