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 (AND-join), or matching (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. 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_api submodule: OrchestraClientInterface, a transport-agnostic contract for driving workflows (start, signal, inspect, variables), with LocalOrchestraClient wrapping 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_api submodule: 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 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.
  • External task handoff (orchestra_inbox): a user node with a handler_url shows a "Process" action that claims the task and sends the assignee to that external handler, passing a signed completion URL. The handler resumes the process by calling that URL with its signature (no login), and the call is idempotent.
  • 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 a merge that collects one variable from each joined branch into a list; 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.
  • 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; an empty set leaves the task pooled. Four 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); a node carries them as the flat modeler fields assignee_users / assignee_roles or as a structured assignments list (several union). New audiences (group, expression) are new plugins minting a new token namespace, with no schema or storage change. The example_quorum workflow shows all three forms.
  • 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. 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 (seconds or an ISO-8601 duration), and a site-wide default_timeout is a safety net against any task hanging forever. On expiry a cron sweep runs the node's TimeoutAction plugin: resume (the default), notify (a recurring reminder via an event/ECA), spawn (fork a parallel branch, keep waiting) and unclaim (release a stale claim). For a schedule, a node declares staged timers ({after, action, repeat}) that the engine arms as one timer token each: fired at its offset, cancelled 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 CANCELLED status 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 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 result 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 result 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 and delegation (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 delegate their own. Reassignment goes through WorkItemManager::reassign() (terminal tasks are immutable) and is logged to the orchestra channel for an audit trail.
  • 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), 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 an orchestra_tenant cache context), plus three ready-made dashboards (processes, tokens, inbox). 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 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 bridge (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.
  • 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): 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 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 result variable routes to an error branch (success/failure), or fails the process 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/cancelled, a cancelled token, the human-task transitions), correlated by the instance UUID. The optional orchestra_audit_trail submodule bridges 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 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; disable the submodule to deliver the same events another way (ECA, the Message stack). A companion orchestra_interaction_notification submodule 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 a count condition over the collected votes. See Joins and splits.
  • Timeout join (timeout): waits for every incoming branch like wait_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.

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 task), the content-moderation bridge (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.