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

# Schedules

> Run a workflow on a recurring schedule.

A schedule trigger runs a [workflow](/docs/learn/workflows/overview) or [agent](/docs/learn/agents/overview) on a recurring cron schedule. Use it for anything time-driven: a morning digest, an hourly sync, a nightly cleanup.

## Example requests

Ask your coding agent what should happen and how often. It can set up the schedule and attach it.

> "Every weekday at 8am, run the morning briefing workflow and post it to Slack."

> "On the first of each month, have the FP\&A agent review recurring charges and flag anything unused."

> "Every night, sync new rows from the form submissions table in Postgres into the reporting spreadsheet."

## Define a schedule

Use `defineCronSource` with a `slug`, `name`, `description`, and a cron `schedule`, then attach a target.

```ts src/triggers/morning-check.ts theme={null}
import { defineCronSource } from "@keystrokehq/keystroke/trigger";
import workflow from "../workflows/morning-check";

export default defineCronSource({
  slug: "morning-check",
  name: "Morning check",
  description: "Runs the morning check workflow every day at 09:00.",
  schedule: "0 9 * * *",
}).attach({ workflow });
```

| Option        | Required | What it does                                                                                              |
| ------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `slug`        | Yes      | Stable trigger slug (used in the [attachment id](/docs/learn/triggers/overview#attachment-id) and run history) |
| `name`        | Yes      | Human-readable label shown in the platform                                                                |
| `description` | Yes      | What the schedule does, shown in the platform                                                             |
| `schedule`    | Yes      | A cron expression for when the trigger fires                                                              |
| `timezone`    | No       | IANA timezone for schedule wall-clock fields (e.g. `America/New_York`). When omitted, UTC                 |
| `payload`     | No       | Static JSON passed to attachments on each tick (defaults to `{}`)                                         |

## The cron schedule

The `schedule` is a standard cron expression. The common five-field form is `minute hour day-of-month month day-of-week`:

```ts theme={null}
schedule: "0 9 * * *"      // every day at 09:00
schedule: "*/15 * * * *"   // every 15 minutes
schedule: "0 0 * * 1"      // every Monday at midnight
```

Named months and weekdays (`MON`, `JAN`) and shorthand nicknames (`@daily`, `@hourly`) are also accepted.

Pass an optional IANA `timezone` so the cron fields are wall-clock times in that zone (DST-aware). When omitted, schedules evaluate in UTC:

```ts theme={null}
defineCronSource({
  slug: "morning-check",
  name: "Morning check",
  description: "Runs the morning check workflow every day at 09:00 Eastern.",
  schedule: "0 9 * * *",
  timezone: "America/New_York", // 9:00 AM Eastern every day
}).attach({ workflow });
```

<Note>
  Prefer `timezone` for wall-clock times like "9am" in a specific region. Without it, `"0 9 * * *"` means 09:00 UTC.
</Note>

## Input / payload

By default a schedule fires with an empty input (`{}`). Pass an optional static `payload` on the source when the workflow needs input — it flows to attachments the same way a webhook body does:

```ts src/triggers/morning-report.ts theme={null}
import { defineCronSource } from "@keystrokehq/keystroke/trigger";
import workflow from "../workflows/morning-report";

export default defineCronSource({
  slug: "morning-report",
  name: "Morning report",
  description: "Runs the daily regional report.",
  schedule: "0 9 * * *",
  payload: { report: "daily", region: "us" },
}).attach({ workflow });
```

Without a `transform`, the payload must match the workflow's `input` schema (TypeScript enforces this at the `.attach()` call). Use `transform` to reshape the payload per attachment:

```ts theme={null}
.attach({
  workflow: euReport,
  transform: (payload) => ({ ...payload, region: "eu" }),
})
```

When `payload` is omitted, attachments still receive `{}`, so empty-input workflows keep working:

```ts theme={null}
input: z.object({}),
```

A schedule attached to an agent can use a static `prompt`, or a `prompt(payload)` function that reads the cron payload:

```ts src/triggers/morning-check.ts theme={null}
import { defineCronSource } from "@keystrokehq/keystroke/trigger";
import support from "../agents/support";

export default defineCronSource({
  slug: "morning-check",
  name: "Morning check",
  description: "Asks support to summarize overnight issues.",
  schedule: "0 9 * * *",
  payload: { focus: "overnight" },
}).attach({
  agent: support,
  prompt: (payload) =>
    `Run the morning check (focus: ${(payload as { focus: string }).focus}) and summarize anything that needs attention.`,
});
```

## Test it while building

You don't have to wait for the clock. After deploy, fire the cron trigger immediately (same static payload as a scheduled tick — platform only):

```bash theme={null}
keystroke triggers invoke morning-check
keystroke triggers invoke morning-check --workflow morning-check   # narrow to one attachment
```

Or invoke the target directly while developing:

```bash theme={null}
keystroke workflows run morning-check --input '{}'
keystroke agents prompt support --message "Run the morning check."
```

After deploy, the platform also fires the schedule on time — every tick runs the target unconditionally (cron has no filters). Inspect runs with `keystroke triggers runs list morning-check --workflow <target-slug>` (or `--agent <target-slug>`).

## Next steps

<CardGroup cols={2}>
  <Card title="Polling" href="/docs/learn/triggers/polling">
    Run on a schedule, but only when there's new work.
  </Card>

  <Card title="Webhooks" href="/docs/learn/triggers/webhooks">
    Run on inbound HTTP requests instead of a clock.
  </Card>

  <Card title="Advanced triggers" href="/docs/learn/triggers/advanced-triggers">
    Agent prompts, transforms, and filtering.
  </Card>

  <Card title="Triggers overview" href="/docs/learn/triggers/overview">
    Sources, attach, and attachment ids.
  </Card>
</CardGroup>
