Roadmap¶
Built¶
- The engine kernel: token-based execution with
start(),advance()andsignal(), asynchronous advancement through theorchestra_advancequeue. - Execution mode (queued or synchronous), site-wide and per workflow:
synchronous advances a process inline, in the request that starts or resumes
it, with no cron in the loop (the default); queued advances through the
orchestra_advancequeue on cron. See Execution mode. - The entities:
Tenant,Workflow,ProcessInstance,TokenandVariable, multi-tenant from the start. A workflow imports/exports through the Modeler API (a Models collection with an Import tab and export / export-as-recipe operations) and clones viaentity_clone, so no bespoke template UI is needed. - The
TaskTypeplugin type and thestart,end,passthroughandwaitprimitives. - The
Joinplugin type and gateways as(join, split)pairs. A node's join is its incoming-side policy:immediate(merge),wait_all(AND-join), ormatching(the condition-driven OR-join); the decision runs atomically under a per-node lock so concurrent arrivals cannot fire it twice or leave it stuck. WithSplitalready in place, the named gateways are now just presets over the two knobs (parallel= (wait_all, all),exclusive= (immediate, first),inclusive= (matching, all)), driven through the same Join and Split plugins any node uses. A gateway is a routing node with no task of its own; a task node may carry its own join and split too. - The
orchestra_inboxsubmodule: ausertype that parks the token and creates a human task, claimed and completed from a task inbox. Completing a task signals the token, so the process resumes. - The
orchestra_ecasubmodule: aStartProcessECA action that launches a process from any ECA event (ECA to Orchestra), and aneca_eventtask type that emits a custom ECA event when a token reaches it (Orchestra to ECA). - Per-node configuration: nodes carry an optional
configmap, so task types can read their own settings (for example theeca_eventtask's event ID). - The
orchestra_apisubmodule:OrchestraClientInterface, a transport-agnostic contract for driving workflows (start, signal, inspect, variables), withLocalOrchestraClientwrapping the engine in-process and scoping every operation to the current tenant. Callers depend only on the interface, so the same code will run same-site and cross-site. - The
orchestra_server_apisubmodule: an OAuth-gated HTTP surface (/orchestra-api/...) whose controllers are a thin transport over the client. The acting OAuth consumer is bound to a tenant (an immutableorchestra_tenantfield added to the consumer), so every API call is scoped to that consumer's tenant via a tagged tenant resolver. - The
orchestra_clientsubmodule:RemoteOrchestraClient, which bindsOrchestraClientInterfaceto the server API over HTTP (client-credentials OAuth, the secret held by the Key module). Enabling it rebinds the contract to the remote adapter, so the same caller code runs against a remote site. The local and remote halves are now symmetric. - Human-node action handoff on interactions (
orchestra_inbox+orchestra_interaction): a human node's action is completed inline (a one-click outcome, or a comment task's note) or through an interaction that owns its own surface (a comment/review page, a webform, an off-site page resumed via a continuation handle). External systems integrate through the interaction bearer gateway, not a signed callback. - The
FlowConditionplugin type and conditional flows: a flow may carry a condition (acomparisonof a process variable, or anall/anycomposite nesting others into a boolean tree). When a node finishes, the engine sends a token down every outgoing flow whose condition holds. That one rule is the split: mutually exclusive conditions give an exclusive choice, none give a parallel fork, overlapping ones give an inclusive split, and a task branches on its own, no gateway needed. - The
Splitplugin type: a node's split chooses, among the live (condition- passing) outgoing flows, which the token follows:all(the default, a fork) orfirst(an exclusive choice). Split and condition compose: the condition says which branches are eligible, the split says how many. Custom splits (weighted, random) are now just plugins. - Token lineage and the join merge policy: each token records its parent, so a
token-local variable set on a branch is inherited by that branch's
descendants and reaches a downstream join. A variable is instance-wide by
default, or token-local when scoped to a token. A join may declare a
mergethat collects one variable from each joined branch into a list; with thecountcondition this expresses a synchronous quorum ("two of three approved") from generic pieces (wait_all+ merge + count) with no special node. See theexample_quorumworkflow inorchestra_examples. - Per-task assignment (
orchestra_inbox): anAudienceplugin that also implementsAssignmentInterfacetargets a user task at an audience. It answers two questions in opaque string tokens: who a task is for (candidates) and which audiences a viewer belongs to (viewerTokens), and the inbox shows a task when the two sets intersect. The resolved audience is materialized onto a multi-valuecandidatesfield at task creation; an empty set leaves the task pooled. Four plugins ship (usersandroles, named in config;users_variableandroles_variable, resolving their audience from a process variable, a uid list or a role-name list decided at runtime by an upstream step); a node carries them as the flat modeler fieldsassignee_users/assignee_rolesor as a structuredassignmentslist (several union). New audiences (group, expression) are new plugins minting a new token namespace, with no schema or storage change. Theexample_quorumworkflow shows the structured form, its third ballot a union of a role pool with a named user. - Visual authoring (
orchestra_modeler,orchestra_bpmn_io,orchestra_cm):orchestra_modeleris the Modeler API model owner, mapping Orchestra nodes and flows to a modeler's components and back so a workflow round-trips id-stable (running instances are never orphaned), with element templates carrying each node's task type and per-node settings.orchestra_bpmn_ioadapts the BPMN.io canvas (layout capture/restore, palette and context-pad restrictions);orchestra_cmis the accessible, form-based "Complete Modeler" alternative that also edits the node-level structures a canvas cannot (timer ladders, assignments). A subscriber warns before editing a workflow that has running instances. See Integrations. - A browser UI (
orchestra_ui): screens to start a workflow, observe an instance's trace (its whole token trail in order, consumed tokens included) and its variables, and manage instances, including bulk deletion that cascades to the instance's tokens, variables and tasks. Each parked human step in the trace offers an Open link to its operator surface (the task form, the operation page or the interaction), the same page the pending actions list opens, so an operator can act on a step straight from the trace. The trace needs no Views;orchestra_viewsadds richer trace and reporting dashboards on top. - Task timeouts in the engine core: a parking node opts in with a
timeoutnaming aDeadlineProvider(when the wait runs out) and aTimeoutAction(what to do once it has), each with its own settings. The bundled providers arerelative(a duration, measured from each park, from the start of the run or from the first arrival at the step, falling back to the site-widedefault_timeoutthat is the safety net against any task hanging forever) andabsolute(a date, optionally shifted by a signed offset). A module ships its own when the deadline belongs to a live subject the run does not own: it is asked as the task parks, so no step has to exist merely to keep a written deadline current. On expiry a cron sweep runs the action:resume(the default),notify(a recurring reminder via an event/ECA),spawn(fork a parallel branch, keep waiting) andunclaim(release a stale claim); an action that resumes with an outcome of its own declares it, so the editor can tell an author what to route on. For a schedule, a node declares stagedtimers({after, action, repeat}) that the engine arms as one timer token each: fired at its offset, canceled when the task is answered. Arepeatcount makes a timer recurring (re-arm N times, or 0 = until answered), so one stage can remind every day while another escalates or gives up later. See Timers. - Token cancellation:
WorkflowEngine::cancel()moves a live token and its live descendants to a terminalCANCELEDstate and completes the instance if nothing else is live. The shared primitive for ending work early: a superseded branch, a task's pending timers, the late branches of a discriminator join. - Subprocesses: a
subprocesstask type runs another workflow as a child (in the parent's tenant), parks the parent token, and resumes it when the child completes, injecting the child's mapped output into the parent (input/outputrename maps, one variable per line,nameorname: other). The engine resumes the parent generically via the instance'sparent_token; the mapping lives in the task type (SubprocessTaskInterface), so any task that runs a child process plugs in. The token parks, so escalation timers apply to a stuck child, and a timeout (or a cancel) that moves the parent on cancels the orphaned child. - Task reassignment (
orchestra_inbox): on top of claim (self-assign), the inbox offers Reassign (hand a task to another user, who becomes its claimed assignee) and Return to pool (clear a claimed task's assignee so its audience can claim it again). A manager (the newreassign orchestra taskspermission) may reassign any task; a task's current assignee may reassign their own. Reassignment goes throughWorkItemManager::reassign()(terminal tasks are immutable) and is logged to the orchestra channel for an audit trail. It transfers ownership outright, which is what distinguishes it from a delegation. - Delegation, absence cover (
orchestra_delegation): for a period, one user acts on another's tasks, seeing both their pooled and their already-claimed work in their own inbox and completing it on their behalf. Core carries only the question (DelegationResolverInterface, answered "nobody" by default); the submodule stores the delegations and decorates it, so a site whose absences live in an HR system can answer from there instead. One hop (cover does not chain), one tenant, and it grants no permission: the delegate still needsprocess orchestra tasks. A completed task records the delegator in itson_behalf_offield, distinct from the assignee (which a delegation never moves) and the completer (who acted). See Delegation. - Reminders and escalation to a person (
orchestra_inbox): areassigntimeout action (the timer-driven half of the manual Reassign) hands a timed-out task to an audience chosen with an assignment plugin (the sameusers/roles/users_variable/roles_variableplugins assignment uses). A single resolved user is assigned the task directly; a pool (a role, several users) is re-offered to claim. Paired with the recurringnotifyreminder in a stagedtimersladder, the common "remind the assignee at T1, escalate to a manager at T2" pattern is now pure config, with no custom code. See Timers. - Loops and task regeneration: a flow pointing back to an earlier node is a
loop, with no special construct. Re-entering a parking node regenerates its
work (a
userre-parks and creates a fresh task each pass), and a join re-entered by a loop re-arms on its own, its only state is the tokens waiting at the node, which firing consumes, so the next pass starts clean. (The early-firing threshold join scopes its straggler teardown per iteration with fork cohorts: a loop re-entering the split starts a fresh cohort, so closing one does not touch the next pass.) See Loops and re-entry. - Views dashboards and bulk actions (
orchestra_views,orchestra_vbo,orchestra_vbo_inbox): the runtime entities, instances, tokens, variables and tasks, are exposed to Views with readable workflow and node labels, their references as relationships, and a current-tenant filter (backed by anorchestra_tenantcache context), plus three ready-made dashboards (processes, tokens, inbox).orchestra_vbolayers Views Bulk Operations actions over them, cancel or delete a process, signal or cancel a token, andorchestra_vbo_inboxthe task-inbox actions (claim, complete with an outcome, reassign). See Views and dashboards. - Content-bound tasks (
orchestra_content): a process binds to a content entity (via theorchestra_attachmentside entity, no fields on the kernel), and anentity_forminteraction (on an interaction task) opens that entity's edit form as the work of the step, save-and-decide in one screen, reusing the inbox, assignment and outcomes. See Content-bound tasks. - Content moderation (
orchestra_content_moderation): amoderation_transitiontask sets the attached entity'smoderation_state, so an Orchestra decision drives Drupal's editorial states, the approved branch publishes, the rejected branch sends back. The workflow graph is the mapping. Ships anexample_content_reviewworkflow. - Content + ECA glue (
orchestra_content_eca): a "start process for this entity" ECA action (start + attach in one step) and a "load the attached entity" action, so an event model starts content reviews and a reacting model can set state via content_moderation's own ECA action, the "when to start" living in the model. Ships an example ECA model that reviews new articles. - Action tasks (
orchestra_action): anactiontask runs a Drupal Action plugin as automated work, then advances, Orchestra's answer to Maestro's batch function, on Drupal's own action system rather than a callable string. An entity action runs on the attached entity or one named by a variable; a system action runs as-is; a configurable action's settings are embedded in the node. A failing action with a payload variable routes to an error branch (success/failure), or fails the process without one. An action implementing the kernel'sOrchestraContextAwareInterfaceis handed the instance and token first, so it can read and write process variables, parity with a Maestro function's process/queue IDs. See Action tasks. - Durable audit log: the engine and inbox dispatch a neutral
OrchestraAuditableEventat each lifecycle transition (instance started/completed/failed/canceled, a canceled token, the human-task transitions), correlated by the instance UUID. The optionalorchestra_audit_trailsubmodule records those into the Audit Trail chain, a durable, tamper-evident record that survives instance deletion, with per-event config gating and a permanent/transient bucket split. The kernel keeps no audit_trail dependency (it only dispatches the event), so the same event is the spine the notifications feature reuses. See Audit log. - Incidents: a token whose task fails deterministically is dead-lettered after a bounded number of retries instead of retrying forever. By default this raises an incident and leaves the instance's other branches running for an operator to resolve (retry, resume, skip, cancel the branch, or fail the instance), rather than failing the whole process. See Incidents.
- Notifications (
orchestra_mail): the inbox dispatches those same neutral events, and the optionalorchestra_mailsubmodule turns the two human-task ones into email, a message to a task's audience when it is assigned and a reminder when it times out. Who is notified is configured per assignment, anotifyflag on eachusers/roles/users_variable/roles_variableassignment, which resolves its own audience to recipients, so pooling to a broad role never mails people by surprise (named users notify by default, a role opts in). The kernel keeps no mail dependency; disable the submodule to deliver the same events another way (ECA, the Message stack). A companionorchestra_interaction_notificationsubmodule dispatches the neutral event for the link instead of the inbox, for a party reached through the bearer doorway: a node's Notify on arrival audience is sent the capability link to its step when the branch parks, and any channel delivers it. See Notifications. - Early-firing threshold join (
threshold): an N-of-M join that fires the moment N branches arrive, then tears the rest of the fork cohort down, cancelling the branches still in flight (and their inbox tasks) so a late branch neither carries on nor re-fires the join. Each token records its fork cohort; firing early closes it, and a still-live branch of a closed cohort self-cancels at its next step, with no lock on the hot path. Cohorts are per-split and per-iteration, so a loop starts a fresh one. See Joins and splits. - Quorum join (
quorum): the value-aware sibling of the threshold join. It reads each arrived branch's collected vote and fires as soon as the decision is settled, either enough branches approve, or approval becomes unreachable (enough have come back negative), ending early on both outcomes and tearing the rest of the cohort down. Routing to the approved or rejected path is the usual merge plus acountcondition over the collected votes. See Joins and splits. - Timeout join (
timeout): waits for every incoming branch likewait_all, but gives up once a deadline passes, firing with whatever has arrived and tearing the branches still running down (reusing the threshold join's cohort teardown). The deadline is armed on the first branch to wait and the cron timeout sweep wakes the join, so it needs no clock beyond the sweep already run for parked tasks. See Joins and splits. - Domain tenancy (
orchestra_domain): resolve the active tenant from the current domain. Each domain is bound to a tenant (a third-party setting on the domain record, edited on the domain form), and a tagged tenant resolver maps the negotiated domain to it, so each domain runs its own isolated set of processes on one site. See Multi-tenancy. - Pull-based human operations: the
user_operation,comment_operationand interaction-operation task types park an assignment-gated step with no inbox work item, acted on from the "My Orchestra actions" list. Completion is unified with the inbox behind oneOutcomeSignaler. See Human tasks. - Payment as a workflow step (
orchestra_payment, experimental): a payment interaction node and a settlement subscriber (authorize-then-capture, settle or fail a parked payment by hand), backed by the Kessai payment engine. The step never prices what it has not first held still: rendering the page previews only, and the click on "Go to payment" announces a checkout opening, so a domain module freezes whatever it owns before the charge is priced. The amount the visitor was shown travels back signed and is compared against what the frozen subject prices, and a subscriber may refuse the checkout outright with reasons the payer reads. See Payment. - Task failure classification and retry: a task failure is retryable, an outcome, or fatal, with a configurable retry policy and an on-demand Retry action from the instance trace. See Incidents.
- Notification template resolution by a tenant / workflow / type / suffix cascade for the Easy Email channel. See Notification delivery.
- The process initiator: the start-user is recorded on each instance (distinct from the audit actor), surfaced as a list column, a Views field and a filter. See Concepts.
- A requester-facing surface: a "My requests" page and a "my workflow instances" View list the instances a user started, each with a read-only detail view, gated by a dedicated View own Orchestra process instances permission (no operator rights). See Views and dashboards.
- A workflow-authored status vocabulary: the tenant-scoped
orchestra_statusconfig entity a node references and a requester filters on, resolved to a labeled, palette-styled tag, distinct from the engine state. Managed at Configuration > Workflow > Orchestra > Statuses, exposed as a State/Status Views field and filter and the[orchestra:status]token. See Concepts.
Planned¶
Nothing is currently queued: the engine is feature-complete against this roadmap.
Maestro parity¶
Orchestra's engine is, by design, more capable than Maestro's (a token/Petri-net core with real gateways, token-scoped variables and merge/quorum); the gap was only the content-workflow ergonomics and task-management UX layered on top, and that gap is now closed.
At parity: content-bound interactive tasks (orchestra_content's entity_form
interaction), content moderation (orchestra_content_moderation) and
automated / named-function tasks (orchestra_action), the content-approval core
of Maestro; loops and task regeneration (a flow back to an earlier node
regenerates a re-entered task, see
Loops and re-entry); template clone plus
import/export (the Modeler API's Export, Export as recipe and Import, and
entity_clone, see Integrations); a durable, tamper-evident
audit log (orchestra_audit_trail, see Audit log); and email
notifications on assignment and reminder (orchestra_mail, see
Notifications).
The content-approval workflows Maestro targets are fully covered.