6.2 KiB
6.2 KiB
Orquesta-Style Task Transition Model
Date: 2026-02-04
Overview
Refactored the workflow builder's task transition model from flat on_success/on_failure/on_complete/on_timeout fields to an Orquesta-style ordered next array of transitions. Each transition can specify a when condition, publish directives, and multiple do targets — enabling far more expressive workflow definitions.
Also added visual drag handles to task nodes in the workflow builder for creating transitions via drag-and-drop.
Motivation
The previous model only allowed a single target task per transition type and had no way to:
- Route to multiple tasks from a single transition
- Attach per-transition variable publishing
- Use custom condition expressions beyond the four fixed types
- Publish variables without transitioning to another task
The Orquesta model (from StackStorm) solves all of these with a simple, ordered list of conditional transitions.
Changes
Frontend (web/)
web/src/types/workflow.ts
- Added
TaskTransitiontype:{ when?: string; publish?: PublishDirective[]; do?: string[] } - Added
TransitionPresettype and constants (PRESET_WHEN,PRESET_LABELS) for the three common quick-access patterns: succeeded, failed, always - Added
classifyTransitionWhen()andtransitionLabel()for edge visualization - Added
EdgeType— simplified to"success" | "failure" | "complete" | "custom" - Added helper functions:
findOrCreateTransition(),addTransitionTarget(),removeTaskFromTransitions() - Removed
on_success,on_failure,on_complete,on_timeout,decision,publishfields fromWorkflowTask - Removed
DecisionBranchtype (subsumed byTaskTransition.when) - Updated
WorkflowYamlTaskto usenext?: WorkflowYamlTransition[] - Updated
builderStateToDefinition()to serializenextarray - Updated
definitionToBuilderState()to load both newnextformat and legacy flat fields (auto-converts) - Updated
deriveEdges()to iteratetask.next[].do[] - Updated
validateWorkflow()to validatenext[].do[]targets
web/src/components/workflows/TaskNode.tsx
- Redesigned output handles: Three color-coded drag handles at bottom (green=succeeded, red=failed, gray=always)
- Added input handle: Neutral circle at top center as drop target, highlights purple during active connection
- Removed old footer link-icon buttons
- Added transition summary in node body (e.g., "2 targets via 1 transition")
- Added custom transitions badge
web/src/components/workflows/WorkflowEdges.tsx
- Updated edge colors for new
EdgeTypevalues - Preview line color now uses
TransitionPresetmapping - Dynamic label width based on text content
web/src/components/workflows/WorkflowCanvas.tsx
- Updated to use
TransitionPresetinstead ofTransitionType - Added
onMouseUphandler for drag-to-cancel on canvas background
web/src/components/workflows/TaskInspector.tsx
- Replaced four fixed
TransitionFielddropdowns with a dynamic transition list editor - Each transition card shows:
whenexpression (editable),dotarget list (add/remove),publishkey-value pairs (add/remove) - Quick-set buttons for common
whenpresets (On Success, On Failure, Always) - Add transition buttons: "On Success", "On Failure", "Custom transition"
- Moved publish variables from task-level section to per-transition
- Removed old
TransitionFieldcomponent - Added Join section for barrier configuration
web/src/pages/actions/WorkflowBuilderPage.tsx
- Updated
handleSetConnectionto useaddTransitionTarget()withTransitionPreset - Updated
handleDeleteTaskto useremoveTaskFromTransitions()
Backend (crates/)
crates/common/src/workflow/parser.rs
- Added
TaskTransitionstruct:{ when, publish, do } - Added
Task::normalize_transitions()— converts legacy fields intonextarray - Added
Task::all_transition_targets()— collects all referenced task names - Updated
parse_workflow_yaml()to callnormalize_all_transitions()after parsing - Updated
validate_task()to useall_transition_targets()instead of checking individual fields - Legacy fields (
on_success,on_failure,on_complete,on_timeout,decision) retained for deserialization but cleared after normalization - Added 12 new tests covering both new and legacy formats
crates/common/src/workflow/validator.rs
- Updated
build_graph()andfind_entry_points()to usetask.all_transition_targets()
crates/common/src/workflow/mod.rs
- Exported new
TaskTransitiontype
crates/executor/src/workflow/graph.rs
- Replaced
TaskTransitionsstruct (flat fields) withVec<GraphTransition> - Added
GraphTransition:{ when, publish: Vec<PublishVar>, do_tasks: Vec<String> } - Added
PublishVar:{ name, expression }— preserves both key and value - Added
TransitionKindenum andGraphTransition::kind()classifier - Added
TaskGraph::matching_transitions()— returns full transition objects for coordinators - Added
TaskGraph::all_transition_targets()— all target names from a task - Updated
next_tasks()to evaluate transitions byTransitionKind - Updated
compute_inbound_edges()to iterateGraphTransition.do_tasks - Updated
extract_publish_vars()to returnVec<PublishVar>instead ofVec<String> - Added 12 new tests
crates/executor/src/workflow/task_executor.rs
- Updated variable publishing to extract from matching transitions instead of removed
task.publishfield
YAML Format
New (canonical)
tasks:
- name: task1
action: core.echo
next:
- when: "{{ succeeded() }}"
publish:
- result: "{{ result() }}"
do:
- task2
- log
- when: "{{ failed() }}"
do:
- error_handler
Legacy (still parsed, auto-converted)
tasks:
- name: task1
action: core.echo
on_success: task2
on_failure: error_handler
Test Results
- Parser tests: 37 passed (includes 12 new)
- Graph tests: 12 passed (includes 10 new)
- TypeScript: Zero errors
- Rust workspace: Zero warnings