Skip to content

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.