Skip to main content
SONZAI

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.

MethodReturnsDescription
create(agentID, userID, opts)ScheduleCreate a recurring schedule
list(agentID, userID)Schedule[]List all schedules for the user
get(agentID, userID, scheduleID)ScheduleFetch a single schedule
update(agentID, userID, scheduleID, opts)SchedulePatch cadence, active window, bounds, or inventory linkage
delete(agentID, userID, scheduleID)voidDelete 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

Next steps

  • Wakeups — the one-off counterpart.
  • Inventory — structured per-user items that schedules can reference.
  • Memory — how user responses to reminders flow into long-term memory.

On this page