Skip to main content

Human-in-the-Loop (HITL)

Human-in-the-Loop enables workflows to pause and wait for human decisions, approvals, or input before continuing.

Why Human-in-the-Loop?

Not everything should be fully automated:

  • High-stakes decisions: Approve large purchases, sensitive operations
  • Quality control: Review AI-generated content before publishing
  • Compliance: Ensure human oversight for regulated processes
  • Exception handling: Escalate edge cases humans handle better
  • Training: Improve AI by collecting human feedback

How HITL Works

Workflow → Human Task Created → Human Reviews → Response → Workflow Continues
  1. Workflow reaches a human node
  2. Human task is created and assigned
  3. Assignee receives notification
  4. Human reviews and responds
  5. Workflow continues with human input

Creating Human Tasks

In Workflows

Add a human node to your workflow:

{
id: 'approval-step',
type: 'human',
prompt: 'Please review and approve this expense report',
approvers: ['manager@company.com', 'finance@company.com'],
inputFields: [
{
name: 'approved',
type: 'boolean',
label: 'Approve this expense?',
required: true
},
{
name: 'comments',
type: 'text',
label: 'Comments (optional)',
required: false
}
],
timeoutMs: 86400000, // 24 hours
autoApproveOnTimeout: false
}

Human Node Configuration

PropertyTypeDescription
promptstringQuestion/instruction for reviewer
approversstring[]Email addresses who can respond
inputFieldsField[]Data to collect from human
timeoutMsnumberHow long to wait
autoApproveOnTimeoutbooleanAuto-approve if timeout
escalateTostring[]Escalate to these users on timeout

Input Field Types

inputFields: [
// Boolean (Yes/No)
{
name: 'approved',
type: 'boolean',
label: 'Approve?',
required: true
},

// Text input
{
name: 'reason',
type: 'text',
label: 'Reason',
required: false,
placeholder: 'Enter reason...'
},

// Number input
{
name: 'amount',
type: 'number',
label: 'Approved Amount',
required: true,
min: 0,
max: 10000
},

// Select dropdown
{
name: 'priority',
type: 'select',
label: 'Priority',
required: true,
options: [
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' }
]
},

// Multi-select
{
name: 'categories',
type: 'multiselect',
label: 'Categories',
required: false,
options: [
{ value: 'travel', label: 'Travel' },
{ value: 'equipment', label: 'Equipment' },
{ value: 'software', label: 'Software' }
]
},

// Date picker
{
name: 'deadline',
type: 'date',
label: 'Deadline',
required: true
}
]

Managing Human Tasks

List Pending Tasks

Get tasks waiting for your response:

const pendingTasks = await client.workflows.listPendingTasks();

for (const task of pendingTasks.data) {
console.log({
id: task.id,
workflowName: task.workflowName,
prompt: task.prompt,
createdAt: task.createdAt,
expiresAt: task.expiresAt
});
}

Get Task Details

const task = await client.workflows.getTask('task-123');

console.log({
id: task.id,
workflowRunId: task.workflowRunId,
nodeId: task.nodeId,
prompt: task.prompt,
inputFields: task.inputFields,
context: task.context, // Data from workflow
status: task.status,
expiresAt: task.expiresAt
});

Respond to Task

// Approve
await client.workflows.respondToTask('task-123', {
status: 'approved',
data: {
approved: true,
comments: 'Looks good, approved!'
}
});

// Reject
await client.workflows.respondToTask('task-123', {
status: 'rejected',
data: {
approved: false,
comments: 'Missing receipts for items 3 and 5'
}
});

Task Lifecycle

┌─────────┐    ┌──────────┐    ┌──────────┐
│ pending │───▶│ approved │───▶│ workflow │
└─────────┘ └──────────┘ │ continues│
│ └──────────┘
│ ┌──────────┐
├────────▶│ rejected │───▶ workflow handles
│ └──────────┘

│ ┌──────────┐
└────────▶│ expired │───▶ auto-approve or fail
└──────────┘

Status Values

StatusDescription
pendingWaiting for human response
approvedHuman approved
rejectedHuman rejected
expiredTimeout reached

Timeout Handling

Auto-Approve on Timeout

{
id: 'low-risk-approval',
type: 'human',
prompt: 'Approve standard purchase?',
timeoutMs: 86400000, // 24 hours
autoApproveOnTimeout: true // Will auto-approve after 24h
}

Escalation on Timeout

{
id: 'escalating-approval',
type: 'human',
prompt: 'Review high-priority request',
approvers: ['reviewer@company.com'],
timeoutMs: 14400000, // 4 hours
escalateTo: ['manager@company.com'], // Escalate after timeout
escalationTimeoutMs: 28800000 // Manager has 8 more hours
}

Fail on Timeout

{
id: 'strict-approval',
type: 'human',
prompt: 'Critical approval required',
timeoutMs: 3600000, // 1 hour
autoApproveOnTimeout: false, // Workflow will fail on timeout
failureMessage: 'Approval not received within time limit'
}

Context Passing

Pass workflow data to help humans make decisions:

{
id: 'informed-approval',
type: 'human',
prompt: 'Review customer refund request',
contextMapping: {
// These will be shown to the reviewer
customerName: '$.enrich.output.name',
customerTier: '$.enrich.output.tier',
orderTotal: '$.input.order.total',
refundAmount: '$.calculate.output.refundAmount',
refundReason: '$.input.reason',
orderHistory: '$.enrich.output.orderCount'
},
inputFields: [
{ name: 'approved', type: 'boolean', required: true },
{ name: 'adjustedAmount', type: 'number', required: false }
]
}

Reviewer sees:

Review customer refund request

Context:
- Customer Name: John Smith
- Customer Tier: Gold
- Order Total: $299.99
- Refund Amount: $299.99
- Refund Reason: Product defective
- Order History: 47 orders

[ ] Approve this refund?
[___] Adjusted Amount (optional)

[Approve] [Reject]

Dashboard Integration

Pending Tasks View

From the Dashboard:

  1. Go to TasksPending
  2. See all tasks awaiting your response
  3. Click a task to view details and context
  4. Fill in required fields
  5. Click Approve or Reject

Notifications

Configure how you receive task notifications:

  • Email notifications
  • Slack integration
  • In-app notifications
  • Webhook to external system

Multi-Level Approvals

Create approval chains with multiple human nodes:

{
nodes: [
{ id: 'start', type: 'start' },

// Level 1: Team lead
{
id: 'team-lead-approval',
type: 'human',
prompt: 'Team lead approval required',
approvers: ['lead@company.com'],
timeoutMs: 86400000
},

// Level 2: Manager (only for high value)
{
id: 'check-value',
type: 'condition',
conditions: [
{ expression: '$.input.amount > 5000', target: 'manager-approval' },
{ expression: 'true', target: 'process' }
]
},

{
id: 'manager-approval',
type: 'human',
prompt: 'Manager approval for high-value request',
approvers: ['manager@company.com'],
timeoutMs: 86400000
},

// Level 3: Finance (only for very high value)
{
id: 'check-finance',
type: 'condition',
conditions: [
{ expression: '$.input.amount > 25000', target: 'finance-approval' },
{ expression: 'true', target: 'process' }
]
},

{
id: 'finance-approval',
type: 'human',
prompt: 'Finance approval required',
approvers: ['finance@company.com', 'cfo@company.com'],
timeoutMs: 172800000 // 48 hours
},

{ id: 'process', type: 'agent', agentId: 'processor' },
{ id: 'end', type: 'end' }
]
}

Example: Content Review Workflow

const contentWorkflow = await client.workflows.create({
name: 'Content Publication',
description: 'AI generates content, human reviews before publishing',
nodes: [
{ id: 'start', type: 'start' },

// AI generates content
{
id: 'generate',
type: 'agent',
agentId: 'content-writer-agent',
inputMapping: {
topic: '$.input.topic',
style: '$.input.style',
length: '$.input.wordCount'
}
},

// Human reviews
{
id: 'review',
type: 'human',
prompt: 'Review AI-generated content before publishing',
approvers: ['editor@company.com', 'content-team@company.com'],
contextMapping: {
topic: '$.input.topic',
generatedContent: '$.generate.output.content',
wordCount: '$.generate.output.wordCount'
},
inputFields: [
{
name: 'approved',
type: 'boolean',
label: 'Approve for publication?',
required: true
},
{
name: 'edits',
type: 'text',
label: 'Suggested edits',
required: false
},
{
name: 'rating',
type: 'select',
label: 'Content quality',
required: true,
options: [
{ value: '5', label: 'Excellent' },
{ value: '4', label: 'Good' },
{ value: '3', label: 'Average' },
{ value: '2', label: 'Below Average' },
{ value: '1', label: 'Poor' }
]
}
],
timeoutMs: 86400000
},

// Branch based on approval
{
id: 'check-approved',
type: 'condition',
conditions: [
{ expression: '$.review.data.approved === true', target: 'publish' },
{ expression: 'true', target: 'revise' }
]
},

// Publish approved content
{
id: 'publish',
type: 'agent',
agentId: 'publishing-agent'
},

// Revise rejected content
{
id: 'revise',
type: 'agent',
agentId: 'content-writer-agent',
inputMapping: {
originalContent: '$.generate.output.content',
feedback: '$.review.data.edits',
instruction: 'Revise based on feedback'
}
},

{ id: 'end-published', type: 'end' },
{ id: 'end-revised', type: 'end' }
],
edges: [
{ id: 'e1', source: 'start', target: 'generate' },
{ id: 'e2', source: 'generate', target: 'review' },
{ id: 'e3', source: 'review', target: 'check-approved' },
{ id: 'e4', source: 'publish', target: 'end-published' },
{ id: 'e5', source: 'revise', target: 'review' } // Loop back for re-review
]
});

Best Practices

1. Clear Prompts

// ✅ Good - specific and actionable
prompt: 'Approve expense report #1234 for $2,500 (Travel - Q4 Conference)'

// ❌ Bad - vague
prompt: 'Please review'

2. Provide Context

// ✅ Good - include relevant data
contextMapping: {
amount: '$.input.amount',
requester: '$.input.requesterName',
department: '$.input.department',
justification: '$.input.justification'
}

3. Reasonable Timeouts

// Low-risk, routine approvals
timeoutMs: 86400000 // 24 hours

// High-stakes decisions
timeoutMs: 604800000 // 7 days

// Urgent matters
timeoutMs: 3600000 // 1 hour

4. Use Escalation

// Don't let tasks sit indefinitely
escalateTo: ['backup-approver@company.com']

5. Collect Useful Feedback

// Helps improve AI over time
inputFields: [
{ name: 'approved', type: 'boolean' },
{ name: 'quality', type: 'select', options: [...] },
{ name: 'feedback', type: 'text' }
]

Next: Learn about Agent-to-Agent Communication for building collaborative agent systems.