Sleep and Delays
AI agents sometimes need to pause execution - waiting before retrying an operation, implementing rate limiting, or scheduling future actions. Workflow DevKit's sleep() function enables time-based delays without consuming compute resources.
Adding a Sleep Tool
Create a tool that allows the agent to pause for a specified duration:
import { tool } from 'ai';
import { getWritable, sleep } from 'workflow';
import { z } from 'zod';
import type { UIMessageChunk } from 'ai';
const inputSchema = z.object({
durationMs: z.number().describe('Duration to sleep in milliseconds'),
});
async function reportSleep(
{ durationMs }: { durationMs: number },
{ toolCallId }: { toolCallId: string }
) {
'use step';
const writable = getWritable<UIMessageChunk>();
const writer = writable.getWriter();
const seconds = Math.ceil(durationMs / 1000);
await writer.write({
id: toolCallId,
type: 'data-wait',
data: { text: `Sleeping for ${seconds} seconds` },
});
writer.releaseLock();
}
async function executeSleep(
{ durationMs }: z.infer<typeof inputSchema>,
{ toolCallId }: { toolCallId: string }
) {
// Note: No "use step" here - sleep is a workflow-level function
await reportSleep({ durationMs }, { toolCallId });
await sleep(durationMs);
return `Slept for ${durationMs}ms`;
}
export const sleepTool = tool({
description: 'Pause execution for a specified duration',
inputSchema,
execute: executeSleep,
});The sleep() function must be called from within a workflow context, not from within a step. This is why executeSleep does not have "use step" - it runs in the workflow context where sleep() is available.
How Sleep Works
When sleep() is called:
- The workflow records the wake-up time in the event log
- The workflow suspends, releasing all compute resources
- At the specified time, the workflow resumes execution
This differs from setTimeout or await new Promise(resolve => setTimeout(resolve, ms)):
- No compute resources are consumed during the sleep
- The workflow survives restarts, deploys, and infrastructure changes
- Sleep durations can span hours, days, or months
Duration Formats
The sleep() function accepts multiple duration formats:
// Milliseconds (number)
await sleep(5000);
// Duration strings
await sleep('30s'); // 30 seconds
await sleep('5m'); // 5 minutes
await sleep('2h'); // 2 hours
await sleep('1d'); // 1 day
await sleep('1 month'); // 1 month
// Date instance
await sleep(new Date('2025-12-31T23:59:59Z'));Emitting Progress Updates
When sleeping for long durations, emit a progress update so the UI can display the waiting state:
import { tool } from 'ai';
import { getWritable, sleep } from 'workflow';
import { z } from 'zod';
import type { UIMessageChunk } from 'ai';
async function emitWaitingStatus(message: string, toolCallId: string) {
'use step';
const writable = getWritable<UIMessageChunk>();
const writer = writable.getWriter();
await writer.write({
id: toolCallId,
type: 'data-wait',
data: { text: message },
});
writer.releaseLock();
}
async function executeScheduleTask(
{ delayMinutes, taskName }: { delayMinutes: number; taskName: string },
{ toolCallId }: { toolCallId: string }
) {
await emitWaitingStatus(
`Scheduled "${taskName}" to run in ${delayMinutes} minutes`,
toolCallId
);
await sleep(`${delayMinutes}m`);
return `Task "${taskName}" is now ready to execute`;
}
export const scheduleTask = tool({
description: 'Schedule a task to run after a delay',
inputSchema: z.object({
delayMinutes: z.number(),
taskName: z.string(),
}),
execute: executeScheduleTask,
});Use Cases
Rate Limiting
When hitting API rate limits, sleep before retrying:
async function callRateLimitedAPI(endpoint: string) {
'use step';
const response = await fetch(endpoint);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new RetryableError('Rate limited', {
retryAfter: retryAfter ? parseInt(retryAfter) * 1000 : '1m',
});
}
return response.json();
}Scheduled Notifications
Send a reminder after a delay:
async function executeReminder(
{ message, delayHours }: { message: string; delayHours: number }
) {
await sleep(`${delayHours}h`);
await sendNotification(message);
return `Reminder sent: ${message}`;
}Polling with Backoff
Poll for a result with increasing delays:
export async function pollForResult(jobId: string) {
'use workflow';
let attempt = 0;
const maxAttempts = 10;
while (attempt < maxAttempts) {
const result = await checkJobStatus(jobId);
if (result.status === 'complete') {
return result.data;
}
attempt++;
await sleep(Math.min(1000 * 2 ** attempt, 60000)); // Exponential backoff, max 1 minute
}
throw new Error('Job did not complete in time');
}
async function checkJobStatus(jobId: string) {
'use step';
// Check job status...
}Related Documentation
sleep()API Reference - Full API documentation- Workflows and Steps - Understanding workflow context
- Errors and Retries - Using
RetryableErrorwith delays