Scheduled Reminders
Register recurring schedules that trigger proactive agent messages at specific times, with per-schedule timezones, quiet-hours filtering, and structured inventory linkage.
Scheduled Reminders let your agent message users on a schedule — daily, weekly, or every few hours. The platform handles timezones, DST, and quiet-hours automatically, and reads live structured data at fire time so messages always reflect current information. Use it for medication reminders, habit nudges, daily check-ins, or any time-based message you want the agent to initiate.
What you can build with it
- Medication adherence — remind users at specific times of day with the correct drug and dose, resilient to dosage changes (tutorial)
- Habit streaks — daily or weekly nudges tied to a goal the agent tracks in memory
- Exercise / hydration check-ins — a cadence with quiet-hours respect, skipping fires overnight
- Bill-payment reminders — one-off or monthly reminders bounded by
starts_at/ends_at - Ritual / daily-standup messages — an opening line from the agent to start the day
Quickstart
Create a daily 09:00 Asia/Singapore check-in. The response contains schedule_id, next_fire_at (UTC), and next_fire_at_local (in the schedule's timezone).
import { Sonzai } from "@sonzai-labs/agents";
const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const schedule = await client.schedules.create("agent_abc", "user_123", {
cadence: {
simple: { frequency: "daily", times: ["09:00"] },
timezone: "Asia/Singapore",
},
intent: "check in on how the user is feeling",
check_type: "reminder",
});
console.log(schedule.schedule_id); // "sched_01HX..."
console.log(schedule.next_fire_at); // "2026-04-22T01:00:00Z"
console.log(schedule.next_fire_at_local); // "2026-04-23T09:00:00+08:00"Core concepts
Cadence shapes
A cadence tells the platform when to fire. Two mutually exclusive shapes are supported: simple and cron. The simple shape covers most use cases through a frequency field with three options: "daily" fires at each listed times entry every calendar day; "weekly" fires on specified days_of_week at each listed time; "interval_hours" fires repeatedly at a fixed interval starting from starts_at (or schedule creation if omitted). All wall-clock times are evaluated in the schedule's timezone.
A 3x-daily schedule:
{
"cadence": {
"simple": { "frequency": "daily", "times": ["08:00", "13:00", "20:00"] },
"timezone": "Asia/Singapore"
}
}An every-4-hours interval:
{
"cadence": {
"simple": { "frequency": "interval_hours", "interval_hours": 4 },
"timezone": "Asia/Singapore"
}
}For advanced recurrence patterns, use the cron shape with a standard 5-field cron expression (e.g. "0 9 * * 1-5" for 09:00 on weekdays). The timezone field is required in both shapes — IANA names only (e.g. "America/New_York"), not UTC offsets.
Active window — quiet hours
The active_window field is a belt-and-braces filter layered on top of the cadence. The cadence computes when a fire would occur; the active window decides whether that fire actually produces a proactive message. Fires outside the window are skipped, not deferred — the cadence grid stays perfectly predictable and no backlog accumulates.
{
"active_window": {
"hours": { "start": "08:00", "end": "22:00" },
"days_of_week": ["mon", "tue", "wed", "thu", "fri"]
}
}Both sub-fields are optional. When start is greater than end, the window wraps midnight — for example {"start": "22:00", "end": "06:00"} allows fires from 22:00 to 05:59 the next morning. This is useful for night-shift users or schedules targeting early-morning timezones where local midnight matters. Day membership is always evaluated in the schedule's own timezone, so a fire at 23:30 Friday Singapore time stays Friday even when stored as 15:30 UTC.
Inventory injection at fire time
Pass inventory_item_id on the create (or update) body to link a schedule to a structured item in the user's inventory — a medication, a goal, a plant, anything with named properties. The key property of this linkage is that the platform reads the item's live properties at every fire, not at schedule creation time. This means updating a medication's dosage, a goal's target, or any other property is automatically reflected in the next reminder without any schedule edit. The schedule is the source of truth for when; the inventory item is the source of truth for what.
Lifecycle — bounded courses
Use starts_at and ends_at (both RFC 3339 UTC) to constrain a schedule to a specific window of time. No fire is produced before starts_at; once ends_at passes, the schedule is automatically disabled — enabled flips to false. The schedule row is not deleted: the audit trail, historical fire log, and linked inventory reference remain accessible. This is a soft-disable, not a hard delete. To permanently remove a schedule and all associated fire history, use the delete method explicitly.
Full API
All methods are on client.schedules.* (TS/Python) or client.Schedules (Go). Full request / response shapes live in the API reference.
| Method | Returns | Description |
|---|---|---|
create(agentID, userID, opts) | Schedule | Create a recurring schedule |
list(agentID, userID) | Schedule[] | List all schedules for the user |
get(agentID, userID, scheduleID) | Schedule | Fetch a single schedule |
update(agentID, userID, scheduleID, opts) | Schedule | Patch cadence, active window, bounds, or inventory linkage |
delete(agentID, userID, scheduleID) | void | Delete a schedule |
upcoming(agentID, userID, scheduleID, limit) | FireTime[] | Preview the next N fires without firing them |
Combines with other features
With Inventory — structured data injected at every fire
Every schedule can reference an inventory_item_id pointing to a structured per-user item (e.g. a medication, a goal, a plant). At each fire, the platform reads the item's live properties and injects them into the agent's wakeup block — no schedule edit needed when the data changes. This is how a "reduce ibuprofen from 500mg to 250mg" change flows through to the next reminder automatically.
// 1. Add an inventory item (e.g. a medication)
const item = await client.agents.inventory.update("agent_abc", "user_123", {
action: "add",
item_type: "medication",
description: "Ibuprofen",
project_id: "proj_abc",
properties: { medication_name: "ibuprofen", dosage: "500mg" },
});
// 2. Link the schedule to it — no duplicated data
await client.schedules.create("agent_abc", "user_123", {
cadence: { simple: { frequency: "daily", times: ["08:00", "20:00"] }, timezone: "Asia/Singapore" },
intent: "remind the user to take their ibuprofen at the correct dose",
check_type: "reminder",
inventory_item_id: item.fact_id,
});
// 3. Later, the dose changes — the next fire automatically sees "250mg"
await client.agents.inventory.directUpdate("agent_abc", "user_123", item.fact_id, {
properties: { dosage: "250mg" },
});See the full worked example in the Medication Reminders tutorial.
With Wakeups — recurring vs one-off proactive messages
Schedules and Wakeups are both proactive primitives but serve different cases. Use a schedule when the agent should reach out on a repeating cadence (daily, weekly, every 4 hours). Use a wakeup when the agent should reach out once at a specific moment — a birthday, a known one-off event, or an agent-initiated interest check. Both feed into the same downstream delivery channels (SSE, polling, webhooks — see Proactive messaging).
// Recurring: Schedule
await client.schedules.create("agent_abc", "user_123", {
cadence: { simple: { frequency: "daily", times: ["09:00"] }, timezone: "Asia/Singapore" },
intent: "morning check-in on mood and sleep",
check_type: "reminder",
});
// One-off: Wakeup
await client.agents.scheduleWakeup("agent_abc", {
user_id: "user_123",
check_type: "birthday",
intent: "wish user happy birthday on their 30th",
delay_hours: 24,
});With Memory — capturing adherence signals
When the agent fires a scheduled reminder and the user responds ("took it, thanks"), the memory layer auto-captures the adherence fact. You can query these facts later to build a compliance view without adding a separate database — useful for tenant-side dashboards or escalation logic.
// After a week of firing daily medication reminders, query memory for responses
const memories = await client.agents.memory.search("agent_abc", {
query: "medication taken ibuprofen",
limit: 10,
});
for (const result of memories.results) {
console.log(result.content, result.score);
// "User confirmed taking 500mg ibuprofen" 0.87
}Tutorials
- Scheduled Reminders — end-to-end walkthrough — covers cadence shapes, DST, preview, pause/resume/delete, error codes.
- Medication Reminders — worked example — combines Inventory + Scheduled Reminders + Memory into a full medication adherence flow.
Next steps
Proactive Messaging
How agents reach out to users on their own — via recurring schedules, one-off wakeups, or tenant-triggered events, delivered over SSE, polling, or webhooks.
Wakeups
Schedule a one-off proactive agent message at a specific future moment — a birthday greeting, appointment reminder, or interest follow-up.