Appearance
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:
md
---
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:
ts
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:
bash
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:
ts
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:
ts
// bot/reminders/ci_recheck.ts
import { 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:
ts
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:
bash
curl http://127.0.0.1:3000/<slug>/v1/dev/reminders # list
curl -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.