The Workflow Engine (Steps)
The Workflow Engine orchestrates signing workflows through a recursive Step tree. If a Blueprint is the plan, the orchestration steps are the detailed instructions within that plan.
The Step Model
A workflow is a tree of Steps. A step is either a grouping of child steps (e.g. "Compliance Phase") or a single action like a participant signing a document. Group steps can nest to any depth.
Two primitives — groups and participants — combined with sequential/parallel execution, completion rules, and data-driven includeIf conditions, can express nearly any signing workflow you'd want to model: single-signer NDAs, multi-party contracts, approval chains with conditional routing, quorum-based approvals, phases that merge after parallel work, workflows that update data mid-flight.
Two Step Types
Group — contains child steps and controls their execution:
- key: compliance_phase
type: group
name: Compliance Phase
execution: parallel # Children execute concurrently
completion: all # Wait for all children
children:
- ...child steps...
Participant — a single signing task performed by a person:
- key: new_hire_signs_nda
type: participant
participant: new_hire
action:
type: sign
envelope: compliance_envelope
assignments:
- document: nda
role: signer
Execution Modes
The execution property on a group controls when children start:
| Mode | Behavior |
|---|---|
sequential |
Children run one at a time, in order. Like a checklist. |
parallel |
All children start at once. Like a team assignment. |
Completion Rules
The completion property on a group controls when the group is done:
| Rule | Behavior |
|---|---|
all |
Wait for every child (default) |
any |
Complete as soon as any one child finishes |
quorum |
Complete when N children finish (set quorum: N) |
Common Patterns
Sequential Signing (Checklist)
HR signs first, then the new hire:
orchestration:
key: root
type: group
execution: sequential
children:
- key: hr_sends_offer
type: participant
participant: hr_manager
action:
type: sign
envelope: offer_envelope
assignments:
- document: offer_letter
role: hr_representative
- key: new_hire_accepts
type: participant
participant: new_hire
action:
type: sign
envelope: offer_envelope
assignments:
- document: offer_letter
role: employee
Parallel Signing (Everyone at Once)
Both parties sign concurrently, then the witness:
orchestration:
key: root
type: group
execution: sequential
children:
- key: signing_phase
type: group
execution: parallel
children:
- key: party_a_signs
type: participant
participant: party_a_signer
action:
type: sign
envelope: contract_envelope
assignments:
- document: contract
role: party_a
- key: party_b_signs
type: participant
participant: party_b_signer
action:
type: sign
envelope: contract_envelope
assignments:
- document: contract
role: party_b
- key: witness_signs
type: participant
participant: witness
action:
type: sign
envelope: contract_envelope
assignments:
- document: contract
role: witness
Quorum (2 of 3 Board Members)
- key: board_approval
type: group
execution: parallel
completion: quorum
quorum: 2
children:
- key: member_1_signs
type: participant
participant: board_member_1
action:
type: sign
envelope: resolution_envelope
assignments:
- document: resolution_doc
role: board_member_1 # Each member needs a distinct role so
- key: member_2_signs # their signatures land on distinct fields
type: participant
participant: board_member_2
action:
type: sign
envelope: resolution_envelope
assignments:
- document: resolution_doc
role: board_member_2
- key: member_3_signs
type: participant
participant: board_member_3
action:
type: sign
envelope: resolution_envelope
assignments:
- document: resolution_doc
role: board_member_3
Conditional Steps
Use includeIf to skip steps based on data:
- key: manager_approves
type: participant
participant: hiring_manager
includeIf: '$.position.level > 3'
action:
type: sign
envelope: offer_envelope
assignments:
- document: offer_letter
role: manager
Evolving Data Across Steps
Often you'll want data captured or decided in one step to drive a later step — a reviewer picks a discount tier that the next document reflects; an approver's notes carry into the final contract; a status set in step 3 gates whether step 4 runs.
The workflow's entity context — the running workflow's entity data — is seeded at creation from the data you pass in (see Workflows > Creating a Workflow). Every step's expressions read from that context. The mechanism for evolving it is the onComplete block: each step can update the context when it finishes, and every downstream step sees the new state.
Sequential execution handles this naturally. Each step sees the state left by the step before it; tree order is update order. You don't think about it — the engine just carries changes forward.
Parallel execution is where things get interesting: two children can't both write to the same entity without colliding. SignStack uses a sandbox-and-merge pattern — each branch works in isolation, then the parent group's onComplete reconciles. See Handling Parallel Data for the full pattern.
The onComplete Block
Works on both group and participant steps — on a group, it runs after all required children complete; on a participant step, after the signing action:
- key: new_hire_accepts
type: participant
participant: new_hire
action:
type: sign
envelope: offer_envelope
assignments:
- document: offer_letter
role: employee
onComplete:
validate: '$exists($envelope.signedAt)' # Step fails if expression is falsy
updates:
- key: employee # Blueprint input to update
transform: | # Output must conform to the employee schema
{
"acceptedAt": $now(),
"status": "offer_accepted"
}
- key: position # Update another entity in the same step
transform: |
{
"filledAt": $now(),
"status": "filled"
}
What validate and transform can see at runtime is covered in Step onComplete — Expression Context.
Why a Tree Structure?
- Structured and predictable — Enforces clean hierarchy, prevents spaghetti logic
- Encapsulation — A group cleanly bundles a sub-process
- Clear data flow — Entity evolution follows the tree, making it auditable and debuggable
Related Concepts
- Blueprints — Full blueprint format including orchestration
- Data-First Principle — How entity data evolves through steps
- Parallel Data Handling — Managing data in parallel branches
