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
- Workflow reaches a human node
- Human task is created and assigned
- Assignee receives notification
- Human reviews and responds
- 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
| Property | Type | Description |
|---|---|---|
prompt | string | Question/instruction for reviewer |
approvers | string[] | Email addresses who can respond |
inputFields | Field[] | Data to collect from human |
timeoutMs | number | How long to wait |
autoApproveOnTimeout | boolean | Auto-approve if timeout |
escalateTo | string[] | 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
| Status | Description |
|---|---|
pending | Waiting for human response |
approved | Human approved |
rejected | Human rejected |
expired | Timeout 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:
- Go to Tasks → Pending
- See all tasks awaiting your response
- Click a task to view details and context
- Fill in required fields
- 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.