Approvals
Human approval gates in automated workflows
Approval Gates
Insert human approval checkpoints into automated workflows for decisions that require accountability, judgment, or regulatory compliance.
Basic Approval
import { human } from 'human-in-the-loop'
const approval = human.approval({
name: 'manager-approval',
description: 'Manager must approve this request',
assignTo: { role: 'manager' },
})
// Use in workflow
const result = await approval.request({
context: { request: purchaseRequest },
timeout: '48h',
})
if (result.approved) {
await processRequest(purchaseRequest)
} else {
await rejectRequest(purchaseRequest, result.reason)
}Approval Configuration
const purchaseApproval = human.approval({
name: 'purchase-approval',
description: 'Approve purchase requests over $1000',
// Who can approve
assignTo: {
role: 'finance-approver',
minLevel: 3, // Manager level 3+
},
// Alternative approvers
fallback: [
{ after: '24h', assignTo: 'department-head' },
{ after: '48h', assignTo: 'cfo' },
],
// Context to show
showContext: ['request', 'budget', 'policy', 'history'],
// Required fields
require: {
decision: ['approve', 'reject', 'defer'],
reason: { when: 'decision = reject' },
notes: false,
},
// Timeout behavior
timeout: '72h',
onTimeout: 'escalate', // 'escalate' | 'auto-approve' | 'auto-reject'
})Dynamic Assignment
const dynamicApproval = human.approval({
name: 'expense-approval',
// Assign based on request attributes
assignTo: ({ input }) => {
if (input.amount > 10000) {
return { role: 'cfo' }
}
if (input.amount > 1000) {
return { role: 'finance-manager' }
}
return input.requester.manager
},
// Or use approval matrix
approvalMatrix: {
conditions: [
{ amount: { gt: 10000 }, approver: 'cfo' },
{ amount: { gt: 1000 }, approver: 'finance-manager' },
{ category: 'travel', approver: 'travel-coordinator' },
{ default: 'direct-manager' },
],
},
})Multi-Level Approval
const multiLevelApproval = human.approval({
name: 'contract-approval',
levels: [
{
name: 'legal-review',
assignTo: { role: 'legal-counsel' },
actions: ['approve', 'request-changes', 'reject'],
},
{
name: 'finance-approval',
assignTo: { role: 'finance-director' },
actions: ['approve', 'reject'],
requiresPrevious: true,
},
{
name: 'executive-approval',
assignTo: { role: 'ceo' },
when: 'value > 100000',
actions: ['approve', 'reject'],
requiresPrevious: true,
},
],
// Approval mode
mode: 'sequential', // 'sequential' | 'parallel' | 'any'
})Parallel Approvals
const parallelApproval = human.approval({
name: 'cross-functional-approval',
approvers: [
{ role: 'engineering-lead', required: true },
{ role: 'product-manager', required: true },
{ role: 'legal', required: false },
],
mode: 'parallel',
// How to combine decisions
resolution: {
approve: 'all-required', // All required must approve
reject: 'any', // Any rejection rejects
},
})Conditional Approval
const conditionalApproval = human.approval({
name: 'conditional-purchase',
// Only require approval when
when: [
'amount > 500',
'vendor.new = true',
'category = software',
],
// Skip approval when
skip: [
'requester.level >= 5',
'preApproved = true',
],
assignTo: { role: 'manager' },
})Delegation
const delegatedApproval = human.approval({
name: 'delegatable-approval',
delegation: {
allowed: true,
maxDepth: 2, // Can delegate to someone who delegates
preserveContext: true,
notifyOriginal: true,
},
// Delegate automatically when
autoDelegation: {
when: ['approver.outOfOffice = true'],
to: 'approver.delegate',
},
})
// Approver can delegate
await approval.delegate({
to: 'backup-approver@company.com',
reason: 'Out of office this week',
expires: nextMonday,
})Approval with Modifications
const modifiableApproval = human.approval({
name: 'budget-approval',
actions: [
{
name: 'approve',
allowModifications: ['amount', 'timeline'],
},
{
name: 'approve-with-conditions',
requiresNotes: true,
addConditions: true,
},
{
name: 'reject',
requiresReason: true,
},
],
})
// Approver can modify
const result = await approval.request({ budget })
if (result.action === 'approve') {
// May have modifications
budget.amount = result.modifications?.amount ?? budget.amount
}Approval Workflow Integration
import { workflow } from 'ai-workflows'
const purchaseWorkflow = workflow({
name: 'purchase-request',
execute: async (ctx, request) => {
// Validate request
await step('validate', () => validateRequest(request))
// Check budget
const budget = await step('check-budget', () => checkBudget(request))
// Human approval gate
const approval = await step('approve', () =>
human.approval({
name: 'manager-approval',
assignTo: request.requester.manager,
showContext: { request, budget },
timeout: '48h',
}).request()
)
if (!approval.approved) {
await step('notify-rejection', () =>
notifyRequester(request, approval.reason)
)
return { status: 'rejected', reason: approval.reason }
}
// Process approved request
await step('process', () => processRequest(request))
return { status: 'approved', processedAt: new Date() }
},
})Approval UI
const approvalForm = human.approval({
name: 'visual-approval',
ui: {
layout: 'card',
sections: [
{
title: 'Request Summary',
fields: ['amount', 'category', 'description'],
highlight: ['amount'],
},
{
title: 'Policy Check',
type: 'checklist',
items: [
'Within budget guidelines',
'Approved vendor',
'Business justification provided',
],
},
{
title: 'Previous Approvals',
type: 'history',
source: 'context.approvalHistory',
},
],
actions: {
approve: { color: 'green', icon: 'check' },
reject: { color: 'red', icon: 'x' },
},
},
})Notifications
const approvalWithNotifications = human.approval({
name: 'notified-approval',
notifications: {
// When approval is requested
onRequest: {
channels: ['email', 'slack'],
template: 'approval-request',
},
// Reminders
remind: [
{ after: '24h', channels: ['slack'] },
{ after: '48h', channels: ['email', 'slack'] },
],
// When decision is made
onDecision: {
notify: ['requester', 'cc-list'],
channels: ['email'],
},
},
})Audit Trail
// All approvals are logged
const auditLog = await human.getApprovalAudit({
approval: 'purchase-approval',
period: 'last-30-days',
})
auditLog.forEach((entry) => {
console.log(entry.requestId)
console.log(entry.approver)
console.log(entry.decision)
console.log(entry.timestamp)
console.log(entry.reason)
})Best Practices
- Clear context - Show approvers everything they need
- Appropriate timeouts - Balance urgency with thoroughness
- Escalation paths - Don't let requests get stuck
- Audit everything - Maintain compliance trail
- Delegation support - Handle vacations and absences
- Mobile-friendly - Enable approvals on the go
Was this page helpful?