Skip to main content
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:
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 KeyWhat It Does
create_scheduleCreates a new schedule
list_schedulesLists all schedules for the agent
update_scheduleUpdates an existing schedule
cancel_scheduleCancels (deletes) a schedule
Pick only the tools your agent actually needs:
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
}
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.
ScenarioTools to Include
Multi-user agent (customers, support)create_schedule only
Single-user personal assistantcreate_schedule + cancel_schedule
Internal team bot (shared context)create_schedule + list_schedules + cancel_schedule
Admin/monitoring agentAll 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:
ParameterTypeRequiredDescription
namestringYesDescriptive name (e.g., “Daily report reminder”)
type'interval' | 'daily' | 'cron'YesSchedule type
intervalnumberFor interval typeMinutes between each execution (1-43200)
timestringFor daily typeTime in HH:MM format, 24-hour (e.g., “09:00”)
cronstringFor cron typeCron expression (e.g., “0 9 * * 1-5”)
timezonestringNoIANA timezone (default: “UTC”)
messagestringYesThe message sent to the agent on each execution
maxExecutionsnumberNoMax 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."
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.

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:
// 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

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.
main.ts
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.
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.`
See the SDR Agent with Scheduled Follow-ups use case for a complete working example with lead qualification, scheduled callbacks, and inactive lead follow-ups.

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

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

// 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:
{
  type: 'interval',
  interval: 60,  // Every hour (range: 1-43200 minutes = 30 days)
}

Daily

Runs at a specific time every day in the given timezone:
{
  type: 'daily',
  time: '09:00',       // HH:MM format, 24-hour
  timezone: 'America/Sao_Paulo',
}

Cron

Full cron expression support for advanced scheduling:
{
  type: 'cron',
  cron: '0 9 * * MON-FRI',  // Weekdays at 9am
  timezone: 'America/Sao_Paulo',
}
Common cron patterns:
ExpressionDescription
0 9 * * *Every day at 9am
0 9 * * MON-FRIWeekdays at 9am
0 9 * * MONEvery 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:
// 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
});
Think of message as the system prompt for that specific execution. The more context you give, the better the agent performs.

Using in Workflows

Combine schedules with workflows to create automated follow-up sequences:
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

ParameterTypeRequiredDefaultDescription
namestringYesSchedule name (shown in list and traces)
type'interval' | 'daily' | 'cron'YesSchedule type
intervalnumberFor intervalMinutes between executions (1-43200)
timestringFor dailyTime in HH:MM format (24-hour)
cronstringFor cronStandard 5-field cron expression
timezonestringNoUTCIANA timezone (e.g., America/Sao_Paulo)
messagestringYesMessage delivered to the agent on each execution
maxExecutionsnumberNo0 (unlimited)Max number of executions. Use 1 for one-time reminders. Schedule auto-deactivates after reaching the limit.
metadataobjectNoCustom 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.
// 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

Web Search

Give agents internet search capabilities

Connectors

Connect to external APIs

Observability

Track schedule executions and metrics

Tools

Create custom tools for scheduled tasks