Human tasks¶
The orchestra_inbox submodule turns a parked token into work a person
does. It adds a user node type, a WorkItem entity (the inbox record), and an inbox where work items
are claimed and completed. Completing a task signals its token, so the process
resumes, the same wait + signal primitive the kernel already has, with a
human in the loop.
The lifecycle¶
A user node, when its token arrives, creates an open task and parks
the token. The task waits in the inbox until someone completes it; the chosen
outcome is written to the node's result_variable, and the token is
signalled so the process moves on.
sequenceDiagram
actor User
participant Engine
participant Task as User task
participant Inbox
Engine->>Task: token reaches user node
Task-->>Engine: create open task, Park the token
User->>Inbox: open /orchestra/tasks
Inbox-->>User: tasks they may act on
User->>Task: claim (now mine)
User->>Task: complete with an outcome
Task->>Engine: write result variable, signal the token
Engine-->>Engine: token advances past the node
A task moves through a small state machine. It starts open; claiming makes it claimed by one user; a task handed off to an external handler (the Process action) becomes in progress while it awaits the completion callback; completing makes it completed. A task whose instance is deleted (or otherwise abandoned) is cancelled.
stateDiagram-v2
[*] --> open: node creates the task
open --> claimed: a user claims it
claimed --> open: claim timeout (unclaim)
claimed --> in_progress: handed off (Process)
claimed --> completed: the user completes it
in_progress --> completed: the handler reports completion
open --> completed: completed while still pooled
open --> cancelled: instance deleted
claimed --> cancelled: instance deleted
in_progress --> cancelled: instance deleted
completed --> [*]
Claim timeout¶
A user node with timeout_action: unclaim (and a timeout) reclaims a
stale hold: if a task sits claimed past its deadline without being acted on,
it is released back to open for its pool to pick up again. Only the claimed
state is released; a task that is in progress (the assignee is at its
external handler) is left alone, since it is actively being handled. See
Timers.
The inbox¶
The inbox at /orchestra/tasks lists the tasks the current user may act on,
gated by the process orchestra tasks permission. A task offers a claim
action while it is open and unassigned, and a complete action (with the
node's outcomes as choices) once it is the user's to finish.
Each outcome is a machine value (recorded as the result, routed on by flow
conditions). A node may also set an outcome label per value, a
translatable, human-readable string shown on the completion button (e.g.
"Approved by manager" for approved) while the value stays unchanged. Labels
are translated per language through Config Translation, and a task type can
override one in code via WorkItemPresentationInterface::getOutcomeLabel(); an
outcome with no label shows its value.
Which tasks a user sees is decided by assignment:
a task is visible when it is assigned to the user, or it is unclaimed and either
pooled (no candidates) or carries a candidate token the user holds. The match is
a plain query against the task's materialized candidates field, not a
per-request recomputation.
Reassignment and delegation¶
Beyond claiming, the inbox can hand a task to someone else. A user with the
reassign orchestra tasks permission may Reassign any task to another user;
a task's current assignee may delegate their own the same way (no extra
permission). Reassignment makes the chosen user the task's claimed assignee
whatever its prior state, a terminal (completed or cancelled) task is left
untouched, and the change is logged to the orchestra channel as an audit
trail.
A claimed task that was offered to a pool of more than one candidate also offers
Return to pool: it clears the assignee and reopens the task so its pool can
claim it again. A directly-assigned or single-candidate task has no pool to fall
back to, so the action is not shown, though the same release the
claim timeout uses underlies both.
Notifications¶
So an assignee need not watch the inbox, the optional
orchestra_mail submodule emails a task's audience when it
is assigned and again when it is reminded (the notify timeout action).
Notification is opted in per assignment, a Notify this audience checkbox on
each users / roles / users_variable / roles_variable assignment, on by
default for named users and opt-in for a role pool. The inbox only dispatches
the events; leave the submodule off to notify another way (ECA).
The four staffing audiences differ only in how they name the audience: users
and roles name it in config, while users_variable and roles_variable read
it at runtime from a process variable (a uid list or a role-name list). The
variable plugins let an earlier step decide who acts: the submission-validation
example seeds the original poster's uid into a variable and assigns the modify
step back to them with users_variable. See
Task assignment.
External task handoff¶
A user node may name an external handler_url. Such a task shows a
Process action instead of inline completion: it claims the task, marks it
in progress (so the inbox shows it is being handled while it awaits the
callback), and sends the assignee to the handler, passing a signed completion
URL, an absolute URL to complete-remote carrying an HMAC of the task (its
UUID and an expiry timestamp, under the site's private key and hash salt). The
URL is time-limited (a 30-day TTL bound into the signature), so it is not a
permanent bearer credential once it leaks into a log or proxy.
That action's label is configurable. The node's Action title (action_title)
replaces the default Process; it is a translatable string (Config Translation
translates it per language, alongside the node's own label) and may carry
@variable placeholders filled from the instance's variables, e.g.
Review @doc_type. For a fully dynamic label, a task type can implement
WorkItemPresentationInterface::getActionTitle() and compute it in code; the
resolution order is the code override, then the configured action_title, then
the default Process.
The external handler does its work, then resumes the process by POSTing to that URL with its signature, no Drupal login needed. The signature is verified with a constant-time comparison, and an expired signature is rejected; only a valid, matching signature completes the task and signals the token. Because completion is idempotent, a retried callback (within the signature's validity window) is harmless.
The callback may carry an outcome (a result field) and extra data (e.g. a
comment), recorded as a structured {result, comment} task result: the
outcome routes the process (a flow condition on decision.result) while the
comment rides along on decision. The orchestra_examples module ships a
working handler at /orchestra-example/review-handler, a comment plus
Approve / Reject, so the whole example_approval round-trip is
clickable. That handler is a deliberately small example of writing your own
external handler.
The built-in review form¶
For the common case, a same-site review with a button per outcome, you do
not need to write a handler at all. Point a user node's handler_url at the
review form Orchestra ships, internal:/orchestra/tasks/review-handler, and it
builds itself from the work item it is handed: the page title is the task's
title, and it offers one submit button per configured outcome, each labeled
with that outcome's (translatable) Outcome label. It also offers an optional
comment recorded alongside the chosen outcome (as decision.comment next to
decision.result). Because this form runs inside the same Drupal as the engine,
choosing an outcome completes the work item directly through the work item
manager and returns the user to the inbox, it does not POST to the signed
callback (that callback exists for a remote handler on another site; a
same-site form calling it would force an HTTP round-trip back to the site
itself). So a reviewable task needs only an action_title, a list of outcomes
with their labels, and this handler_url.
The orchestra_examples module ships example_review, a clickable round-trip
built this way.
sequenceDiagram
actor User
participant Inbox
participant Handler as External handler
participant Engine
User->>Inbox: Process (a task with a handler_url)
Inbox-->>User: redirect to handler_url + signed completion URL
User->>Handler: do the work on the external site
Handler->>Engine: POST complete-remote?signature=...
Engine-->>Engine: verify HMAC, complete, signal the token
Interaction tasks¶
A user node completes through outcome buttons or a handoff. An
interaction_task node (in orchestra_task_interaction) completes through
an interaction plugin instead: it parks the token and assigns the task like
any other, but its body is the same interaction the public dispatcher serves
(a Webform to fill, review or edit; a message; a custom plugin). The assignee
opens it from the inbox and acts on it in-site, no public link involved.
This is the identity doorway to the interaction system: one interaction
plugin runs either as a capability-token link to an outside party (the
external interaction dispatcher) or as a task to a logged-in
assignee, with only the doorway differing. An interaction_task carries the
chosen interaction_plugin and its interaction_settings, and otherwise reads
like a user node: it has assignments, outcomes and a result_variable, so it
notifies, claims and routes exactly as above. It records the same structured
{result, comment} result, so a flow routes on <var>.result whether the
assignee chose an outcome alone or left a comment with it.
The orchestra_webform_example module is a worked example: a submission a
validator reviews (a Webform interaction in Review mode), can send back for
changes (the original poster edits their own submission in a modify task), and a
processor finally handles. The same workflow ships in a link variant where
the modify step is a public link mailed to the poster instead of a task, the
two doorways side by side. See external interaction.