The Two Primitives: Deep Dive
You learned that TASK and WAITFORINPUT are the two building blocks for 90% of workflows. This page explores them in depth—patterns, edge cases, and when they work best.
The Complete Picture
TASK: Execute Work Immediately
Definition: A state that executes code and returns when done.
- name: ValidateOrder
type: Task
resource: "urn:cascade:action:validate_order"
parameters:
order_id: "$.input.order_id"
resultPath: "$.state.validation_result"
next: CheckInventoryExecution Model:
TASK starts
↓
Call your activity (Go function)
↓
Activity executes (can take milliseconds to seconds)
↓
TASK receives result
↓
Result stored in state
↓
Next state beginsCharacteristics:
- ⚡ Fast (milliseconds to seconds)
- 🔄 Idempotent (safe to retry)
- 🎯 Single purpose (one activity per TASK)
- 📊 Produces output (state changes)
- 🛡️ Error handling (try/catch/retry)
What TASK Can Do: ✅ Call APIs (Stripe, Twilio, AWS) ✅ Query databases ✅ Evaluate policies (OPA, DMN) ✅ Run AI agents ✅ Send notifications ✅ Transform data ✅ Execute calculations ✅ Call webhooks
What TASK Cannot Do: ❌ Wait for humans ❌ Wait for external events ❌ Time-based delays (use Wait state) ❌ Have unknown duration
WAITFORINPUT: Pause and Resume
Definition: A state that pauses the workflow until an external signal arrives.
- name: ManagerApproval
type: Task
resource: "urn:cascade:waitFor:human"
input_schema: approval_form
resultPath: "$.state.approval"
next: ProcessOrderExecution Model:
WAITFORINPUT starts
↓
Workflow PAUSES (saved to database)
↓
Human/system provides input (minutes to months later)
↓
Workflow RESUMES from exact point
↓
Result stored in state
↓
Next state beginsCharacteristics:
- ⏸️ Pauseable (saves state to database)
- 🔔 External trigger (human, webhook, event)
- ⏱️ Unknown duration (could be months)
- 🎯 Single purpose (one input type per state)
- 🛡️ Resumable (survives deployments, crashes)
What WAITFORINPUT Can Do: ✅ Wait for human form submission ✅ Wait for webhook callback (payment, shipping) ✅ Wait for external event (system notification) ✅ Wait for manual override (admin action) ✅ Collect structured data via forms ✅ Route based on user decision
What WAITFORINPUT Cannot Do: ❌ Execute code immediately ❌ Call APIs directly (TASK does this) ❌ Return instantly ❌ Have deterministic duration
WAITFORINPUT: The Four Input Types
All WAITFORINPUT states use the same state type but differ in how the trigger arrives:
1️⃣ Human Input (Form Submission)
Scenario: Employee submits leave request form
- name: SubmitLeaveRequest
type: Task
resource: "urn:cascade:waitFor:human"
input_schema: leave_request_form
assignment:
assigned_to: "$.context.employee_id"
next: ManagerReviewFlow:
Workflow pauses
↓
System generates form from schema
↓
Employee fills and submits form
↓
Data saved to workflow state
↓
Next state beginsUse Cases:
- Leave requests
- Expense approvals
- Performance reviews
- Customer feedback
2️⃣ Webhook Callback (External System)
Scenario: Wait for payment provider to confirm charge
- name: WaitForPaymentConfirmation
type: Task
resource: "urn:cascade:waitFor:webhook"
webhook:
path: "/webhooks/stripe-charge-confirmed"
method: POST
next: ShipOrderFlow:
Workflow pauses
↓
System registers webhook listener
↓
External system calls webhook (Stripe, Shopify, etc.)
↓
Webhook payload saved to state
↓
Next state beginsUse Cases:
- Payment confirmations
- Shipping notifications
- Email delivery confirmations
- Third-party API callbacks
3️⃣ Event Arrival (Internal/External Events)
Scenario: Wait for order shipped event from logistics system
- name: WaitForShipment
type: Task
resource: "urn:cascade:waitFor:event"
events:
- name: "order.shipped"
- name: "order.failed"
next: RetryShipment
timeout: 7d
timeoutNext: CheckStatusManuallyFlow:
Workflow pauses
↓
System subscribes to NATS events
↓
Event published (shipment tracking updated)
↓
Event payload saved to state
↓
Next state beginsUse Cases:
- Event-driven workflows
- Real-time notifications
- System integrations
- Multi-service orchestration
4️⃣ Manual Override (Admin Action)
Scenario: Admin manually approves/rejects pending item
- name: WaitForAdminOverride
type: Task
resource: "urn:cascade:waitFor:manual"
form:
title: "Manual Review Required"
fields:
- decision: "string"
- notes: "string"
next: ProcessDecisionFlow:
Workflow pauses
↓
Admin sees pending item in queue
↓
Admin submits form (approve/reject + notes)
↓
Decision saved to state
↓
Next state beginsUse Cases:
- Escalations
- Manual interventions
- Edge case handling
- Exception management
Real-World Patterns: How They Combine
Pattern 1: Simple Automation (3 TASKs)
order_processing:
start: ValidateOrder
states:
- name: ValidateOrder
type: Task
resource: "urn:cascade:action:validate"
next: ChargePayment
- name: ChargePayment
type: Task
resource: "urn:cascade:action:stripe.charge"
retry: { max_attempts: 3, backoff_rate: 2.0 }
catch:
- error: PaymentFailed
next: NotifyCustomer
next: ShipOrder
- name: ShipOrder
type: Task
resource: "urn:cascade:action:ship"
end: trueCharacteristics:
- ⚡ Fast (completes in seconds)
- 🤖 Fully automated
- 📈 Predictable flow
- ✅ Best for: E-commerce, event processing
Pattern 2: Human-in-Loop (TASK → WAITFORINPUT → TASK)
leave_request:
start: ValidateRequest
states:
- name: ValidateRequest
type: Task
resource: "urn:cascade:action:validate"
next: ManagerApproval
- name: ManagerApproval
type: Task
resource: "urn:cascade:waitFor:human"
input_schema: approval_form
next: NotifyEmployee
- name: NotifyEmployee
type: Task
resource: "urn:cascade:action:send_email"
end: trueCharacteristics:
- ⏸️ Pauses for human decision
- 🔄 Resumes when input arrives
- 📅 Could take hours/days
- ✅ Best for: Approvals, reviews
Pattern 3: Multi-Path Decision (TASK → Choice → Multiple Paths)
fraud_detection:
start: AnalyzeRisk
states:
- name: AnalyzeRisk
type: Task
resource: "urn:cascade:agent:fraud-scorer"
next: RouteByRisk
- name: RouteByRisk
type: Choice
choices:
- condition: "$.state.risk_score > 0.9"
next: BlockTransaction
- condition: "$.state.risk_score > 0.7"
next: RequireVerification
default: AutoApprove
- name: BlockTransaction
type: Task
resource: "urn:cascade:action:block_and_notify"
end: true
- name: RequireVerification
type: Task
resource: "urn:cascade:waitFor:human"
input_schema: verification_form
next: ProcessAfterVerification
- name: AutoApprove
type: Task
resource: "urn:cascade:action:approve"
end: trueCharacteristics:
- 🧠 Intelligent routing
- 🔀 Multiple branches
- 🔄 Mix of TASK and WAITFORINPUT
- ✅ Best for: Risk assessment, prioritization
Pattern 4: Parallel Processing (Multiple TASKs Simultaneously)
notification_system:
start: PrepareNotification
states:
- name: PrepareNotification
type: Task
resource: "urn:cascade:action:prepare"
next: SendNotifications
- name: SendNotifications
type: Parallel
branches:
- start: SendEmail
states:
- name: SendEmail
type: Task
resource: "urn:cascade:action:send_email"
end: true
- start: SendSMS
states:
- name: SendSMS
type: Task
resource: "urn:cascade:action:send_sms"
end: true
- start: SendSlack
states:
- name: SendSlack
type: Task
resource: "urn:cascade:action:send_slack"
end: true
next: LogCompletion
- name: LogCompletion
type: Task
resource: "urn:cascade:action:log"
end: trueCharacteristics:
- 🚀 Execute multiple TASKs at once
- ⏱️ Wait for all to complete
- 📊 Aggregate results
- ✅ Best for: Notifications, batch operations
Edge Cases & Advanced Scenarios
Edge Case 1: Very Long WAITFORINPUT
Scenario: Waiting for physical delivery (could be weeks)
- name: WaitForDelivery
type: Task
resource: "urn:cascade:waitFor:event"
events:
- name: "delivery.completed"
timeout: 30d
timeoutNext: CheckDeliveryStatusConsiderations:
- ✅ Workflow survives deployments
- ✅ State persisted to database
- ✅ Configurable timeout
- ✅ Can escalate if timeout exceeded
Edge Case 2: TASK with Variable Duration
Scenario: Calling an external API that could take 30+ seconds
- name: GenerateReport
type: Task
resource: "urn:cascade:action:generate_report"
parameters:
dataset_size: "$.state.size"
timeout: 5m # Set timeout for long-running tasks
catch:
- error: TimeoutError
next: RequestAsyncConsiderations:
- ✅ Set timeout to prevent infinite waits
- ✅ Catch timeout errors
- ✅ Escalate to async if needed
- ✅ Use WAITFORINPUT for callback
Edge Case 3: Nested Decision Making
Scenario: Complex multi-tier approval
approval_chain:
start: EvaluateAmount
states:
- name: EvaluateAmount
type: Task
resource: "urn:cascade:policy:approval_tier"
next: RouteToApprover
- name: RouteToApprover
type: Choice
choices:
- condition: "$.state.tier == 'manager'"
next: ManagerApproval
- condition: "$.state.tier == 'director'"
next: DirectorApproval
- condition: "$.state.tier == 'executive'"
next: ExecutiveApproval
- name: ManagerApproval
type: Task
resource: "urn:cascade:waitFor:human"
assignment: { assigned_to: "$.state.manager_id" }
next: FinalApproval
- name: DirectorApproval
type: Task
resource: "urn:cascade:waitFor:human"
assignment: { assigned_to: "$.state.director_id" }
next: FinalApproval
- name: ExecutiveApproval
type: Task
resource: "urn:cascade:waitFor:human"
assignment: { assigned_to: "$.state.ceo_id" }
next: FinalApproval
- name: FinalApproval
type: Task
resource: "urn:cascade:action:process_approval"
end: trueConsiderations:
- ✅ Route based on data
- ✅ Assign to right person
- ✅ Converge back to single path
- ✅ Clear separation of concerns
Decision: When to Use Each
Use TASK When:
- ✅ Work is deterministic (takes known time)
- ✅ Calling an external API
- ✅ Processing data
- ✅ Making instant decisions
- ✅ Result available immediately
- Duration: Milliseconds to seconds
- Example: “Charge card” (happens immediately or fails)
Use WAITFORINPUT When:
- ✅ Need external signal
- ✅ Waiting for human decision
- ✅ Waiting for system callback
- ✅ Waiting for event
- ✅ Result has unknown arrival time
- Duration: Minutes to months
- Example: “Manager approval” (could take days)
Use Choice When:
- ✅ Routing based on instant decision
- ✅ Conditional branching
- ✅ Multiple paths based on data
- ✅ Performance-critical (
<1msneeded) - No wait: Evaluates instantly
- Example: “Route by amount” (
<1msdecision)
Use Parallel When:
- ✅ Multiple independent TASKs
- ✅ Wait for all to complete
- ✅ Aggregating results
- Performance: Faster than sequential
- Example: “Send 3 notification channels” (faster in parallel)
Performance Characteristics
| Pattern | Speed | State | Example |
|---|---|---|---|
| TASK | Fast (< 1s) | Changes immediately | API call |
| WAITFORINPUT | Slow (min to months) | Paused in DB | Human approval |
| Choice | Instant (< 1ms) | No change | Route by value |
| Parallel | Concurrent | All complete | Parallel notifications |
| Sequential TASK→TASK | Sequential sum | Changes each step | Validate → Charge → Ship |
Anti-Patterns: What NOT to Do
❌ Anti-Pattern 1: Using WAITFORINPUT for API Callback
Wrong:
- name: WaitForCallback
type: Task
resource: "urn:cascade:waitFor:human" # Wrong!Right:
- name: WaitForCallback
type: Task
resource: "urn:cascade:waitFor:webhook" # Correct!❌ Anti-Pattern 2: Putting TASK Where WAITFORINPUT Needed
Wrong:
- name: GetApproval
type: Task
resource: "urn:cascade:action:request_approval" # Doesn't wait!
next: ContinueProcess # Immediately goes here!Right:
- name: GetApproval
type: Task
resource: "urn:cascade:waitFor:human" # Pauses until submitted
next: ContinueProcess # Resumes after submission❌ Anti-Pattern 3: Too Many Sequential TASKs
Wrong (N+1 states):
states:
- name: Validate
type: Task
next: Charge
- name: Charge
type: Task
next: Ship
- name: Ship
type: Task
next: Notify
- name: Notify
type: Task
end: trueBetter (Parallel where possible):
states:
- name: Validate
type: Task
next: ProcessPaymentAndShip
- name: ProcessPaymentAndShip
type: Parallel
branches:
- [Charge]
- [Ship]
next: Notify
- name: Notify
type: Task
end: trueSummary: The Two Primitives Mastery
Master these concepts and you can build 90% of workflows:
| Concept | Duration | Trigger | Use For |
|---|---|---|---|
| TASK | Seconds | Automatic | Work execution |
| WAITFORINPUT | Minutes-months | External | Human/system input |
| Choice | Instant | Automatic | Routing |
| Parallel | Concurrent | Automatic | Parallel work |
Remember:
- TASK when you have work to do NOW
- WAITFORINPUT when you need to pause and wait for someone/something ELSE
- Combine them for complex workflows
Next Steps
Now that you master the two primitives:
- Application-as-Data - Why structured YAML enables this
- AI-Native Development - How LLMs use this
- Agent-First Platform - Agents orchestrating these patterns
- Comparisons - How we compare to competitors
You’re now a Two Primitives expert! 🎓 Time to see how AI leverages this. →