Calendar and time
The change feed, event writes, timezone handling across two countries, and the triggers that hang off a moving calendar.
Live.The calendar section, confirm-first writes and the change feed all run today.
Reading
Today's events ride in the brief, with the timezone you are actually in rather than the one your account was set up with. You move between Italy and Brazil, so a time without a zone is a bug, and "today" is taken from the machine clock rather than from a home country.
The change feed
Otto currently sees a static list of today's events. The change feed snapshots the next fourteen days on a cycle and diffs successive snapshots into real change events:
| Change | Meaning |
|---|---|
invited | a new event appeared |
moved | the start time changed |
location | the location changed |
cancelled | the event went away |
declined | an attendee declined |
Only differences are emitted, so a poll that finds nothing new produces nothing. The fetch is injected rather than hardcoded, which is what makes the diff testable against fixtures instead of against your real calendar.
This turns the calendar from something Otto looks at into something that can wake it, which is what everything below depends on.
Writes
Creating an event is confirm-first: Otto shows the exact item, with title, time, timezone and target calendar, and waits for a clear yes or a tapped button. Task creation follows the same rule.
Otto never deletes or edits an event it did not create. Every event it does create writes an undo note saying exactly how to reverse it.
Not built yet
These do not exist. The substrate each one reads is live, so every item below is a self-contained piece of work rather than a rewrite. The button copies a prompt that builds it in your own checkout.
"Let's do Thursday at three" in a voice note resolves against the real calendar and the right country's clock and comes back as one [block it: Thu 14 Aug, 15:00 BRT] button instead of a clarifying question; handles the Italy/Brazil ambiguity of "your time" and "next Friday" explicitly.
Triggers gain an optional eventId so when the change feed reports an event moving, its prep, travel and follow-up triggers silently re-arm to the new time and a cancellation deletes them; they never learns their 15:00 prep fired for a meeting that moved to Thursday.
A ~21:30 line about tomorrow rather than today: the first hard commitment and what it implies for waking, the longest free block, anything they can still move or decline tonight while people are awake, and on an empty night whatever leisure is actually queued — sends nothing when there's nothing.
For events with an address, compute door-to-door travel from where they actually is, fire one "leave in 10" only when travel genuinely threatens the start, re-check 20 minutes out for traffic, and flag at booking time when two consecutive events are in places they can't get between; a reminder at the event's start time is useless and São Paulo traffic makes an optimistic calendar a lie.
Flags invites still in needsAction as the event nears, prose invitations ("are you coming on the 14th?") with a decide-by date distinct from the event date, events moved after they accepted, and accepted events that now overlap — one message per conflict with [yes / no / propose new time], never re-raised.
A separate low-key "Otto" Google calendar it writes freely without confirmation — leave-by blocks, bill due dates, follow-up deadlines, trigger fire times — where deleting one of its events reads back as dropping the underlying loop; their calendar app becomes a second, editable surface for Otto's state.
Two minutes after a meeting ends — from the calendar, or from mic/camera release for a call that was never on it — and only if they're back at the keyboard, one line: "anything from the Marco call?" with a [nothing] button, where a voice answer parses straight into loops and follow-ups; retires itself for a series after three ignores.
Learn their real deep-work windows from which hours repos go dirty, write the biggest matching free block into the calendar as busy so external schedulers can't take it, and have the send queue treat it exactly like a meeting; today the calendar-aware hold only respects meetings other people booked.
Computes meeting hours, back-to-back chains and whether a lunch gap survives, warning only on threshold crossings with [add 15m buffers / show the weakest one / leave it] — fired when the day gets booked, not the morning of, when it can still be fixed.
A forecast sidecar keyed to the `location` field of today's events that speaks only when weather would alter a decision already on the calendar — rain in the window they have to cross town, a storm during an outdoor booking — and never as a standalone forecast; the message must name an event or it doesn't send.
A monthly pass over recurring series — attendance versus declines, whether the invite has had any agenda or attendee activity in 8 weeks, hours per year it costs — proposing the zombies for death one tap at a time; the calendar counterpart to the self-audit that today only judges Otto's own triggers.