> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runflow.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Schedule

> Create and manage scheduled executions programmatically or let agents schedule themselves

**Schedule** lets you create timed executions for your agents — from code or from the agent itself during a conversation. When a user says *"remind me tomorrow at 9am"*, the agent can create that schedule automatically using built-in tools.

## Quick Start: Agent with Schedule

Give your agent the ability to create schedules:

```typescript theme={null}
import { Agent, openai, createScheduleTools } from '@runflow-ai/sdk';

const scheduleTools = createScheduleTools();

const agent = new Agent({
  name: 'Assistant',
  instructions: 'You are a helpful assistant. You can schedule reminders.',
  model: openai('gpt-4o'),
  tools: {
    create_schedule: scheduleTools.create_schedule,
  },
});
```

Now the agent handles conversations like:

> **User:** "Me chama amanha as 9h pra eu enviar o relatorio"
>
> **Agent:** *uses `create_schedule` tool* — Schedule "Lembrete relatorio" created. I'll remind you tomorrow at 09:00.

## What `createScheduleTools()` Returns

This function returns an object with **4 independent tools**. You choose which ones to give your agent — **don't spread all tools blindly**.

### Choosing the Right Tools

`createScheduleTools()` returns:

| Tool Key          | What It Does                      |
| ----------------- | --------------------------------- |
| `create_schedule` | Creates a new schedule            |
| `list_schedules`  | Lists all schedules for the agent |
| `update_schedule` | Updates an existing schedule      |
| `cancel_schedule` | Cancels (deletes) a schedule      |

Pick only the tools your agent actually needs:

```typescript theme={null}
const scheduleTools = createScheduleTools();

// Most common: agent that creates reminders
tools: {
  create_schedule: scheduleTools.create_schedule,
}

// Agent that creates and can cancel its own reminders
tools: {
  create_schedule: scheduleTools.create_schedule,
  cancel_schedule: scheduleTools.cancel_schedule,
}

// Internal admin agent with full control
tools: {
  ...createScheduleTools(),  // All 4 tools
}
```

<Warning>
  **Security: schedules are scoped per agent, not per user.** The `list_schedules` tool returns **all active schedules for the agent** — including schedules created by other users. If user A creates a reminder and user B asks "what are my reminders?", the agent will show user A's schedules too.

  Only give `list_schedules`, `update_schedule`, and `cancel_schedule` to agents where this is acceptable (e.g., internal admin bots, single-user agents, or agents where all users share the same context). For multi-user agents, prefer giving only `create_schedule`.
</Warning>

### Recommended Patterns

| Scenario                              | Tools to Include                                         |
| ------------------------------------- | -------------------------------------------------------- |
| Multi-user agent (customers, support) | `create_schedule` only                                   |
| Single-user personal assistant        | `create_schedule` + `cancel_schedule`                    |
| Internal team bot (shared context)    | `create_schedule` + `list_schedules` + `cancel_schedule` |
| Admin/monitoring agent                | All 4 (`...createScheduleTools()`)                       |

### Tool Details

### `create_schedule`

Creates a new scheduled execution. The LLM fills in the parameters based on the user's request.

**Input schema:**

| Parameter       | Type                              | Required            | Description                                      |
| --------------- | --------------------------------- | ------------------- | ------------------------------------------------ |
| `name`          | `string`                          | Yes                 | Descriptive name (e.g., "Daily report reminder") |
| `type`          | `'interval' \| 'daily' \| 'cron'` | Yes                 | Schedule type                                    |
| `interval`      | `number`                          | For `interval` type | Minutes between each execution (1-43200)         |
| `time`          | `string`                          | For `daily` type    | Time in HH:MM format, 24-hour (e.g., "09:00")    |
| `cron`          | `string`                          | For `cron` type     | Cron expression (e.g., "0 9 \* \* 1-5")          |
| `timezone`      | `string`                          | No                  | IANA timezone (default: "UTC")                   |
| `message`       | `string`                          | Yes                 | The message sent to the agent on each execution  |
| `maxExecutions` | `number`                          | No                  | Max times to fire (omit = unlimited)             |

**Returns:** `"Schedule created successfully. ID: {id}, Name: {name}, Type: {type}, Status: {status}"`

### `list_schedules`

Lists all active schedules for the current agent. Takes no parameters.

**Returns:** A formatted list of all schedules with ID, name, type, status, and message — or `"No schedules found."`

### `update_schedule`

Updates an existing schedule. Only the fields you provide are changed.

**Input:** `id` (required) + any field from `create_schedule` (optional).

**Returns:** `"Schedule updated successfully. ID: {id}, Name: {name}, Status: {status}"`

### `cancel_schedule`

Permanently deletes a schedule.

**Input:** `id` (required).

**Returns:** `"Schedule {id} has been cancelled and deleted successfully."`

<Note>
  The LLM decides which tool to call based on the conversation. You don't need to write any routing logic — the tool descriptions guide the model. For example, when a user says "change my reminder to 10am", the LLM will call `list_schedules` first to find the ID, then `update_schedule` with the new time.
</Note>

## How It Works End-to-End

Understanding the full lifecycle is important. Here's what happens from creation to execution:

```
1. User says "Remind me daily at 9am to check tickets"
         |
2. Agent LLM decides to call create_schedule tool
         |
3. SDK sends POST /runtime/v1/schedules to Runflow API
         |
4. API creates a SCHEDULER trigger in the trigger engine
         |
5. Trigger engine checks every minute for due triggers
         |
6. At 9:00 AM, trigger fires → HTTP POST to agent endpoint
         |
7. Agent receives the message and processes it
         |
8. Agent generates a response (sent via configured channel)
```

### Step-by-step breakdown

**Creation (steps 1-4):** When the agent calls `create_schedule`, the SDK sends the configuration to the Runflow API. The API creates a `SCHEDULER` trigger in the database with the cron expression, next run time, and the message payload.

**Scheduling (step 5):** The Runflow trigger engine runs a periodic job (every minute) that queries the database for triggers where `nextRun <= now`. This is powered by the trigger-engine service.

**Execution (steps 6-7):** When a trigger is due, the trigger engine sends an HTTP POST to the agent's endpoint with a payload containing the schedule data. The agent processes the `message` field as if it were a new conversation input.

**Response (step 8):** The agent's response is delivered through the configured channel (API response, webhook, WhatsApp, etc.) depending on how the agent is deployed.

## What the Agent Receives When a Schedule Fires

When a scheduled trigger fires, your agent receives an `AgentInput` with the schedule's `message` as the input, plus metadata about the trigger:

```typescript theme={null}
// This is what arrives to your agent's process() method
{
  message: "Check for new support tickets and summarize",  // The 'message' from schedule.create()
  metadata: {
    triggerId: "trig_abc123",           // Trigger ID
    triggerName: "Check Tickets",       // Schedule name
    executedAt: "2026-03-20T09:00:00Z", // When it fired
  }
}
```

This means:

* **The `message` field** is what drives the agent's behavior. Write it as if you were sending a chat message to the agent. The agent processes it with all its tools, RAG, and memory — just like a regular conversation.
* **The `metadata`** lets you know this was a scheduled execution (not a user message), which is useful for logging or conditional logic in tools.

### Example: Agent Reacting to a Scheduled Message

```typescript theme={null}
const agent = new Agent({
  name: 'Report Bot',
  instructions: `You generate daily reports.
    When you receive a scheduled message, execute the task described in it.
    Use the generate-report tool to create the report.`,
  model: openai('gpt-4o'),
  tools: {
    ...createScheduleTools(),
    generateReport: reportTool,
  },
});

// User creates the schedule during conversation
await agent.process({
  message: 'Schedule a daily sales report at 8am',
  sessionId: 'user_123',
});
// Agent calls create_schedule with message: "Generate the daily sales report and send a summary"

// Next day at 8:00 AM, the agent receives:
// { message: "Generate the daily sales report and send a summary" }
// Agent calls generateReport tool → produces the report
```

## Conversation Context (Automatic)

When an agent creates a schedule during a conversation where `identify()` is active, the SDK **automatically captures** the conversation context (entityType, entityValue, sessionId) and stores it in the trigger. When the schedule fires, this context is injected back into the agent input — so memory loads and the agent resumes the conversation.

```typescript main.ts theme={null}
import { identify } from '@runflow-ai/sdk';
import { agent } from './agent';

export async function main(input: any) {
  // Identify the user — auto-detects phone, email, etc.
  const phone = input.entityValue || input.metadata?.phone;
  if (phone) {
    identify(phone);
  }

  // Same flow for regular messages, scheduled callbacks, and follow-ups
  return agent.process(input);
}
```

```
Lead: "Me liga amanha meio dia"
  → Agent calls create_schedule (maxExecutions: 1)
  → SDK captures current identify() state automatically:
    { entityType: 'phone', entityValue: '+5511999999999' }
  → Stored in trigger metadata

Tomorrow at 12:00:
  → Trigger fires with entityType/entityValue in the input
  → main() calls identify('+5511999999999')
  → agent.process() loads memory for phone:+5511999999999
  → Agent has full conversation history, resumes naturally
  → Schedule auto-deactivates (maxExecutions: 1 reached)
```

No extra code from the developer. The `create_schedule` tool captures and the trigger engine restores — the agent doesn't know the difference between a live message and a scheduled callback.

<Warning>
  **One-time vs recurring:** When the LLM creates a schedule from a request like "call me tomorrow", it must set `maxExecutions: 1` — otherwise the schedule fires every day forever. The tool description guides the LLM to do this, but you should also reinforce it in your agent's instructions:

  ```
  instructions: `...
  When creating a one-time reminder, ALWAYS set maxExecutions to 1.
  Only omit maxExecutions for recurring tasks the user explicitly asked to repeat.`
  ```
</Warning>

<Tip>
  See the [SDR Agent with Scheduled Follow-ups](/use-cases/sdr-follow-up) use case for a complete working example with lead qualification, scheduled callbacks, and inactive lead follow-ups.
</Tip>

## Programmatic API

Use the `schedule` object directly in your code — useful for creating schedules in workflows, during deployment, or from external triggers:

### Create a Schedule

```typescript theme={null}
import { schedule } from '@runflow-ai/sdk';

// Daily at 9am
await schedule.create({
  name: 'Daily Report',
  type: 'daily',
  time: '09:00',
  timezone: 'America/Sao_Paulo',
  message: 'Generate the daily sales report',
});

// Every 2 hours
await schedule.create({
  name: 'Check Tickets',
  type: 'interval',
  interval: 120,  // minutes
  message: 'Check for new support tickets and summarize',
});

// Cron expression
await schedule.create({
  name: 'Weekly Summary',
  type: 'cron',
  cron: '0 9 * * MON',
  timezone: 'America/Sao_Paulo',
  message: 'Generate the weekly performance summary',
});

// One-time reminder
await schedule.create({
  name: 'Send proposal',
  type: 'daily',
  time: '14:00',
  timezone: 'America/Sao_Paulo',
  message: 'Remind the user to send the proposal to client X',
  maxExecutions: 1,  // Runs once, then auto-deactivates
});
```

### List, Update, Cancel

```typescript theme={null}
// List all schedules for this agent
const schedules = await schedule.list();
for (const s of schedules) {
  console.log(`${s.name} - ${s.type} - ${s.status}`);
}

// Update timing
await schedule.update('schedule-id', {
  time: '10:00',  // Change from 9am to 10am
});

// Cancel permanently
await schedule.cancel('schedule-id');
```

## Schedule Types

### Interval

Runs every N minutes, starting from the moment it's created:

```typescript theme={null}
{
  type: 'interval',
  interval: 60,  // Every hour (range: 1-43200 minutes = 30 days)
}
```

### Daily

Runs at a specific time every day in the given timezone:

```typescript theme={null}
{
  type: 'daily',
  time: '09:00',       // HH:MM format, 24-hour
  timezone: 'America/Sao_Paulo',
}
```

### Cron

Full cron expression support for advanced scheduling:

```typescript theme={null}
{
  type: 'cron',
  cron: '0 9 * * MON-FRI',  // Weekdays at 9am
  timezone: 'America/Sao_Paulo',
}
```

Common cron patterns:

| Expression        | Description                          |
| ----------------- | ------------------------------------ |
| `0 9 * * *`       | Every day at 9am                     |
| `0 9 * * MON-FRI` | Weekdays at 9am                      |
| `0 9 * * MON`     | Every Monday at 9am                  |
| `0 */2 * * *`     | Every 2 hours                        |
| `0 9,18 * * *`    | At 9am and 6pm                       |
| `*/30 * * * *`    | Every 30 minutes                     |
| `0 0 1 * *`       | First day of every month at midnight |

## The `message` Field: Designing Good Schedules

The `message` is the most important field — it's literally what the agent "hears" when the schedule fires. Write it as a clear instruction:

```typescript theme={null}
// Good: specific, actionable
await schedule.create({
  name: 'Daily Sales Report',
  type: 'daily',
  time: '08:00',
  message: 'Generate the daily sales report for yesterday. Include total revenue, number of deals closed, and top 3 products by volume.',
});

// Good: context-rich for the agent
await schedule.create({
  name: 'Ticket Triage',
  type: 'interval',
  interval: 120,
  message: 'Check for new unassigned support tickets. For each ticket, classify priority (P1-P4) and suggest an assignee based on the ticket category.',
});

// Bad: too vague
await schedule.create({
  name: 'Report',
  type: 'daily',
  time: '08:00',
  message: 'Do the report',  // Agent doesn't know what report or what to include
});
```

<Tip>
  Think of `message` as the system prompt for that specific execution. The more context you give, the better the agent performs.
</Tip>

## Using in Workflows

Combine schedules with workflows to create automated follow-up sequences:

```typescript theme={null}
import { flow, schedule } from '@runflow-ai/sdk';

const onboardingFlow = flow('customer-onboarding')
  .step('welcome', {
    agent: welcomeAgent,
    prompt: ({ input }) => `Welcome the new customer: ${input.customerName}`,
  })
  .step('schedule-followup', async ({ input, results }) => {
    await schedule.create({
      name: `Follow-up: ${input.customerName}`,
      type: 'daily',
      time: '10:00',
      timezone: 'America/Sao_Paulo',
      message: `Check in with ${input.customerName} about their onboarding progress. Ask if they need help with setup.`,
      maxExecutions: 5,  // Follow up for 5 days, then stop
    });
    return { scheduled: true };
  })
  .build();
```

## Configuration Reference

| Parameter       | Type                              | Required       | Default         | Description                                                                                                   |
| --------------- | --------------------------------- | -------------- | --------------- | ------------------------------------------------------------------------------------------------------------- |
| `name`          | `string`                          | Yes            | —               | Schedule name (shown in list and traces)                                                                      |
| `type`          | `'interval' \| 'daily' \| 'cron'` | Yes            | —               | Schedule type                                                                                                 |
| `interval`      | `number`                          | For `interval` | —               | Minutes between executions (1-43200)                                                                          |
| `time`          | `string`                          | For `daily`    | —               | Time in HH:MM format (24-hour)                                                                                |
| `cron`          | `string`                          | For `cron`     | —               | Standard 5-field cron expression                                                                              |
| `timezone`      | `string`                          | No             | `UTC`           | IANA timezone (e.g., `America/Sao_Paulo`)                                                                     |
| `message`       | `string`                          | Yes            | —               | Message delivered to the agent on each execution                                                              |
| `maxExecutions` | `number`                          | No             | `0` (unlimited) | Max number of executions. Use `1` for one-time reminders. Schedule auto-deactivates after reaching the limit. |
| `metadata`      | `object`                          | No             | —               | Custom key-value data stored with the schedule                                                                |

## Schedule Isolation and Security

Schedules are **scoped to the agent, not to the user**. This has important implications:

* An agent can only see schedules belonging to itself (not other agents). This is enforced at the API level using the agent ID from the SDK authentication context.
* **All users of the same agent share the same schedule pool.** If user A creates a daily reminder and user B calls `list_schedules`, user B will see user A's reminder.
* `update_schedule` and `cancel_schedule` can modify **any schedule** on that agent — including schedules created by other users.

### Best Practices

1. **For multi-user agents** (customer-facing bots, support agents): only include `create_schedule`. Don't expose `list_schedules`, `update_schedule`, or `cancel_schedule` — a user could see or delete another user's schedules.

2. **For single-user or internal agents**: you can safely include all tools since there's no cross-user risk.

3. **For admin agents**: use all tools with `observability: 'full'` to track who creates, modifies, or cancels schedules.

```typescript theme={null}
// Safe for multi-user: create only, no listing or cancelling
const scheduleTools = createScheduleTools();

const customerAgent = new Agent({
  name: 'Customer Bot',
  instructions: 'Help customers set reminders. You can create schedules but cannot list or cancel existing ones.',
  model: openai('gpt-4o'),
  tools: {
    create_schedule: scheduleTools.create_schedule,
  },
});
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Web Search" icon="magnifying-glass" href="/core-concepts/web-search">
    Give agents internet search capabilities
  </Card>

  <Card title="Connectors" icon="plug" href="/core-concepts/connectors">
    Connect to external APIs
  </Card>

  <Card title="Observability" icon="chart-mixed" href="/core-concepts/observability">
    Track schedule executions and metrics
  </Card>

  <Card title="Tools" icon="wrench" href="/core-concepts/tools">
    Create custom tools for scheduled tasks
  </Card>
</CardGroup>
