Skip to Content
Getting StartedYour First Workflow

Your First Real Workflow: Leave Request

Build a real-world Leave Request workflow with human tasks, business logic, and notifications.


What You’ll Build

A complete workflow that:

  1. Collects leave request details from the user
  2. Checks vacation balance
  3. Routes based on number of days
  4. Sends approval/denial emails
  5. Updates HR system

State types you’ll learn:

  • HumanTask - Wait for user input
  • Choice - Conditional logic
  • EvaluatePolicy - Business rules
  • Task - Execute activities
  • Parallel - Send multiple notifications
  • Wait - Time-based delays (bonus)
  • Receive - External events (bonus)

Complete Leave Request Workflow

Create leave-request-app.yaml:

apiVersion: cascade.io/v1 kind: Application metadata: name: hr-leave-request spec: workflows: - name: leave_request_flow description: "HR Leave Request Process" start: RequestDetails states: # ========================================== # 1. HUMANTASK STATE - Collect user input # ========================================== - name: RequestDetails type: HumanTask description: "Employee submits leave request" ui: schema: urn:cascade:schema:leave_request_form target: appsmith next: CheckVacationBalance # ========================================== # 2. CHOICE STATE - Conditional branching # ========================================== - name: CheckVacationBalance type: Choice description: "Check if employee has enough vacation days" choices: - variable: $.state.days_requested numericLessThanEquals: $.context.vacation_balance next: SelectApprovalPath - default: InsufficientBalance # ========================================== # 3. EVALUATE POLICY STATE - Business rules # ========================================== - name: SelectApprovalPath type: EvaluatePolicy description: "Determine approval workflow based on days" policy: urn:cascade:policy:leave_approval_routing engine: opa # Options: opa, dmn, wasm parameters: days_requested: $.state.days_requested department: $.context.department resultPath: $.policy_decision next: DecideRoute # ========================================== # 4. CHOICE STATE - Route based on policy # ========================================== - name: DecideRoute type: Choice choices: - variable: $.policy_decision.approval_type stringEquals: "auto_approve" next: AutoApprove - variable: $.policy_decision.approval_type stringEquals: "manager_approval" next: ManagerApprovalTask - default: ExecutiveApproval # ========================================== # Path A: Auto-approve short leaves # ========================================== - name: AutoApprove type: Task resource: urn:cascade:activity:approve_leave parameters: instance_id: $.id approval_type: "automatic" resultPath: $.approval_result next: SendApprovalNotifications # ========================================== # Path B: Manager approval task # ========================================== - name: ManagerApprovalTask type: HumanTask description: "Manager reviews and approves/denies" ui: schema: urn:cascade:schema:approval_form target: appsmith next: EvaluateManagerDecision - name: EvaluateManagerDecision type: Choice choices: - variable: $.state.manager_approved booleanEquals: true next: SendApprovalNotifications - default: Denied # ========================================== # Path C: Executive approval (for long leaves) # ========================================== - name: ExecutiveApproval type: HumanTask description: "Executive reviews extended leave" ui: schema: urn:cascade:schema:executive_approval_form target: appsmith next: EvaluateExecutiveDecision - name: EvaluateExecutiveDecision type: Choice choices: - variable: $.state.executive_approved booleanEquals: true next: SendApprovalNotifications - default: Denied # ========================================== # 5. PARALLEL STATE - Send notifications # ========================================== - name: SendApprovalNotifications type: Parallel description: "Notify employee and update systems" branches: - name: SendEmployeeEmail type: Task resource: urn:cascade:activity:send_email parameters: recipient: $.context.employee_email subject: "Your leave request has been approved" template: "leave_approved" - name: NotifyHR type: Task resource: urn:cascade:activity:notify_hr parameters: employee_id: $.context.employee_id leave_dates: $.state.leave_dates days: $.state.days_requested - name: UpdateCalendar type: Task resource: urn:cascade:activity:update_calendar parameters: employee_id: $.context.employee_id leave_dates: $.state.leave_dates next: Complete # ========================================== # Denial path # ========================================== - name: Denied type: Task resource: urn:cascade:activity:deny_leave parameters: instance_id: $.id reason: $.state.denial_reason next: SendDenialNotification - name: SendDenialNotification type: Task resource: urn:cascade:activity:send_email parameters: recipient: $.context.employee_email subject: "Your leave request has been denied" template: "leave_denied" next: Complete # ========================================== # Insufficient balance path # ========================================== - name: InsufficientBalance type: Task resource: urn:cascade:activity:insufficient_balance_email parameters: recipient: $.context.employee_email available_balance: $.context.vacation_balance requested_days: $.state.days_requested next: Complete # ========================================== # Final state # ========================================== - name: Complete type: Task end: true

Understanding Each Part

1. HumanTask State: RequestDetails

- name: RequestDetails type: HumanTask ui: schema: urn:cascade:schema:leave_request_form target: appsmith next: CheckVacationBalance

What it does:

  • Waits for user to interact with UI form
  • Collects: employee name, dates, reason
  • Stores input in $.state
  • Moves to CheckVacationBalance when user submits

Output example:

{ "state": { "employee_name": "John Doe", "leave_dates": ["2025-11-15", "2025-11-16"], "days_requested": 2, "reason": "Vacation" } }

2. Choice State: CheckVacationBalance

- name: CheckVacationBalance type: Choice choices: - variable: $.state.days_requested numericLessThanEquals: $.context.vacation_balance next: SelectApprovalPath - default: InsufficientBalance

What it does:

  • Compares days_requested (from form) with vacation_balance (from context)
  • If balance is sufficient → routes to SelectApprovalPath
  • Otherwise → routes to InsufficientBalance
  • Execution time: <0.1ms (in-process logic)

Available operators:

  • stringEquals, stringLessThan, stringContains
  • numericEquals, numericLessThan, numericGreaterThan
  • booleanEquals
  • Combinations: and, or, not

3. EvaluatePolicy State: SelectApprovalPath

- name: SelectApprovalPath type: EvaluatePolicy policy: urn:cascade:policy:leave_approval_routing engine: opa # or dmn, wasm parameters: days_requested: $.state.days_requested department: $.context.department resultPath: $.policy_decision next: DecideRoute

What it does:

  • Evaluates complex business rules (OPA Rego policy)
  • Passes employee info and request to policy engine
  • Gets decision back: auto_approve, manager_approval, or executive_approval
  • Typical execution time: <5ms (OPA) or 10-50ms (DMN)

Example OPA policy (urn:cascade:policy:leave_approval_routing):

package leave_approval default approval_type = "manager_approval" approval_type = "auto_approve" { input.days_requested <= 2 input.department == "Engineering" } approval_type = "executive_approval" { input.days_requested > 10 }

4. Choice State: DecideRoute

- name: DecideRoute type: Choice choices: - variable: $.policy_decision.approval_type stringEquals: "auto_approve" next: AutoApprove - variable: $.policy_decision.approval_type stringEquals: "manager_approval" next: ManagerApprovalTask - default: ExecutiveApproval

What it does:

  • Routes workflow to different approval paths based on policy decision
  • Shows how Choice and EvaluatePolicy work together

5. Parallel State: SendApprovalNotifications

- name: SendApprovalNotifications type: Parallel branches: - name: SendEmployeeEmail type: Task resource: urn:cascade:activity:send_email parameters: recipient: $.context.employee_email subject: "Your leave request has been approved" template: "leave_approved" - name: NotifyHR type: Task resource: urn:cascade:activity:notify_hr parameters: employee_id: $.context.employee_id leave_dates: $.state.leave_dates - name: UpdateCalendar type: Task resource: urn:cascade:activity:update_calendar parameters: employee_id: $.context.employee_id leave_dates: $.state.leave_dates next: Complete

What it does:

  • Executes 3 activities simultaneously (not sequentially)
  • All 3 complete before moving to next
  • Ideal for: sending notifications, updating multiple systems
  • Execution time: ~max(SendEmail, NotifyHR, UpdateCalendar), not sum

Deploy and Test

1. Deploy the application

cascade app apply --file leave-request-app.yaml

2. Start a workflow instance

cascade process start leave_request_flow \ --app=hr-leave-request \ --input='{"employee_id": "emp-123"}'

3. Monitor execution

cascade process inspect <instance-id> --app=hr-leave-request

4. Check logs

cascade logs --app=hr-leave-request --instance=<instance-id>

Next Steps

You now understand:

  • ✅ HumanTask (user input)
  • ✅ Choice (conditional routing)
  • ✅ EvaluatePolicy (business rules)
  • ✅ Task (execute activities)
  • ✅ Parallel (concurrent execution)

Learn More

  1. Core Concepts - Understand CDL, activities, policies
  2. Architecture Guide - How the system works
  3. Policy Authoring - Write OPA/DMN policies
  4. Workflow Development - Advanced patterns
  5. Examples - More real-world workflows

Ready to dive deeper? Continue with Core Concepts 🚀

Last updated on