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 control lists + standalone lanes run once; reconcile runs per visible project.

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.

iCloud delivers reminders silently to both ends. EKEventStoreChanged only fires for local macOS edits, so iOS-originated changes are picked up by the 5s 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).

Three reverse signals: capture, close, notes-edit. All other reminder edits are clobbered next sync.

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:

Tamper is non-fatal. The body is rewritten from bead state, notes are preserved, a banner records the event, and the banner self-clears on the next clean cycle.

Polling vs notifications

TriggerLatencyNotes
Interval (default)≤ 5slaunchd plist overrides the in-code 30s default to 5s. Acceptable for a personal control surface.
EKEventStoreChanged~ 1sFires 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 unloadDaemon 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.

Cooldown. Auto-fixer also enforces RBRIDGE_FIXER_COOLDOWN_S (default 1h) between escalations so a hard-down dependency can't spawn unlimited self-debug sessions.