---
name: scheduling
description: "Calendar and scheduling — create events, manage appointments, set up recurring triggers."
tools:
  - schedule-event
  - schedule-list
  - schedule-get
  - schedule-update
  - schedule-cancel
  - schedule-export-ics
  - schedule-import-ics
  - time-resolve
always: false
embed: false
metadata: {"platform":{}}
---

# Scheduling

Manages events, appointments, and recurring triggers in the graph. Events are discoverable via memory-search.

## When to create events

Create events immediately when the user identifies something time-bound — an appointment, a reminder, a recurring briefing. Confirm after creation; don't ask permission first.

## One-time vs recurring

- **One-time events** have a `startDate` and optionally an `endDate`. They transition to `due` when their time arrives (via the platform cron) and to `completed` when the agent or user marks them done.
- **Recurring events** have a `recurrence` (cron expression) and a computed `nextRun`. They stay in `scheduled` status permanently. The cron advances `nextRun` and sets `lastTriggered` each cycle.

## Cron expressions

Use standard 5-field cron syntax: `minute hour day-of-month month day-of-week`.

| Pattern | Meaning |
|---------|---------|
| `0 8 * * 1-5` | Weekdays at 8am |
| `0 9 * * 1` | Every Monday at 9am |
| `0 0 1 * *` | First of each month at midnight |
| `*/30 * * * *` | Every 30 minutes |

## Skip next

For recurring events, `schedule-update` with `skipNext: true` advances `nextRun` by one cycle without triggering. Use when the user says "skip tomorrow's briefing" or similar.

## Cancellation

`schedule-cancel` sets status to `cancelled`. For recurring events this kills the entire series. There is no per-occurrence cancellation — use skip-next for that.

## Event actions

Events can carry an automated action that fires when the event's time arrives. The platform heartbeat cron (every minute) checks for due events and dispatches their actions by spawning the target plugin's MCP server and calling the named tool.

To create an event with an action, pass the `action` parameter to `schedule-event`:

```json
{ "plugin": "waitlist", "tool": "waitlist-extract", "args": {} }
```

- `plugin` — the plugin directory name under `$PLATFORM_ROOT/plugins/` (validated at creation — must exist)
- `tool` — the MCP tool name to call (validated at dispatch — if it doesn't exist, the error is logged)
- `args` — optional arguments passed to the tool call

Actions are stored as flat properties on the Event node: `actionPlugin`, `actionTool`, `actionArgs` (JSON string). Use `schedule-update` with `action` to modify or `action: null` to clear.

Dispatch results are recorded on the Event node (`lastDispatchResult`, `lastDispatchAt`) and appended to the notes history. The admin can also read dispatch logs via `logs-read` with `type: "heartbeat"`.

Users and plugins both create events with actions via the same `schedule-event` interface — the mechanism is symmetric.

## Locale time formatting

All timestamps in tool responses (schedule-list, schedule-get, schedule-event) are formatted in the user's locale timezone with a relative delta. The user's timezone is read from `UserProfile.timezone` (IANA format, e.g. `Europe/London`).

Example output: `Thursday 3 April 2026, 12:00 BST (in 51 minutes)` instead of raw `2026-04-03T11:00:00.000Z`.

Relative deltas follow standard cutoffs: < 1 min ("just now"), minutes, hours, days (1–6), and no relative for 7+ days. Storage remains UTC — formatting is applied at output time only.

The platform auto-detects the server's IANA timezone and sets it on `UserProfile.timezone` at first login (and backfills existing profiles that lack one). Onboarding step 7 confirms the auto-detected timezone with the user. If `UserProfile.timezone` is still not set (e.g. Neo4j was unreachable during the backfill), the tool returns an error instructing the agent to set the timezone — there is no fallback to UTC. To set the user's timezone manually, use `profile-update` (memory plugin) with `profileFields: { timezone: 'Europe/London' }`. The timezone must be a valid IANA identifier (e.g. `Europe/London`, `America/New_York`, `Asia/Tokyo`).

## Time resolution

`time-resolve` converts a UTC ISO 8601 timestamp to the user's locale timezone with a human-readable relative delta. Use it when the agent needs to present a time that didn't come from a scheduling tool (e.g., a timestamp from another plugin or a raw ISO string the user provided).

## Relationships

Link events to other graph entities using relationship types:

- `ABOUT` — what the event concerns (a Service, Task, etc.)
- `SCHEDULED_FOR` — who the event is for (a Person)
- `PART_OF` — which conversation created the event

The `sessionKey` parameter on `schedule-event` automatically creates the `PART_OF` relationship.

## ICS calendar files

Export events as `.ics` files so users can add them to Apple Calendar, Google Calendar, or Outlook. Use `schedule-export-ics` with the event IDs, then `file-attach` with the returned path and `render-component file-attachment` to deliver the download.

Import `.ics` files uploaded by the user. Use `schedule-import-ics` with the attachment path. Present the extracted events for confirmation before creating them via `schedule-event`.

Cron→RRULE mapping for recurring events: daily, weekly-with-days, and monthly-by-date patterns produce standard RRULE properties. Cron patterns that cannot map to RRULE (e.g. `0 8 1,15 * *`) produce individual VEVENTs per occurrence instead.

## References
- references/scheduling.md (inherited from business-assistant — booking protocol, geographic clustering, briefings)
