Skip to main content
A schedule trigger runs a workflow or agent 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 and a cron schedule, then attach a target.
src/triggers/morning-check.ts
import { defineCronSource } from "@keystrokehq/keystroke/trigger";
import workflow from "../workflows/morning-check";

export default defineCronSource({
  slug: "morning-check",
  schedule: "0 9 * * *",
}).attach({ workflow });
OptionRequiredWhat it does
slugYesStable trigger slug (used in the attachment id and run history)
scheduleYesA cron expression for when the trigger fires

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:
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.
Cron expressions have no timezone option. They evaluate against the runtime clock: UTC on the hosted platform, and your machine’s local time under keystroke dev. For a specific local time in production, convert it to UTC in the expression (for example, 9am US Eastern is 0 13 * * * or 0 14 * * * depending on daylight saving).

Empty input

A schedule fires on time alone; there is no inbound payload. A schedule attached to a workflow runs it with an empty input ({}), so its workflow should accept an empty object:
input: z.object({}),
A schedule attached to an agent runs it with a static prompt (or a prompt() function that ignores its argument):
src/triggers/morning-check.ts
import { defineCronSource } from "@keystrokehq/keystroke/trigger";
import support from "../agents/support";

export default defineCronSource({ slug: "morning-check", schedule: "0 9 * * *" }).attach({
  agent: support,
  prompt: "Run the morning check and summarize anything that needs attention.",
});

Test it while building

You don’t have to wait for the clock. Invoke the target directly while developing:
keystroke workflows run morning-check --input '{}'
keystroke agents prompt support --message "Run the morning check."
After deploy, the platform 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

Polling

Run on a schedule, but only when there’s new work.

Webhooks

Run on inbound HTTP requests instead of a clock.

Advanced triggers

Agent prompts, transforms, and filtering.

Triggers overview

Sources, attach, and attachment ids.