Skip to content

Roadmap

Built

  • The engine kernel: token-based execution with start(), advance() and signal(), asynchronous advancement through the orchestra_advance queue.
  • 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_advance queue on cron. See Execution mode.
  • The entities: Tenant, Workflow, ProcessInstance, Token and Variable, 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 via entity_clone, so no bespoke template UI is needed.
  • The TaskType plugin type and the start, end, passthrough and wait primitives.
  • The Join plugin type and gateways as (join, split) pairs. A node's join is its incoming-side policy: immediate (merge), wait_all (every branch), or matching (the branches that can still arrive); the decision runs atomically under a per-node lock so concurrent arrivals cannot fire it twice or leave it stuck. With Split already 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_inbox submodule: a user type 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_eca submodule: a StartProcess ECA action that launches a process from any ECA event (ECA to Orchestra), and an eca_event task type that emits a custom ECA event when a token reaches it (Orchestra to ECA).
  • Per-node configuration: nodes carry an optional config map, so task types can read their own settings (for example the eca_event task's event ID).
  • The orchestra module: OrchestraClientInterface, a transport-agnostic contract for driving workflows (start, signal, inspect, variables). Callers depend only on the interface, so the same code will run same-site and cross-site.
  • LocalOrchestraClient in the engine, wrapping it in-process and scoping every operation to the current tenant.
  • The orchestra_server submodule: an OAuth-gated HTTP surface (/orchestra-api/...) whose controllers are a thin transport over the local client. The acting OAuth consumer is bound to a tenant (an immutable orchestra_tenant field added to the consumer), so every API call is scoped to that consumer's tenant via a tagged tenant resolver.
  • The orchestra_client submodule: RemoteOrchestraClient, which binds OrchestraClientInterface to 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.
  • Resuming a parked step from another site: the client contract checks and completes the step a resume handle names, with a proof of the person, and the engine decides with the checks its own pages use (StepResume).
  • 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 resume handle). External systems integrate through the interaction bearer gateway, not a signed callback.
  • The FlowCondition plugin type and conditional flows: a flow may carry a condition (a comparison of a process variable, or an all/any composite 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 Split plugin type: a node's split chooses, among the live (condition- passing) outgoing flows, which the token follows: all (the default, a fork) or first (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 collect lines, each gathering one variable from the joined branches into its own list, in arrival order and optionally without repeats; with the count condition this expresses a synchronous quorum ("two of three approved") from generic pieces (wait_all + merge + count) with no special node. See the example_quorum workflow in orchestra_examples. A synchronizing join can also keep the branches' token-scoped variables on the token going on, each from the first or the last arrival that set it.
  • Per-task assignment (orchestra_inbox): an Audience plugin that also implements AssignmentInterface targets 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-value candidates field at task creation; no candidates at all is a step open to anyone who may process tasks. Every human node must name an audience: the modeler refuses to save a step that names none, and a definition that reaches the engine without one halts with an incident rather than parking a task no account can see. Five plugins ship (users and roles, named in config; users_variable and roles_variable, resolving their audience from a process variable, a uid list or a role-name list decided at runtime by an upstream step; and everyone, which restricts nobody and is how an open step says so out loud); a node carries them as a structured assignments list (several union), which a canvas modeler edits through one field per audience that is one list (assignee_users, assignee_roles). New audiences are new plugins minting a new token namespace, with no schema or storage change: the optional orchestra_group submodule adds groups and groups_variable that way. The example_quorum workflow 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_modeler is 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_io adapts the BPMN.io canvas (layout capture/restore, palette and context-pad restrictions); orchestra_cm is 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. On a task somebody else holds, the link is offered only to who may act on it, and a manager who may take the task over is offered Reassign instead, plus Return to pool for a claimed task drawn from a pool. The trace needs no Views; orchestra_views adds richer trace and reporting dashboards on top.
  • Task timeouts in the engine core: a parking node opts in with a timeout naming a DeadlineProvider (when the wait runs out) and a TimeoutAction (what to do once it has), each with its own settings. The bundled providers are relative (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-wide default_timeout that is the safety net against any task hanging forever) and absolute (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) and unclaim (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 staged timers ({after, action, repeat}) that the engine arms as one timer token each: fired at its offset, canceled when the task is answered. A repeat count 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 terminal CANCELED state 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 early-firing join.
  • Subprocesses: a subprocess task 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/output rename maps, one variable per line, name or name|other). The engine resumes the parent generically via the instance's parent_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 new reassign orchestra tasks permission) may reassign any task; a task's current assignee may reassign their own. Reassignment goes through WorkItemManager::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 needs process orchestra tasks. A completed task records the delegator in its on_behalf_of field, distinct from the assignee (which a delegation never moves) and the completer (who acted). See Delegation.
  • Reminders and escalation to a person (orchestra_inbox): a reassign timeout action (the timer-driven half of the manual Reassign) hands a timed-out task to an audience chosen with an assignment plugin (the same users / roles / users_variable / roles_variable plugins 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 recurring notify reminder in a staged timers ladder, 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 user re-parks and creates a fresh task each pass). A join fires once for each pass through its opening split, with that pass's branches only, so a loop back through the split starts a new pass that the join fires for again. A join set to cancel the split tears down only the pass that fired it. 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 an orchestra_tenant cache context), plus six ready-made dashboards: processes, tokens, completed processes and one instance's trace here, with the inbox and the acting user's own tasks shipping in orchestra_inbox_views, which reads the work item. orchestra_vbo layers Views Bulk Operations actions over them, cancel or delete a process, signal or cancel a token, and orchestra_vbo_inbox the 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 the orchestra_attachment side entity, no fields on the kernel), and an entity_form interaction (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. An attach_entity task binds content the run only learned about while running, the entity two of its own variables name. See Content-bound tasks.
  • Content moderation (orchestra_content_moderation): a moderation_transition task sets the attached entity's moderation_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 an example_content_review workflow, which binds its own content at its first step, so starting it takes nothing but the two variables naming it.
  • Content + ECA glue (orchestra_content_eca): a "load the attached entity" ECA action, so a model reacting to an Orchestra event can act on the run's content, for example setting its state via content_moderation's own ECA action. Starting a content-bound run needs nothing from this module: the generic "start process" action names the entity in variables and the workflow's attach step binds it. Ships an example ECA model that reviews new articles, which is exactly that pairing.
  • Action tasks (orchestra_action): an action task 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 entity a variable names, or, through the content_action task its orchestra_content_action sibling ships, on the run's own attachment; 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 halts the step on an incident without one. An action implementing the kernel's OrchestraContextAwareInterface is 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 OrchestraAuditableEvent at each lifecycle transition (instance started/completed/failed/canceled, a canceled token, the human-task transitions), correlated by the instance UUID. The optional orchestra_audit_trail submodule 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 any subscriber, a log of your own for example, can record it instead. Notifications do not ride it: reaching people is a separate concern, with an event of its own. 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 optional orchestra_mail submodule 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, a notify flag on each users / roles / users_variable / roles_variable assignment, 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; swap the delivery channel to deliver the same events another way (Easy Email, the Message stack). orchestra_interaction dispatches the neutral event for the link instead of the inbox, for a party reached through the link 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, counting the branches still on their way, then cancels the branches still heading to it (and their inbox tasks), or the whole split they came out of when set to do so, so a late branch neither carries on nor re-fires the join. What is on its way is read from the workflow's flows, whatever their conditions. Each token records its fork cohort; canceling a split 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 tests its quorum condition, a flow condition, on each arrived branch and fires as soon as the decision is settled, either the condition holds on enough branches, or that becomes unreachable (enough have come back without it), ending early on both outcomes and tearing down what is left as the threshold join does. Routing on the outcome compares the number of branches the condition held on, which the join writes to its result variable when it fires. See Joins and splits.
  • Join timeout (join_timeout): any join that waits for branches can give up on a deadline, with the deadline providers, anchors and outcome a task's timeout has. Fire the join with what arrived, remind, or stop on an incident. See Timeouts at a join.
  • Domain tenancy (orchestra_domain): resolve the active tenant from the current domain. Each tenant names the domains it lives on, on its own Domains form (a domain record holds Domain's configuration and nothing of ours), and a tagged tenant resolver maps the negotiated domain back to the tenant that names 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_operation and interaction-operation task types park an assignment-gated step and mint the same WorkItem an inbox task does, differing only in its mode: operation rather than task, so it is acted on from the "My Orchestra actions" list instead of the inbox. Completion is unified with the inbox behind one OutcomeSignaler. 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 link holder 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.
  • Nobody waits for a notification to be sent, and a lost one is sent again: each is queued with the step and goes once the page has been sent, before it, or by cron after a delay, as its notification profile says. A failed send is tried again for the recipients it missed, and one given up is a line in the process's history or, for an interaction link, an incident whose Send again sends it. A node, a workflow, a tenant and the site each name the profile. See When a notification is sent.
  • 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_status config 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.