Getting started
This tutorial takes you from an empty directory to a fully reconciled calendar — declare events in YAML, plan the diff, apply it, then watch cicada detect an out-of-band edit as drift.
Everything below runs against the bundled fake provider: a file-backed stand-in for a real calendar service. No Google account, no credentials, no network.
You will need Rust (cargo) installed.
Install cicada
Clone the repository and run the install task:
git clone https://github.com/9aia/cicada.git
cd cicada
cargo xtask installThis release-builds two binaries into ~/.local/bin:
cicada— the CLIcicada-mcp— the MCP server for agents (you will not need it today)
Check the install:
cicada --helpScaffold a project
Make a fresh directory and scaffold it:
mkdir ~/calendars && cd ~/calendars
cicada initcicada creates three things:
cicada.yaml — the manifest (lists your calendar files)
calendars/work.yaml — a starter calendar
.cicada/ — runtime dir; state.yaml lands here on applyOpen calendars/work.yaml. It declares one logical calendar, work, with two events — a recurring standup and a one-off offsite:
apiVersion: cicada/v1
kind: Calendar
metadata:
name: work
timeZone: UTC
provider: fake # the bundled demo provider
calendarId: primary
spec:
events:
- id: standup
summary: Daily Standup
start: "09:00" # wall clock — recurs at 09:00 local, DST-proof
duration: 30m
recurrence:
freq: WEEKLY
byDay: [MO, TU, WE, TH, FR]
attendees:
- email: [email protected]
reminders:
- popup: 10m
conferencing: meet
- id: offsite
summary: Team Offsite
start: 2026-03-20T10:00
duration: 4hValidate the YAML
cicada validateinfo: discovered manifest cicada.yaml
✓ calendars/work.yaml — work — 2 event(s)
1 valid, 0 failedValidate checks the schema shape plus domain invariants (end after start, sane recurrence, no duplicate ids) — before anything touches a provider.
Plan
cicada plancalendar work (provider: fake · calendar: primary) — generated …
create offsite "Team Offsite" — 2026-03-20T10:00:00 [UTC]
create standup "Daily Standup" — 1970-01-01T09:00:00 [UTC]
plan: 2 create(2 remote ops)Plan diffs three states — desired (your YAML), recorded (the state file, empty so far) and live (what the provider has, nothing yet) — into ordered operations. Both events are new, so the plan is two creates. Nothing has been written.
(Don't worry about the 1970 date on standup — a bare "09:00" start anchors to the earliest date its recurrence rule generates. range, below, shows the real occurrences.)
Apply
cicada apply --auto-approve--auto-approve skips the confirmation prompt; without it cicada shows the plan and asks before writing.
✓ create offsite → remote fake-1
✓ create standup → remote fake-2
apply work: 2 applied · 0 skipped · 0 failedTwo things just appeared in .cicada/:
state.yaml— recorded state: your stable ids (standup) mapped to the remote ids the provider assigned (fake-2), plus a per-op journal. This is what makes re-runs converge instead of duplicating.fake-provider.json— the fake provider's database. On Google, this would be your actual calendar.
Run cicada plan again — it now reports noops, because desired, recorded, and live all agree.
See the events
cicada range --from today --to +7dwork:
2026-10-29 09:00 [UTC] → 09:30 · standup — Daily Standup
2026-10-30 09:00 [UTC] → 09:30 · standup — Daily Standup
…range expands recurrence into concrete occurrences — five weekdays, as declared. cicada show standup gives one event's full detail.
Detect drift
Now simulate someone editing the calendar behind cicada's back — the equivalent of a colleague dragging the meeting in the Google Calendar UI. With the fake provider, that means editing its database file.
Open .cicada/fake-provider.json, find the event whose remoteId is fake-2, and change its summary to "Daily Standup (moved!)". Save, then:
cicada driftwork: 1 drifted
! standup (remote fake-2): modified — live differs from recordeddrift compares recorded state against live and exits non-zero — this is how CI notices out-of-band edits. Note that cicada only tracks events it applied; a brand-new event created in the provider UI is invisible to drift, and untouchable by apply.
Reconcile
Two ways to resolve drift. First, refresh — pull live truth into recorded state:
cicada refreshrefreshed work: 1 updated · 0 removed · 1 unchanged
~ standup — record now matches liveNow recorded matches live, so cicada drift is clean. But your YAML still says "Daily Standup" — plan again and cicada proposes the repair:
cicada plan update standup remote fake-2 → "Daily Standup"
~ summary: Daily Standup (moved!) → Daily StandupApply it and the declared name is pushed back to the provider. The loop is complete: desired → plan → apply → drift → refresh → re-plan.
What's next
- Connect Google Calendar — the same lifecycle against a real provider.
- Reconcile a calendar — force, resume, and notifications.
- Use cicada from an agent — the MCP interface to everything you just did by hand.
- Why plan/apply/drift? — the reconciliation model underneath.