calendar-scheduling
Schedules calendar events based on user instructions. Uses configurable event-type instruction files to handle different kinds of events (interviews, team meetings, 1:1s, etc.) with type-specific workflows, required info, attendee management, and post-scheduling actions. Use this skill whenever the user wants to schedule, plan, or book a calendar event, meeting, interview, appointment, or any time-based commitment. Also trigger when the user mentions finding availability, coordinating schedules, sending calendar invites, or setting up recurring meetings. Even if the user just says something casual like "set up a call with Jane next week" or "find a time for the team to meet", this skill applies.
Calendar Scheduling
Schedule calendar events by following event-type-specific instruction files. This skill coordinates across Gmail (for context), Google Calendar (for reading availability), and a Google Apps Script web app (for creating, updating, and deleting calendar events, and for sending emails).
Overview
Different types of events have different requirements. An interview loop needs
candidate info, interviewer panels, room bookings, and a follow-up email to the
candidate. A weekly team sync just needs a recurring slot where everyone is free.
Rather than hard-coding every event type, this skill uses event instruction
files — markdown documents in an Event Instructions/ folder in the user's
working folder that define what information is needed, what events to create,
what constraints to respect, and what to do after scheduling. These files live
in the working folder (not inside the skill) so that users can add, edit, and
remove event types at any time without modifying the skill itself.
The flow works like this:
- Check that connectors and the Apps Script are working
- Identify which event type the user wants and load its instructions
- Gather all required information per the instructions
- Plan the event(s) — find times that work for all attendees
- Create the calendar events
- Verify everything was scheduled correctly
- Execute any post-scheduling actions (emails, notifications, etc.)
- Reflect on the run and log any issues or improvement ideas
Step 1 — Check connectors and configuration
Before doing anything else, confirm that the required connectors and configuration are in place.
Google Calendar connector
Try calling list_gcal_events with a simple query. If the tool is not available
or errors out, tell the user:
The Google Calendar connector isn't connected. Please add it from the connectors menu so I can read calendars and check availability.
Gmail connector
Try calling search_gmail_messages with a simple query like in:inbox. If not
available, tell the user:
The Gmail connector isn't connected. Please add it so I can read relevant emails for context when needed.
Gmail is helpful but not strictly required for all event types. If the event instructions don't reference email, you can proceed without it. But let the user know it's not connected in case they need it later.
Apps Script configuration
Read app-script-config.json from the user's working folder. This file must
contain apps_script_url and apps_script_secret. If the file is missing or
either field is empty, tell the user:
I need your Apps Script configuration to create calendar events and send emails. Please follow the setup guide in
references/apps-script-setup.mdto deploy the script and createapp-script-config.jsonin your working folder.
Then read and show the template from references/app-script-config-template.json
so they know what to fill in.
Health check (optional but recommended): If the config file exists, make a
GET request to the apps_script_url using a Python script via Bash (e.g.,
urllib.request.urlopen). You should see a JSON response containing
"status":"ok". If it fails, point the user to the troubleshooting section in
the setup guide.
Review available event types
Look for an Event Instructions/ folder in the user's working folder. List the
markdown files inside it — each one defines an event type. You don't need to
read their contents yet, just the file names. Tell the user what event types you
found, for example:
I see you have instructions for: Interview Loop, Weekly Team Sync, and 1:1 Meeting. What would you like to schedule?
If the folder doesn't exist or is empty, let the user know and offer to help
them create their first event instruction file using the template in
references/event-instructions-template.md. Create the Event Instructions/
folder in the working folder if it doesn't exist.
Do not proceed to Step 2 until the Google Calendar connector is confirmed working and you've scanned the available event types.
Step 2 — Identify the event type and load instructions
Based on the user's request, determine which event type they want to schedule. Match their request against the instruction files you found in Step 1.
If the match is clear — for example, the user says "schedule an interview"
and you have an Interview Loop.md file — read that instruction file and move
to Step 3.
If the match is ambiguous — the user's request could fit multiple event types, or the phrasing is vague — ask them to clarify:
I have instructions for [list event types]. Which type of event is this?
If no instruction file matches — the user wants to schedule something you
don't have instructions for. Help them write an instruction file using the
template. Read references/event-instructions-template.md, walk through each
section with the user, and save the completed file to the
Event Instructions/ folder in the user's working folder. This skill is
designed for complex or frequently scheduled events that benefit from
repeatable instructions. For simple one-off events, let the user know they
should create those manually in Google Calendar.
Once you have the instruction file loaded, proceed to Step 3.
Step 3 — Gather required information
The event instruction file defines what information is needed to schedule this type of event. It will have a "Required Information" section listing everything you need to collect.
Start with what the user already told you. Their initial request often contains some of the needed info (dates, names, topics). Extract everything you can from their message first.
Then ask for anything that's missing. Group your questions logically rather than asking one at a time. For example:
To schedule this interview loop, I still need:
- The candidate's name and email
- Which interviewers should be on the panel
- Preferred date range
Can you provide those?
Check email for context if relevant. If the event instructions mention pulling information from email (common for interviews where a recruiter sends candidate details), use the Gmail connector to search for relevant messages. Ask the user what to search for if it's not obvious.
Apply defaults from the instructions. Many instruction files specify defaults (e.g., "default duration: 60 minutes", "default calendar: Interviews"). Use these without asking the user unless they conflict with something the user said.
Handle file attachments. If the user uploads files in the chat (or the event
instructions specify attachments), note them for inclusion in Step 5. The user
can drop files directly into the conversation at any point during scheduling.
When you reach Step 5, you'll read each uploaded file from /mnt/uploads/,
base64-encode it, and include it in the Apps Script payload. The Apps Script
uploads the file to a /Temp folder in the user's Google Drive and attaches it
to the calendar event. Let the user know their file will appear as a calendar
event attachment.
Don't move to Step 4 until you have all required information, or the user has explicitly said to skip optional fields.
Step 4 — Plan the event(s)
Now that you have all the information, plan what events need to be created. The event instruction file specifies the event structure — some event types create a single event, others create multiple (e.g., an interview loop might create separate events for each interviewer).
Check attendee availability
Unless the event instructions say otherwise (some events have fixed times that attendees must accommodate), check that all attendees are free:
- Use
list_gcal_eventsorfind_free_timeto check each attendee's calendar for the proposed time window. - If checking individual calendars isn't possible (you may only have access to the user's calendars), ask the user whether they can confirm attendee availability or whether you should just send the invite.
Handle scheduling conflicts
If attendees have conflicts at the proposed time:
-
Identify who has conflicts and when.
-
Look for alternative times within the date range specified in the instructions or by the user.
-
Present options to the user:
Jane is busy 2-3pm on Tuesday. Here are some alternatives where everyone is free:
- Tuesday 10-11am
- Wednesday 2-3pm
- Thursday 11am-12pm
Which works best?
If no conflict-free time exists within the constraints, explain the situation and ask the user how to proceed. They might want to override a conflict, extend the date range, or drop an optional attendee.
Respect scheduling constraints
The event instructions may include constraints like:
- Time windows (e.g., "only schedule between 9am and 5pm")
- Buffer time (e.g., "leave 15 minutes between back-to-back interviews")
- Day restrictions (e.g., "no meetings on Fridays")
- Timezone requirements (e.g., "schedule in the candidate's timezone")
Honor all constraints from the instructions. If a constraint makes scheduling impossible, explain why and ask the user to relax a constraint.
Present the plan
Before creating any events, present the full schedule to the user. Include:
- Event title(s)
- Date(s) and time(s) with timezone
- Duration
- Attendees for each event
- Calendar the event will be created on
- File attachments (if any — list file names)
- Any special notes (video link, location, etc.)
Approval behavior
Check the event instruction file for an Approval field. This field controls whether the user must confirm the plan before events are created:
required(default): Wait for the user to approve or suggest changes before proceeding to Step 5. If they have feedback, adjust the plan and present it again.skip: Proceed directly to Step 5 after presenting the plan. Do not wait for confirmation. This is useful for event types where the user trusts the process and wants things to move quickly (e.g., routine meetings with standard patterns).
If the instruction file doesn't include an Approval field, treat it as
required — always ask for confirmation by default. The user can also override
this in their scheduling request (e.g., "schedule it without asking me to
confirm").
Step 5 — Create the calendar events
With the finalized plan (approved by the user or auto-approved per the event instruction's Approval setting), create the events.
The Google Calendar connector is read-only — it can check availability but cannot create, update, or delete events. Use the Apps Script web app for all event mutations. Send a POST request to the Apps Script URL using a Python script executed via Bash.
The Apps Script accepts calendar actions in this format:
Create an event:
{
"secret": "<from app-script-config.json>",
"calendarActions": [
{
"action": "createEvent",
"calendarId": "primary",
"title": "Interview: Jane Smith — Technical Round",
"startTime": "2026-03-15T10:00:00-05:00",
"endTime": "2026-03-15T11:00:00-05:00",
"description": "Technical interview with Jane Smith",
"attendees": ["[email protected]", "[email protected]"],
"location": "Zoom (link in description)",
"reminders": [{"method": "popup", "minutes": 15}],
"showAs": "busy",
"colorId": "9",
"attachments": [
{
"fileName": "Interview Prep.pdf",
"mimeType": "application/pdf",
"base64Data": "<base64-encoded file contents>"
}
]
}
]
}
The showAs field accepts "free" or "busy" (default is "busy"). This
uses the Calendar Advanced Service — see the setup guide for how to enable it.
The colorId field is optional. If provided, it sets the event's color using
one of Google Calendar's 11 predefined colors. Pass a string from "1" to
"11":
| ID | Color | ID | Color |
|---|---|---|---|
"1" | Lavender | "7" | Peacock |
"2" | Sage | "8" | Graphite |
"3" | Grape | "9" | Blueberry |
"4" | Flamingo | "10" | Basil |
"5" | Banana | "11" | Tomato |
"6" | Tangerine |
Event instruction files specify color per-event as a Color field on each
event in the Event Structure section (e.g., - **Color**: Tomato). A value of
Default or the absence of a Color field means no colorId should be sent —
the event will use the calendar's default color. When a color name is present,
map it to the corresponding ID before including it in the payload.
The attachments field is optional. Each attachment needs fileName, mimeType,
and base64Data. The Apps Script decodes the file, uploads it to a /Temp
folder in Google Drive, and attaches it to the calendar event via the Calendar
Advanced Service. You can also pass fileUrl instead of base64Data to attach
an existing Google Drive file without re-uploading.
Encoding uploaded files for the payload:
When the user has uploaded files in the chat, read and base64-encode them before including them in the payload:
python3 -c "
import base64, mimetypes, os
path = '/mnt/uploads/Interview Prep.pdf'
name = os.path.basename(path)
mime = mimetypes.guess_type(path)[0] or 'application/octet-stream'
with open(path, 'rb') as f:
b64 = base64.b64encode(f.read()).decode('ascii')
print(name)
print(mime)
print(b64)
"
Then include the output as fileName, mimeType, and base64Data in the
attachments array. Keep the base64 encoding and the Apps Script POST in the
same Python script to avoid passing large strings between commands.
Update an event (only include fields you want to change):
{
"secret": "<from app-script-config.json>",
"calendarActions": [
{
"action": "updateEvent",
"calendarId": "primary",
"eventId": "[email protected]",
"title": "Updated Title",
"startTime": "2026-03-15T14:00:00-05:00",
"endTime": "2026-03-15T15:00:00-05:00"
}
]
}
Delete an event:
{
"secret": "<from app-script-config.json>",
"calendarActions": [
{
"action": "deleteEvent",
"calendarId": "primary",
"eventId": "[email protected]"
}
]
}
Sending the request:
Use the Bash tool to run a Python script that POSTs to the Apps Script URL.
Read the apps_script_url and apps_script_secret from the
app-script-config.json file loaded in Step 1.
python3 -c "
import urllib.request, json, ssl
url = '<apps_script_url>'
payload = json.dumps(<json_payload>).encode('utf-8')
ctx = ssl.create_default_context()
req = urllib.request.Request(url, data=payload, method='POST')
req.add_header('Content-Type', 'text/plain')
resp = urllib.request.urlopen(req, context=ctx, timeout=30)
print(resp.read().decode('utf-8'))
"
Why text/plain? Apps Script redirects from script.google.com to
script.googleusercontent.com. Using Content-Type: application/json can
trigger issues with the redirect. text/plain avoids this.
Parse the response
The Apps Script returns:
{
"success": true,
"processed": 2,
"results": [
{ "action": "createEvent", "status": "ok", "eventId": "abc123" },
{ "action": "createEvent", "status": "error", "error": "Calendar not found" }
],
"errors": []
}
If any events failed, report the specific failures and offer to retry or troubleshoot.
Step 6 — Verify the events
After creating events, verify they exist on the calendar.
-
Use
list_gcal_eventsto query the calendar for the time range you just scheduled. -
Confirm each planned event appears with the correct title, time, and attendees.
-
If anything is missing or wrong, attempt to fix it (recreate the event or update it).
-
Report the verification results to the user:
All 3 events verified on the Interviews calendar:
- Technical Round: March 15, 10:00-11:00 AM
- Behavioral Round: March 15, 11:15 AM-12:15 PM
- Hiring Manager Chat: March 15, 1:00-1:30 PM
Step 7 — Post-scheduling actions
Check the event instruction file for any additional actions to take after events are created. Common post-scheduling actions include:
- Sending a confirmation email to the candidate or attendees with the full schedule. Use the Apps Script's email functionality or Gmail for this.
- Updating a tracking document or spreadsheet with the scheduled details.
- Posting to Slack to notify a channel about the scheduled event.
- Creating reminder events or follow-up tasks.
Follow whatever the instruction file specifies. If it requires sending
an email, build the email content per the instructions and use the Apps Script
to send it (similar to how event creation works, but with an emailActions
payload).
If there are no post-scheduling actions defined, simply confirm to the user that everything is complete.
Step 8 — Reflect and log improvements
After every run, briefly review what happened and decide whether anything is worth recording. The goal is to capture issues and opportunities that the user would otherwise never see because the skill runs unattended or because the details are easy to forget between sessions.
When to write an entry
Add an entry to improvement-log.md in the user's working folder only when
one of these occurred during the run:
- Error: An Apps Script call failed, an event wasn't created, verification in Step 6 found missing or incorrect events, a retry was needed, or the health check returned something unexpected.
- Reliability concern: Something worked but felt fragile — e.g., a scheduling conflict was resolved but only one viable slot existed, or timezone handling required manual correction, or the Apps Script response was slow.
- Efficiency opportunity: You noticed a pattern that could be handled more cheaply — e.g., the same availability-check dance keeps happening for a group that always meets at the same time, or an event instruction file is missing a default that you keep having to ask about.
- Scheduling logic: A constraint from the event instructions was hard to satisfy, or the instructions were ambiguous about something you had to guess at, or you think a new constraint or default would prevent a recurring issue.
If the run was clean and unremarkable, skip this step entirely. The log should be high-signal, not a diary.
How to write an entry
Read the template at references/improvement-log-template.md for the exact
format. Each entry includes:
- Date and short title — e.g., "2026-03-10 — Timezone mismatch for remote candidate"
- Category — one of:
error,reliability,efficiency,scheduling-logic - Run summary — how many events were planned, created, and verified
- Context — which event instruction file was used, how many attendees, which calendar. This helps when reviewing the log later to understand what kind of run produced the issue.
- What happened — specific details: event titles, attendee conflicts, error messages, unexpected scheduling outcomes
- What was tried — if you took corrective action (retry, reschedule, skip), what you did and whether it worked
- Proposed fix — a concrete suggestion: an event instruction update, a SKILL.md change, an Apps Script fix, or an investigation the user should run
- Priority —
low,medium, orhigh
Append behavior
Add new entries at the top of the file (newest first), below the header.
If improvement-log.md doesn't exist yet, create it by copying the template
from references/improvement-log-template.md and then adding your entry.
Reference files
| File | Purpose |
|---|---|
references/apps-script-setup.md | Step-by-step guide for deploying the combined Gmail + Calendar Apps Script. Share if app-script-config.json is missing. |
references/app-script-config-template.json | Template for the config file. Share alongside the setup guide. |
references/event-instructions-template.md | Template for creating new event type instruction files. Share when the user wants to define a new event type. |
references/improvement-log-template.md | Format template for the improvement log. Copy this to create improvement-log.md in the working folder on first use. |
scripts/gmail-calendar-actions.gs | Google Apps Script source code handling both Gmail and Calendar operations. The user pastes this into their Apps Script project. |
User working folder files (not part of the skill package):
| File/Folder | Purpose |
|---|---|
Event Instructions/ | Folder in the user's working folder containing per-event-type instruction markdown files. Users create and edit these freely. |
app-script-config.json | User's Apps Script configuration with URL and secret. |