14 min read • Updated February 26, 2026
Most AI agent failures aren't model problems — they're orchestration problems. You've got a smart model making dumb decisions because the workflow logic is brittle, poorly structured, or missing critical error handling. This guide covers the patterns that turn fragile demos into reliable production systems.
Workflow patterns are reusable templates for orchestrating multi-step agent tasks. They define:
Each step depends on the output of the previous step. Linear flow with no branching.
Extract customer email → Validate format → Check if existing → Create account → Send welcome email
async function sequentialChain(input) {
const steps = [
{ name: 'extract', handler: extractEmail },
{ name: 'validate', handler: validateEmail },
{ name: 'check_existing', handler: checkIfExisting },
{ name: 'create', handler: createAccount },
{ name: 'notify', handler: sendWelcome }
];
let data = input;
const results = {};
for (const step of steps) {
console.log(`Running: ${step.name}`);
data = await step.handler(data);
results[step.name] = data;
if (!data.success) {
return { success: false, failedAt: step.name, error: data.error };
}
}
return { success: true, results };
}
{success, data, error} patternMultiple independent tasks can run simultaneously. No dependencies between parallel branches.
Fetch customer data from CRM + Fetch order history from ERP + Fetch support tickets from Zendesk → Combine → Generate report
async function parallelExecution(customerId) {
const tasks = {
crm: fetchFromCRM(customerId),
orders: fetchFromERP(customerId),
tickets: fetchFromZendesk(customerId)
};
// Run all in parallel
const results = {};
const errors = {};
await Promise.allSettled(
Object.entries(tasks).map(async ([key, promise]) => {
try {
results[key] = await promise;
} catch (error) {
errors[key] = error.message;
}
})
);
// Check if minimum required data exists
if (!results.crm) {
return { success: false, error: 'CRM data required', errors };
}
// Combine with fallbacks for optional data
return {
success: true,
data: {
customer: results.crm,
orders: results.orders || [],
tickets: results.tickets || []
},
partialFailure: Object.keys(errors).length > 0,
errors
};
}
| Strategy | When to Use | Behavior |
|---|---|---|
| Fail Fast | All tasks required | Stop on first error |
| Best Effort | Optional enrichment | Continue with partial results |
| Quorum | Redundant sources | Succeed if N of M tasks complete |
| Fallback Chain | Primary + backup sources | Try backup if primary fails |
Workflow branches based on conditions. Multiple paths, clear decision points.
Customer inquiry → Classify intent → If "refund" check policy → If within 30 days auto-approve else escalate → If "technical" check knowledge base → If found provide answer else create ticket
async function decisionTree(input) {
// Step 1: Classify intent
const intent = await classifyIntent(input.message);
// Step 2: Route based on intent
switch (intent.primary) {
case 'refund':
return await handleRefund(input, intent);
case 'technical':
return await handleTechnical(input, intent);
case 'billing':
return await handleBilling(input, intent);
case 'complaint':
return await handleComplaint(input, intent);
default:
return await handleGeneral(input, intent);
}
}
async function handleRefund(input, intent) {
const order = await getOrder(input.orderId);
// Decision: Within 30 days?
const daysSincePurchase = getDaysSince(order.purchaseDate);
if (daysSincePurchase <= 30) {
// Decision: High-value order?
if (order.total > 500) {
return { action: 'escalate', reason: 'High-value auto-refund' };
}
return { action: 'auto_approve', refundAmount: order.total };
}
// Decision: Has prior refunds?
const priorRefunds = await getPriorRefunds(input.customerId);
if (priorRefunds.count > 2) {
return { action: 'escalate', reason: 'Multiple prior refunds' };
}
return { action: 'manual_review', reason: 'Outside 30-day window' };
}
Long-running processes with complex state transitions. May pause and resume. Multiple possible outcomes.
Loan application: Submitted → Under Review → Additional Info Needed → (loop) → Approved/Rejected → Funded
const STATES = {
SUBMITTED: {
onEnter: 'notifyUnderwriter',
transitions: {
review_complete: 'UNDER_REVIEW',
missing_info: 'ADDITIONAL_INFO_NEEDED'
}
},
ADDITIONAL_INFO_NEEDED: {
onEnter: 'requestInfoFromApplicant',
transitions: {
info_provided: 'UNDER_REVIEW',
timeout: 'REJECTED',
withdrawn: 'WITHDRAWN'
}
},
UNDER_REVIEW: {
onEnter: 'performReview',
transitions: {
approved: 'APPROVED',
rejected: 'REJECTED',
need_more: 'ADDITIONAL_INFO_NEEDED'
}
},
APPROVED: {
onEnter: 'initiateFunding',
transitions: {
funded: 'FUNDED',
funding_failed: 'FUNDING_ERROR'
}
},
REJECTED: 'terminal',
WITHDRAWN: 'terminal',
FUNDED: 'terminal',
FUNDING_ERROR: {
onEnter: 'alertOperations',
transitions: {
retry: 'APPROVED'
}
}
};
class WorkflowStateMachine {
constructor(workflowId) {
this.id = workflowId;
this.state = 'SUBMITTED';
this.history = [];
}
async transition(event, data) {
const currentState = STATES[this.state];
if (currentState === 'terminal') {
throw new Error(`Cannot transition from terminal state: ${this.state}`);
}
const nextState = currentState.transitions[event];
if (!nextState) {
throw new Error(`Invalid transition: ${this.state} → ${event}`);
}
// Record transition
this.history.push({
from: this.state,
to: nextState,
event,
timestamp: new Date(),
data
});
// Update state
this.state = nextState;
// Execute onEnter action
const stateConfig = STATES[nextState];
if (typeof stateConfig === 'object' && stateConfig.onEnter) {
await this.executeAction(stateConfig.onEnter, data);
}
// Persist state
await this.save();
return this.state;
}
}
Same operation applied to many items, then results combined. Batch processing at scale.
Process 100 customer emails: For each email extract intent → For each intent generate response → Combine into daily summary report
async function mapReduce(items, mapFn, reduceFn, options = {}) {
const { concurrency = 5, failFast = false } = options;
// Map phase: Process each item
const results = [];
const errors = [];
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency);
const batchResults = await Promise.allSettled(
batch.map(async (item, idx) => {
const result = await mapFn(item, i + idx);
return { item, result };
})
);
for (const settled of batchResults) {
if (settled.status === 'fulfilled') {
results.push(settled.value);
} else {
errors.push({ error: settled.reason.message });
if (failFast) {
return { success: false, errors, partialResults: results };
}
}
}
}
// Reduce phase: Combine results
const combined = await reduceFn(results);
return {
success: true,
data: combined,
processed: results.length,
failed: errors.length,
errors: errors.length > 0 ? errors : undefined
};
}
// Example usage
const emailAnalysis = await mapReduce(
emails,
async (email) => ({
intent: await classifyIntent(email.body),
sentiment: await analyzeSentiment(email.body),
urgency: await assessUrgency(email.body)
}),
async (results) => ({
intents: results.groupBy(r => r.result.intent),
avgSentiment: results.avg(r => r.result.sentiment),
urgentCount: results.filter(r => r.result.urgency === 'high').length
}),
{ concurrency: 10 }
);
Critical decisions require human judgment. High-stakes or ambiguous scenarios.
Generate response → If confidence < 80% queue for review → Human approves/edits → Deliver to customer
| Pattern | Trigger | Human Action |
|---|---|---|
| Review Queue | Low confidence score | Approve/Edit/Reject |
| Approval Gate | High-value/impact action | Approve/Reject with reason |
| Active Learning | Model uncertainty | Provide correct label |
| Escalation | Unable to handle | Take over conversation |
async function humanInTheLoop(action, context) {
// Step 1: Generate action
const proposal = await generateAction(action, context);
// Step 2: Check confidence
if (proposal.confidence >= 0.85) {
// High confidence: Auto-execute
return await executeAction(proposal);
}
// Step 3: Queue for human review
const reviewItem = await createReviewItem({
proposal,
context,
reason: proposal.confidence < 0.85 ? 'low_confidence' : 'flagged',
createdAt: new Date()
});
// Step 4: Wait for human response (with timeout)
const review = await waitForReview(reviewItem.id, {
timeout: 3600000, // 1 hour
onTimeout: 'escalate'
});
// Step 5: Handle review outcome
switch (review.decision) {
case 'approved':
return await executeAction(proposal);
case 'edited':
return await executeAction(review.editedProposal);
case 'rejected':
return { success: false, reason: review.reason };
case 'timeout':
return await escalateToOps(context);
}
}
async function retryWithBackoff(fn, options = {}) {
const { maxAttempts = 3, baseDelay = 1000, maxDelay = 30000 } = options;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) throw error;
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
await sleep(delay);
}
}
}
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 60000;
this.failures = 0;
this.state = 'CLOSED';
}
async execute(...args) {
if (this.state === 'OPEN') {
throw new Error('Circuit breaker is OPEN');
}
try {
const result = await this.fn(...args);
this.failures = 0;
return result;
} catch (error) {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
setTimeout(() => {
this.state = 'HALF_OPEN';
}, this.resetTimeout);
}
throw error;
}
}
}
async function fallbackChain(tasks, options = {}) {
const { stopOnSuccess = true } = options;
for (const [name, task] of Object.entries(tasks)) {
try {
const result = await task();
if (stopOnSuccess) return { source: name, result };
} catch (error) {
console.log(`${name} failed: ${error.message}`);
continue;
}
}
throw new Error('All fallback tasks failed');
}
| Pattern | Latency (p50) | Latency (p95) | Cost per 1K runs |
|---|---|---|---|
| Sequential Chain (5 steps) | 2.5s | 4.2s | $0.12 |
| Parallel (3 concurrent) | 1.8s | 3.1s | $0.15 |
| Decision Tree (avg 3 branches) | 1.2s | 2.8s | $0.08 |
| Map-Reduce (100 items) | 8.5s | 15.2s | $0.95 |
| Scenario | Recommended Pattern | Why |
|---|---|---|
| Simple data transformation | Sequential Chain | Clear dependencies, linear flow |
| Enrich data from multiple sources | Parallel Execution | Independent fetches, combine at end |
| Customer support routing | Decision Tree | Multiple branches, clear decision points |
| Loan application process | State Machine | Long-running, resumable, auditable |
| Batch email processing | Map-Reduce | Same operation on many items |
| High-stakes decisions | Human-in-the-Loop | Requires human judgment |
Real workflows often combine multiple patterns:
Clawsistant provides workflow design and implementation services for AI agent systems:
Setup packages: $99 (basic) • $299 (standard) • $499 (enterprise)
AI agent workflow patterns are reusable templates for orchestrating multi-step agent tasks. They define how agents sequence actions, handle decisions, manage state, and recover from errors. Common patterns include sequential chains, parallel execution, decision trees, state machines, and map-reduce. These patterns reduce development time by 60-80% and improve reliability.
Use sequential execution when steps depend on previous results (e.g., extract data → validate → transform → save). Use parallel execution when tasks are independent (e.g., fetch data from 5 APIs simultaneously). Sequential is safer but slower; parallel is faster but requires careful error handling. Hybrid approaches (parallel fetch + sequential process) often work best.
Implement 5 failure handling strategies: 1) Retry with exponential backoff (for transient errors), 2) Fallback to simpler approach (for complexity issues), 3) Checkpoint and resume (for long-running tasks), 4) Circuit breaker (for repeated failures), 5) Graceful degradation (continue with partial results). Always log failures with context for debugging.
The state machine pattern models agent workflows as a set of states and transitions. Each state represents a stage (e.g., 'collecting_info', 'processing', 'awaiting_approval'), and transitions define valid paths between states. This pattern is ideal for complex, long-running processes like customer onboarding, loan applications, or multi-party negotiations where agents must track progress and handle various outcomes.
Optimize with 5 techniques: 1) Batch similar operations (reduce API calls), 2) Cache frequently accessed data, 3) Use cheaper models for simple steps, 4) Parallelize independent tasks, 5) Implement early termination (stop if quality checks fail). Measure baseline performance first, then apply optimizations incrementally with A/B testing.
Last updated: February 26, 2026 • Contact us for AI agent workflow consulting