Schedules and reminders
A schedule or a reminder lets an agent act without an inbound message.
Schedules vs reminders
| Schedule | Reminder | |
|---|---|---|
| Defined | At deploy time, in bot/schedules/* | At runtime, with createReminder or host.reminders |
| Scope | The whole agent | One conversation |
| Session | Starts a new task session, or hands off through receive | Wakes an existing conversation |
| Cadence | 5-field cron, in UTC, to the minute | every, cron, delay, or at |
| Dev mode | Manual dispatch only | Manual dispatch only, with timers off |
Schedules
Markdown schedules
A Markdown file with cron: frontmatter is a fire-and-forget task:
---cron: "0 9 * * 1-5"---Pull open incidents and post a summary to the metrics endpoint.Each fire starts a task-mode session with the body as its prompt, and
the session runs to session.completed or session.failed without
follow-ups.
Schedule handlers
A run handler can call tools or hand work to a channel, whose delivery
events then fire:
import { defineSchedule } from "@grok/agent-sdk/schedules";import webhook from "../channels/webhook.js";export default defineSchedule({ cron: "*/30 * * * *", async run({ receive, waitUntil, appAuth, host }) { waitUntil( receive(webhook, { message: "Report any new critical alerts.", auth: appAuth, }) ); },});defineSchedule takes exactly one of markdown or run. appAuth is a
schedule-scoped principal for work the agent does on its own behalf. The
handler also receives callTool, for server-tool calls without a model
turn, and host, which holds host.mcp, host.github, host.slack,
and host.reminders.
Dispatch a schedule
Schedules fire on their cron cadence on Cursor hosting and under
production agent-sdk serve. Run them in exactly one serve process
per project, or pass --no-schedules to turn them off. serve --dev
fires nothing on its own, so dispatch by hand:
curl -X POST http://127.0.0.1:3000/<slug>/v1/dev/schedules/heartbeatThe playground and handle.dispatchSchedule("heartbeat") dispatch the
same way.
Schedules on Cursor hosting
Each deploy replaces the hosted schedules with the ones in
bot/schedules/, so a schedule you delete stops after your next deploy.
A run handler must finish in 26 minutes, including the work it passes
to receive and waitUntil. Module memory doesn't carry over to the
next fire, so keep state in ctx.host.kv or ctx.host.files. Each scheduled time
starts at most one run, and a stopped deployment skips its fires.
A schedule's name is its path under bot/schedules/ without the
extension. On Cursor hosting, a name has 1 to 100 letters, digits, .,
_, -, or /, and starts with a letter or digit. A deploy can hold
at most 64 schedules, and deploy rejects one that breaks either rule.
Reminders
A reminder belongs to one channel continuation, and it stays armed
across restarts. A recurring reminder works like setInterval, and a
one-shot reminder works like setTimeout.
Reminders take a prompt or a named handler. A prompt reminder sends
prompt into the conversation when it fires, and until tells the
model when to cancel it:
await handle.createReminder({ purpose: "ci_recheck", channelId: "drive", continuationToken: "pr:acme/checkout#42", delay: "2h", prompt: "Re-check CI. Only act if still failing.", until: "Cancel once CI is green or the PR is merged.",});When host code must decide what happens on each tick, write a named
handler. It starts a model turn only if it calls followup:
// bot/reminders/ci_recheck.tsimport { defineReminder } from "@grok/agent-sdk/reminders";export default defineReminder({ async run({ args, followup }) { await followup({ message: `Re-check CI for ${String(args.prUrl)}.`, }); return { action: "delivered" }; },});Pass its default export as handler, with serializable args:
import ciRecheck from "./bot/reminders/ci_recheck.js";await handle.createReminder({ purpose: "ci_recheck", channelId: "drive", continuationToken: "pr:acme/checkout#42", every: "30m", handler: ciRecheck, args: { prUrl: "https://github.com/acme/checkout/pull/42" },});Channel handlers, tools, and schedule runs get the same API as
host.reminders. builtinTools: { reminders: true } gives the model
reminders_create, reminders_list, and reminders_cancel for the
current conversation. See
built-in tools.
--dev doesn't fire reminders on its own. Dispatch one by hand, or call
handle.dispatchReminder(id) from code:
curl http://127.0.0.1:3000/<slug>/v1/dev/reminders # listcurl -X POST http://127.0.0.1:3000/<slug>/v1/dev/reminders/<id> # fire oneCancel a reminder when its subject ends, for example on
pull_request.closed. Keep wake prompts generic so the agent re-reads
live state.