Skip to content

Calendar

Calendar mirrors your Google Calendar into Subspace so agents can read your schedule and, with your approval, create events on it, while you keep using your native calendar app for everything else. Subspace doesn’t try to replace it, it adds a thin agenda view and an event-driven hook into the rest of the system.

Calendar rides the same OAuth grant as Mail: /oauth/gmail/start requests the calendar scope alongside Gmail’s, so connecting one account connects both. If an existing grant predates the calendar scope, the account shows the same amber reconnect banner Mail does; re-running the OAuth flow (always prompt=consent) mints a fresh full-scope grant.

Every 30 seconds, the calendar sync engine first mirrors your calendar directory into comms.cal_calendars, then syncs each calendar using Google’s events.list syncToken semantics:

  • No stored sync token triggers a full listing.
  • A stored token fetches only what changed since last time, an incremental delta.
  • An expired token (Google returns 410 GONE) triggers a silent full resync that also purges any mirrored events the provider no longer lists.

Every changed event commits in one transaction: the comms.cal_events row upserts (attendees as a JSON array of {email, name?, self?}, and any CRM page ids already resolved for its attendees are preserved across the update rather than overwritten, so a meeting page linked to an event survives the next sync). A cancelled event (status: 'cancelled') deletes its mirror row.

Attendees resolve to CRM pages through the same identity resolver mail and CRM auto-create use: an exact, case-insensitive email match first, falling back to a normalized-name match (lowercased, diacritics stripped, punctuation collapsed to spaces) against known CRM identities. An attendee that resolves gets a real page link in the agenda; one that doesn’t just shows as plain text.

Calendar isn’t read-only. An agent (through a gated tool) can push a new event upstream first, then commit the local mirror and its change-feed row in one transaction, returning the new event’s id, provider id, and calendar. Google round-trips the write as agent-created through extendedProperties.private, so both the provider and Subspace agree on provenance. Gating happens at the tool layer, the write to Google itself carries no approval prompt of its own; whatever policy governs the tool call governs the event.

/p/calendar is deliberately thin: a this-week list (Monday through Sunday), grouped by day, each row showing the time range, title, and calendar name.

  • Attendee chips that resolved to a CRM page are [[page]] links; clicking one navigates straight to that person’s page.
  • Events an agent created carry an agent-created chip.
  • Any event with attendees gets a Record button. Clicking it calls the meeting-start flow and navigates you straight to the new meeting page.

There’s no month grid, by design, Subspace isn’t trying to be your calendar app, just a one-week-ahead surface agents and you can both act on.

cal.events.list {from?, to?} is an ungated, read-only tool that lists synced events in a date range (defaulting to the next 7 days, capped at 200 rows), returning {eventId, title, start, end, attendees, calendar} per event. It’s the read half of the calendar surface agents reach for when reasoning about your schedule, for example before proposing a meeting time or checking whether you’re free.