Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Schedule a small meeting end-to-end: resolve attendee emails, check the owner's calendar for the slot, dedup-check, then create + email the Google Calendar invite. The mechanical core only — cross-person availability negotiation stays interactive.
.claude/skills/sonichi-meeting-scheduler/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 224% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 22% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 14% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 31% | 0% |
Encapsulates the ~10 manual steps of "set up a quick meeting" into one command: resolve attendee emails → check the owner's calendar for the slot → dedup-check → create the event + send invites → report the link.
Usage: /meeting-scheduler [what to schedule]
ARGUMENTS: $ARGUMENTS
This skill handles the mechanical core only. It does NOT negotiate availability across people — proposing times, collecting "does 3pm work?", juggling everyone's free/busy stays an interactive conversation you have with the owner (and, if asked, the attendees). Once a concrete slot is chosen, this skill does the rote work.
Authority note. Creating and sending a calendar invite is an owner action. Run the create+send step only when the owner has invoked this skill for a concrete meeting. Never auto-send speculatively — default to a dry-run and show the plan first. The helper enforces this: it defaults to --dry-run and only creates with an explicit --send.
| Input | Required | Notes | |---|---|---| | title | yes | Event summary, e.g. "Sutando sync" | | attendees | yes | Either explicit emails (--attendees) or names to resolve (--resolve) | | when | yes | Start time — ISO 8601 (2026-07-25T15:00) or today/tomorrow HH[:MM][am/pm] | | duration | no | Minutes; default 30 | | location | no | Room, address, or a video link | | description | no | Agenda / notes |
Emails must come from a Gmail lookup — the identity map (workspace/.claude-sutando/projects/<slug>/memory/reference_identity_map.md) resolves a name to a person (and their Discord/GitHub ids) but does not store email addresses. Use the map to disambiguate who is meant; get the address from Gmail.
Everything below is done by scripts/schedule_meeting.py, which shells out to the gws (Google Workspace) CLI. You can run it directly, or perform the steps by hand with the gws commands shown.
For each name, search recent mail and read From/To headers to extract the address:
bashgws gmail users messages list --params '{"userId":"me","q":"\"Alice\" newer_than:365d","maxResults":10}' # then, per hit id: gws gmail users messages get --params '{"userId":"me","id":"<id>","format":"metadata","metadataHeaders":["From","To","Cc"]}'
Strip any line containing keyring from gws output before parsing JSON. For the ag2.ai account, set GOOGLE_WORKSPACE_CLI_CONFIG_DIR=$HOME/.config/gws-ag2 (the helper's --account ag2 does this). If a name is ambiguous, confirm with the owner rather than guessing.
Read the owner's events across that day and flag anything whose busy time overlaps [start, end) (back-to-back is not a conflict; transparency:transparent / all-day events don't block):
bashgws calendar events list --params '{"calendarId":"primary","timeMin":"2026-07-25T00:00:00-07:00","timeMax":"2026-07-26T00:00:00-07:00","singleEvents":true,"orderBy":"startTime"}'
From the same day's events, refuse if one already has the same title (case/space-insensitive, ignoring cancelled ones) — prevents a double-booked duplicate invite.
Only on explicit owner action (--send). sendUpdates=all makes Google email the invitations:
bashgws calendar events insert \ --params '{"calendarId":"primary","sendUpdates":"all"}' \ --json '{"summary":"Sutando sync","location":"Meet link","description":"...","start":{"dateTime":"2026-07-25T15:00:00","timeZone":"America/Los_Angeles"},"end":{"dateTime":"2026-07-25T15:30:00","timeZone":"America/Los_Angeles"},"attendees":[{"email":"a@x.com"},{"email":"b@y.com"}]}'
Print the htmlLink from the insert response back to the owner.
bash# Dry-run (default) — resolve, check, dedup, report. NO changes: python3 skills/meeting-scheduler/scripts/schedule_meeting.py \ --title "Sutando sync" --when 2026-07-25T15:00 --duration-min 30 \ --resolve "Alice, Bob" # Create + email invites (owner action): python3 skills/meeting-scheduler/scripts/schedule_meeting.py \ --title "Sutando sync" --when 2026-07-25T15:00 --duration-min 30 \ --attendees "a@x.com,b@y.com" --location "https://meet.google.com/xxx" --send # Override a detected conflict/duplicate (owner's explicit call): # ... --send --force
Flags: --title --when --duration-min --attendees --resolve --location --description --calendar (default primary) --timezone --account {default,ag2} --gmail-lookback --send/--dry-run --force --self-check --verbose.
Fail-safe behavior:
--dry-run — nothing is created or emailed without --send.--send, unless --force is also given (the owner's explicit override).
The pure logic (when-parsing, conflict overlap, dedup, email-pick) is unit-tested without the network:
bashpython3 skills/meeting-scheduler/scripts/schedule_meeting.py --self-check python3 tests/meeting-scheduler.test.py
today/tomorrow HH[:MM][am/pm] only. For "next Tuesday afternoon" etc., resolve it to a concrete ISO time (the owner picks the slot) before calling the helper.
the interactive part this skill deliberately leaves out.
America/Los_Angeles);override with --timezone.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-15 | pass→pass | 7,855 | 2,090 | -73% | 1 | 1 | 0% | 1,270 | 1,987 | +56% | 0 | 0 | — |
case-01 | fail→fail | 7,224 | 6,147 | -15% | 1 | 1 | 0% | 1,282 | 2,042 | +59% | 0 | 0 | — |
case-02 | fail→fail | 17,274 | 7,000 | -59% | 1 | 1 | 0% | 1,177 | 2,201 | +87% | 0 | 0 | — |
case-03 | pass→fail | 6,104 | 5,360 | -12% | 1 | 1 | 0% | 1,013 | 2,038 | +101% | 0 | 0 | — |
case-04 | pass→pass | 9,069 | 10,696 | +18% | 1 | 1 | 0% | 1,679 | 3,542 | +111% | 0 | 0 | — |
case-05 | fail→fail | 9,583 | 6,772 | -29% | 1 | 1 | 0% | 1,877 | 1,972 | +5% | 0 | 0 | — |
case-06 | fail→pass | 5,093 | 3,322 | -35% | 1 | 1 | 0% | 698 | 2,261 | +224% | 0 | 0 | — |
case-07 | fail→pass | 11,065 | 4,174 | -62% | 1 | 1 | 0% | 2,003 | 2,438 | +22% | 0 | 0 | — |
case-08 | fail→fail | 5,026 | 9,288 | +85% | 1 | 1 | 0% | 801 | 2,273 | +184% | 0 | 0 | — |
case-09 | fail→pass | 16,044 | 1,501 | -91% | 1 | 1 | 0% | 1,310 | 1,887 | +44% | 0 | 0 | — |
case-10 | fail→pass | 13,973 | 5,466 | -61% | 1 | 1 | 0% | 2,269 | 2,586 | +14% | 0 | 0 | — |
case-11 | fail→pass | 9,867 | 2,886 | -71% | 1 | 1 | 0% | 1,713 | 2,242 | +31% | 0 | 0 | — |
case-12 | pass→pass | 6,741 | 3,173 | -53% | 1 | 1 | 0% | 1,072 | 2,153 | +101% | 0 | 0 | — |
case-13 | pass→pass | 5,376 | 2,483 | -54% | 1 | 1 | 0% | 868 | 2,094 | +141% | 0 | 0 | — |
case-14 | fail→pass | 6,090 | 2,978 | -51% | 1 | 1 | 0% | 964 | 2,193 | +127% | 0 | 0 | — |
case-16 | pass→pass | 3,610 | 2,092 | -42% | 1 | 1 | 0% | 631 | 1,969 | +212% | 0 | 0 | — |
case-17 | fail→pass | 13,703 | 4,386 | -68% | 1 | 1 | 0% | 2,180 | 2,446 | +12% | 0 | 0 | — |
case-18 | pass→pass | 5,571 | 2,827 | -49% | 1 | 1 | 0% | 888 | 2,124 | +139% | 0 | 0 | — |
case-19 | fail→pass | 4,595 | 1,474 | -68% | 1 | 1 | 0% | 710 | 1,895 | +167% | 0 | 0 | — |
case-20 | pass→pass | 8,989 | 2,993 | -67% | 1 | 1 | 0% | 1,322 | 2,167 | +64% | 0 | 0 | — |
case-21 | pass→pass | 14,215 | 2,814 | -80% | 1 | 1 | 0% | 2,231 | 2,169 | -3% | 0 | 0 | — |
case-22 | fail→fail | 9,893 | 1,300 | -87% | 1 | 1 | 0% | 1,511 | 1,873 | +24% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 17 counted toward the lift figure. The other 5 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 17 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.