Specs/Meeting-Scheduler-Spec.md
Meeting Scheduler — Specification
Module 1 of the Personal Assistant Platform. Finds mutual availability, presents ranked options, and books calendar events with the correct conferencing link.
Skill: ~/.claude/skills/scheduler.md
Code: /Users/arijitchowdhury/AI-Development/Scheduler/
Tests: 42 unit tests, all passing (as of 2026-04-15)
Architecture
User request
↓
scheduler.md skill (orchestrator)
├── gws calendar freebusy (Google Calendar API)
├── resolve_contact.py (name → email + timezone)
├── find_slots.py (mutual availability + ranking)
└── render_options.py (ASCII timeline + option cards)
↓
User picks option
↓
gws calendar events insert (creates event as organizer)
Python helpers (src/)
| File | Purpose |
|---|---|
resolve_contact.py |
Resolves name → {email, timezone, working_hours} from team.json or calendar subscriptions |
find_slots.py |
Computes mutual free windows, ranks by tier (Green/Yellow/Red) and score |
render_options.py |
Renders ASCII timeline + option cards for terminal display |
calendar_client.py |
Thin wrapper around gws freebusy queries |
Config files (config/)
| File | Purpose |
|---|---|
team.json |
Canonical team directory: name, email, timezone, working hours |
preferences.json |
User defaults: conferencing provider, working hours, avoid hours |
Slot Ranking
Slots are tiered and scored:
| Tier | Condition |
|---|---|
| 🟢 Green | All participants free, within working hours for everyone |
| 🟡 Yellow | Outside working hours for at least one participant |
| 🔴 Red | Conflict or unavailable |
Scoring factors: preferred hours, avoid hours, tier. max_results defaults to 6.
Time Zone Handling
- Owner timezone:
America/New_York(EDT/EST) - Working hours for global team calls:
09:00–23:59(owner) to reach Asia-Pacific - All freebusy queries use UTC; local times rendered per participant
- AEST = UTC+10 (April, no daylight saving)
Booking Behavior (v1.1 — corrected)
- Organizer is NOT added to attendees array — as calendar owner, Arijit is implicit organizer; adding him caused a spurious "accept your own meeting" notification. Fixed 2026-04-15.
- Conference default: Google Meet (
hangoutsMeet) — see v2 gap below - sendUpdates: "all" — David and other invitees receive email notification
- 10-minute popup reminder set on every event
- Google Meet link auto-generated via
conferenceData.createRequest
Known Gaps (v2 backlog)
| ID | Gap | Notes |
|---|---|---|
| GAP-01 | gws people returns 403 |
Need re-auth with contacts scope for name→email lookup outside team.json |
| GAP-06 | Conferencing provider hardcoded to Google Meet | Zoom, Meet, Gong Engage must be selectable. Zoom = personal URL in preferences or Zoom API OAuth. Gong = Gong Engage (outbound only). Google Calendar API only exposes hangoutsMeet — Zoom/Gong are add-ons, not API-native. |
| GAP-07 | ~~Organizer added to attendees~~ | ✅ Fixed 2026-04-15 — removed from skill template |
v2 Conferencing Plan
Arijit needs three providers (what his Google Calendar UI offers today): - Google Meet — works today ✓ - Zoom — personal link stored in preferences, or Zoom API for unique links - Gong — Gong Engage only (external/prospect meetings, not internal team calls)
Phase 1 — Personal URL approach (build next)
No new APIs. Config + skill change only.
// preferences.json target schema
{
"conferencing": {
"default": "zoom",
"providers": {
"zoom": { "personal_url": "https://algolia.zoom.us/j/XXXXXXXXXX" },
"meet": {},
"gong": { "personal_url": "https://app.gong.io/book/XXXXXXXXXX", "note": "external only" }
}
}
}
Skill change (Step 6):
1. Read preferences.conferencing.default
2. If unset → ask: "Zoom, Meet, or Gong?"
3. Zoom → add personal_url to event description + location (no conferenceData block)
4. Meet → keep hangoutsMeet conferenceData as-is
5. Gong → add personal_url to description, flag as external-meeting tool
Trade-off: Zoom personal link is same every meeting (no unique room per call).
Phase 2 — Zoom API (later)
One-time setup: create Zoom OAuth app → store client_id/secret/refresh_token.
At booking: POST /v2/users/me/meetings → unique link per meeting.
Only worthwhile when the "same link every time" limitation becomes a real problem.
Landscape Research Summary (2026-04-15)
19 projects surveyed. Key findings:
- No project matches our integrated loop — freebusy + slot ranking + ASCII visualization + booking loop is a unique combination
- Best MCP candidate: nspady/google-calendar-mcp (1,100 stars, MIT, TypeScript, clean deps, March 2026) — NOT adopting yet. gws is working; gaps are in UX not plumbing.
- gigamonkey/scheduler — closest in intent (Python, multi-party freebusy, CLI) but no LLM, no NLP, no visualization
- calcom/cal.com (41k stars) — booking pages, not ad-hoc mutual scheduling. Different problem.
- Security: Indirect prompt injection via calendar event content (CVSS 10, LayerX Feb 2026) applies to any calendar+LLM integration including ours. Anthropic declined to fix at platform level.
Session Log
| Date | What happened |
|---|---|
| 2026-04-15 | MVP v1 shipped. 42 tests passing. Skill installed. |
| 2026-04-15 | First live booking: 30 min with David Howden, Wed Apr 15 8–8:30 PM ET / Thu Apr 16 10–10:30 AM AEST |
| 2026-04-15 | Bug fixed: organizer removed from attendees array in skill template |
| 2026-04-15 | Landscape research: 19 projects surveyed, nspady/google-calendar-mcp evaluated, decision = keep gws |
| 2026-04-15 | v2 conferencing plan defined: Phase 1 (personal URLs) + Phase 2 (Zoom API). Phase 1 = next task. |