> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/vercel/workflow/llms.txt
> Use this file to discover all available pages before exploring further.

# Sleep and Delays

> Schedule recurring actions, handle rate limiting, and wait for external state in AI agents.

AI agents sometimes need to pause execution to schedule recurring or future actions, wait before retrying an operation (for rate limiting), or wait for external state to be available.

Workflow DevKit's `sleep` function enables agents to pause execution without consuming resources, and resume at a specified time, after a specified duration, or in response to an external event. Workflows that suspend survive restarts, new deploys, and infrastructure changes, whether the suspension takes seconds or months.

<Callout type="info">
  See the [`sleep()` API Reference](/api-reference/workflow/sleep) for the full list of supported duration formats and detailed API documentation.
</Callout>

## Adding a Sleep Tool

Sleep is a built-in function in Workflow DevKit, so exposing it as a tool is as simple as wrapping it in a tool definition:

<Steps>
  <Step>
    ### Define the Tool

    Add a sleep tool to your tool definitions:

    ```typescript title="workflows/chat/steps/tools.ts" lineNumbers theme={null}
    import { sleep } from "workflow";
    import { z } from "zod";

    async function executeSleep({ durationMs }: { durationMs: number }) {
      // Note: No "use step" here - sleep is a workflow-level function
      await sleep(durationMs);
      return { message: `Slept for ${durationMs}ms` };
    }

    export const tools = {
      // ... existing tool definitions ...
      sleep: {
        description: "Pause execution for a specified duration",
        inputSchema: z.object({
          durationMs: z.number().describe("Duration to sleep in milliseconds"),
        }),
        execute: executeSleep,
      },
    };
    ```

    <Callout type="info">
      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.
    </Callout>
  </Step>

  <Step>
    ### Show Tool Status in the UI

    Extend your chat UI to display the sleep tool call:

    ```typescript title="app/page.tsx" lineNumbers theme={null}
    export default function ChatPage() {
      const { messages } = useChat();

      return (
        <div>
          {messages.map((message) => (
            <div key={message.id}>
              {message.parts.map((part, partIndex) => {
                // ... other rendering logic ...

                if (part.type === "tool-sleep") {
                  return (
                    <div key={partIndex} className="border rounded-lg p-4">
                      <p className="text-sm font-medium">
                        {part.output
                          ? `Slept for ${part.input.durationMs}ms`
                          : `Sleeping for ${part.input.durationMs}ms...`}
                      </p>
                    </div>
                  );
                }

                return null;
              })}
            </div>
          ))}
        </div>
      );
    }
    ```
  </Step>
</Steps>

Now you can ask the agent to sleep, and the workflow will pause without consuming resources until the duration expires.

## Duration Formats

`sleep()` supports multiple duration formats:

```typescript lineNumbers theme={null}
// Milliseconds
await sleep(5000);

// Human-readable strings
await sleep("5s");   // 5 seconds
await sleep("2m");   // 2 minutes
await sleep("1h");   // 1 hour
await sleep("1d");   // 1 day
await sleep("1w");   // 1 week

// Date object (absolute time)
await sleep(new Date("2024-12-31T23:59:59Z"));

// ISO 8601 duration
await sleep("PT5M");  // 5 minutes
```

## Use Cases

### Rate Limiting

When hitting API rate limits, use `RetryableError` with a delay:

```typescript title="workflows/chat/steps/tools.ts" lineNumbers theme={null}
import { RetryableError } from "workflow";

async function callRateLimitedAPI({ endpoint }: { 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();
}
```

Learn more in [Errors and Retries](/concepts/errors-and-retries).

### Scheduled Tasks

Schedule future actions within your agent workflow:

```typescript lineNumbers theme={null}
import { sleep } from "workflow";

async function scheduleReminder({
  message,
  delayMs,
}: {
  message: string;
  delayMs: number;
}) {
  // No "use step" - this is workflow-level
  
  await sleep(delayMs);
  
  // Send the reminder
  await sendNotification(message);
  
  return { sent: true, message };
}

async function sendNotification(message: string) {
  "use step";
  
  await fetch("https://api.notifications.com/send", {
    method: "POST",
    body: JSON.stringify({ message }),
  });
}
```

### Polling External State

Poll an external service until a condition is met:

```typescript lineNumbers theme={null}
import { sleep } from "workflow";

async function waitForJobCompletion({ jobId }: { jobId: string }) {
  // No "use step" - orchestration logic
  
  let attempts = 0;
  const maxAttempts = 60; // 5 minutes total
  
  while (attempts < maxAttempts) {
    const status = await checkJobStatus(jobId);
    
    if (status === "completed") {
      return { status: "success", jobId };
    }
    
    if (status === "failed") {
      return { status: "failed", jobId };
    }
    
    // Wait 5 seconds before checking again
    await sleep("5s");
    attempts++;
  }
  
  throw new Error("Job timed out after 5 minutes");
}

async function checkJobStatus(jobId: string) {
  "use step";
  
  const response = await fetch(`https://api.jobs.com/${jobId}/status`);
  const data = await response.json();
  return data.status;
}
```

### Exponential Backoff

Implement custom retry logic with exponential backoff:

```typescript lineNumbers theme={null}
import { sleep } from "workflow";

async function retryWithBackoff({ url }: { url: string }) {
  const maxRetries = 5;
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      return await fetchData(url);
    } catch (error) {
      attempt++;
      
      if (attempt >= maxRetries) {
        throw error;
      }
      
      // Exponential backoff: 1s, 2s, 4s, 8s, 16s
      const delay = Math.pow(2, attempt) * 1000;
      await sleep(delay);
    }
  }
}

async function fetchData(url: string) {
  "use step";
  
  const response = await fetch(url);
  if (!response.ok) throw new Error("Fetch failed");
  return response.json();
}
```

### Daily Reports

Schedule recurring daily tasks:

```typescript lineNumbers theme={null}
import { sleep } from "workflow";

async function dailyReport() {
  "use workflow";
  
  while (true) {
    // Generate and send report
    const report = await generateReport();
    await sendReport(report);
    
    // Sleep until next day at 9 AM
    const now = new Date();
    const tomorrow9AM = new Date(now);
    tomorrow9AM.setDate(tomorrow9AM.getDate() + 1);
    tomorrow9AM.setHours(9, 0, 0, 0);
    
    await sleep(tomorrow9AM);
  }
}

async function generateReport() {
  "use step";
  
  const response = await fetch("https://api.analytics.com/daily-report");
  return response.json();
}

async function sendReport(report: any) {
  "use step";
  
  await fetch("https://api.email.com/send", {
    method: "POST",
    body: JSON.stringify({
      to: "team@example.com",
      subject: "Daily Report",
      body: JSON.stringify(report),
    }),
  });
}
```

## Combining Sleep with Other Patterns

### Sleep + Human-in-the-Loop

Implement timeouts for human approval:

```typescript lineNumbers theme={null}
import { sleep } from "workflow";
import { bookingApprovalHook } from "@/workflows/hooks/booking-approval";

async function executeBookingWithTimeout(
  { flightNumber, price }: { flightNumber: string; price: number },
  { toolCallId }: { toolCallId: string }
) {
  const hook = bookingApprovalHook.create({ token: toolCallId });
  
  // Race between approval and timeout
  const result = await Promise.race([
    hook.then((data) => ({ type: "approval" as const, data })),
    sleep("1h").then(() => ({ type: "timeout" as const })),
  ]);
  
  if (result.type === "timeout") {
    return "Booking request timed out after 1 hour. Auto-rejected.";
  }
  
  const { approved } = result.data;
  return approved ? "Booking approved" : "Booking rejected";
}
```

### Sleep + Streaming Updates

Stream progress updates during a long sleep:

```typescript lineNumbers theme={null}
import { sleep, getWritable } from "workflow";
import type { UIMessageChunk } from "ai";

async function executeLongTask(
  { taskId }: { taskId: string },
  { toolCallId }: { toolCallId: string }
) {
  const totalDuration = 60000; // 60 seconds
  const updateInterval = 10000; // 10 seconds
  const steps = totalDuration / updateInterval;
  
  for (let i = 0; i < steps; i++) {
    await writeProgress(toolCallId, i + 1, steps);
    await sleep(updateInterval);
  }
  
  return { completed: true, taskId };
}

async function writeProgress(
  toolCallId: string,
  current: number,
  total: number
) {
  "use step";
  
  const writable = getWritable<UIMessageChunk>();
  const writer = writable.getWriter();
  try {
    await writer.write({
      type: "data-progress",
      id: toolCallId,
      data: { current, total, percentage: (current / total) * 100 },
    });
  } finally {
    writer.releaseLock();
  }
}
```

## Best Practices

### Don't Sleep in Steps

Always call `sleep()` from workflow-level code, not steps:

```typescript lineNumbers theme={null}
// ❌ Don't do this
async function badExample() {
  "use step";
  await sleep(1000); // Error: sleep not available in steps
}

// ✅ Do this instead
async function goodExample() {
  // No "use step" - workflow-level
  await sleep(1000);
  await performStepAction();
}

async function performStepAction() {
  "use step";
  // Step logic here
}
```

### Combine with Steps for I/O

Use steps for I/O operations around sleep:

```typescript lineNumbers theme={null}
async function scheduledJob() {
  while (true) {
    // Step: I/O operation
    await performJob();
    
    // Workflow: sleep
    await sleep("1h");
  }
}

async function performJob() {
  "use step";
  // Automatically retried on failure
  await fetch("https://api.example.com/job");
}
```

### Handle Sleep Interruptions

Workflows can be cancelled during sleep:

```typescript lineNumbers theme={null}
import { sleep, getWorkflowMetadata } from "workflow";

async function interruptibleSleep({ durationMs }: { durationMs: number }) {
  try {
    await sleep(durationMs);
    return { completed: true };
  } catch (error) {
    // Workflow was cancelled during sleep
    return { completed: false, interrupted: true };
  }
}
```

## Related Documentation

* [`sleep()` API Reference](/api-reference/workflow/sleep) - Full API documentation
* [Workflows and Steps](/concepts/workflows-and-steps) - Understanding workflow context
* [Errors and Retries](/concepts/errors-and-retries) - Using `RetryableError` with delays
* [Human-in-the-Loop](/ai/human-in-the-loop) - Combining sleep with approval workflows


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.