Sync cycle
Every 5 seconds (or sooner, on an EventKit change notification), daemon.sync_once walks all three lanes in a fixed order.
Reconcile is idempotent — a quiet cycle should log zero changes.
One full cycle
The order matters. Control lists (Projects, Settings) feed flags into
the per-project reconcile (visibility, show_completed). The
standalone lanes run before reconcile so a long beads pass can't starve
session triggers or voice mailbox drift.
iCloud · iOS · Mac — who sees what when
The daemon only touches the macOS EventKit store. Edits made on the
iPhone (by the user, or by Claude iOS via reminder_*_v0 tools) reach the Mac through iCloud
reminder sync. There is no notification when iCloud
delivers a change — it lands silently in the calendar store and
waits for the next interval poll.
This is why the launchd plist overrides the default poll to 5s: it bounds visible latency for iOS-originated changes at one interval, since the in-process notification can't reach across iCloud.
Reminder → bead signals (the only writes back)
Three small upstream flows from Reminders.app into beads. Everything else is one-way (bead → reminder).
Tamper handling
If a user (or a confused agent) edits inside <bb:meta> or <bb:desc>, the next sync notices on the expected/
actual diff:
Polling vs notifications
| Trigger | Latency | Notes |
|---|---|---|
| Interval (default) | ≤ 5s | launchd plist overrides the in-code 30s default to 5s. Acceptable for a personal control surface. |
EKEventStoreChanged | ~ 1s | Fires only for changes made on the host Mac. iCloud sync from iPhone does not surface as this notification — it lands silently in the calendar store and waits for the next interval poll. |
| SIGTERM / launchd unload | — | Daemon dies between cycles, no cleanup needed. State files are written at the end of each cycle, not mid-batch. |
Failure isolation
Each subsystem in sync_once runs through _safe:
a wrapper that catches exceptions, increments a per-subsystem failure
counter, and triggers the auto-fixer (a self-diagnose claude session) when the counter crosses RBRIDGE_FIXER_THRESHOLD (default 5). One broken project's bd list --json never blocks
the rest.
RBRIDGE_FIXER_COOLDOWN_S (default 1h) between
escalations so a hard-down dependency can't spawn unlimited
self-debug sessions.