Calendar Integration
The Calendar Integration module enables proactive meeting preparation by fetching your Google Calendar events, matching attendees to your People notes, and generating context-rich pre-meeting briefs using your searchable knowledge base.
Prerequisites
- Foundation module completed (vault structure configured)
- Meeting Processing module completed (enriched notes with attendees)
- RAG Search module completed (semantic search available)
- Google account with Calendar access
- ~25 minutes to complete setup
What You'll Get
- Calendar Event Retrieval - Fetch today's meetings with attendees and metadata
- Attendee Matching - Map calendar emails to your People notes automatically
- Pre-Meeting Briefs - Generate context for each meeting from relevant past notes
- Daily Briefing - Consolidated view of today's schedule with action items and context
- Smart Filtering - Exclude declined events and working location markers
Google Calendar Setup
To access your Google Calendar programmatically, you need to create OAuth2 credentials in the Google Cloud Console.
Step 1: Create a Google Cloud Project
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google Calendar API:
- Navigate to "APIs & Services" → "Library"
- Search for "Google Calendar API"
- Click "Enable"
Step 2: Create OAuth2 Credentials
- Go to "APIs & Services" → "Credentials"
- Click "Create Credentials" → "OAuth client ID"
- Configure consent screen if prompted:
- User Type: External (or Internal if using Google Workspace)
- App name: "AI Executive Assistant" (or your preference)
- Add your email as a test user
- Application type: "Desktop app"
- Name: "Calendar Integration"
- Click "Create"
Step 3: Download Credentials
- Click the download icon next to your newly created OAuth client
- Save the JSON file as
~/.google/credentials.json - Create the directory if needed:
mkdir -p ~/.google
Security Note: Never commit credentials.json or token.json
to version control. These files contain sensitive authentication data.
OAuth Authentication
The first time you run a calendar script, it will open a browser window to authenticate with Google and request calendar read permissions.
Initial Authentication
cd ~/Documents/MyVault/scripts/calendar-events
./fetch_today_events.py This will:
- Open your default browser
- Prompt you to sign in to your Google account
- Ask permission to access your calendar (read-only)
- Save an authentication token to
~/.google/token.json
Token Refresh: The token automatically refreshes when expired. If you encounter
authentication errors, delete ~/.google/token.json and re-authenticate.
Fetching Events
The fetch_today_events.py script retrieves calendar events and outputs structured JSON
with event details and attendee information.
How It Works
- Authenticate - Uses OAuth2 token from
~/.google/token.json - Query Calendar - Fetches events for specified date range (defaults to today)
- Filter Events - Excludes:
- Working location markers (
eventType: "workingLocation") - Events you're not invited to
- Future events you've declined
- Working location markers (
- Extract Attendees - Collects emails of attendees who accepted or tentatively accepted
- Format Output - Returns JSON with dates, times, titles, attendees, and metadata
Usage Examples
# Fetch today's events
./fetch_today_events.py
# Fetch specific date
./fetch_today_events.py --date 2025-12-15
# Fetch custom date range
./fetch_today_events.py --start 2025-12-09 --end 2025-12-10
# Fetch with debug output
./fetch_today_events.py --debug Output Format
{
"date": "2025-12-09",
"total_events": 3,
"events": [
{
"date": "2025-12-09",
"start_time": "09:00:00",
"end_time": "10:00:00",
"title": "Staff Meeting",
"accepted_attendees": [
"alice@company.com",
"bob@company.com"
],
"event_id": "abc123...",
"location": "Conference Room A",
"description": "Weekly staff sync",
"organizer": "manager@company.com",
"status": "confirmed",
"html_link": "https://calendar.google.com/..."
}
]
} Attendee Matching
The system automatically matches calendar attendee emails to People notes in your vault, enabling context retrieval for meetings.
Matching Strategy
- Email Parsing - Extracts name components from email addresses:
alice.smith@company.com→ ["alice", "smith"]bob-jones@company.com→ ["bob", "jones"]
- Frontmatter Search - Looks for matches in People note frontmatter:
attendeesfield in processed noteslinksfield with[[@ First Last]]format
- Filename Pattern - Checks for one-on-one patterns:
MM-DD-YY - Alice / Erik.mdMM-DD-YY - Bob 1:1.md
- Fuzzy Matching - Uses difflib for title similarity when attendees overlap
People Note Format
For best matching results, ensure your People notes use this format:
---
title: Alice Smith
tags: [people, engineering]
attendees:
- Alice Smith
- alice
---
# @ Alice Smith
Notes about Alice... Meeting Briefs
Pre-meeting briefs combine calendar event data with relevant historical context from your knowledge base, surfacing action items and key discussion points.
How Briefs Are Generated
- Identify Event - Parse event title and attendees from calendar
- Search Context - Query Qdrant for semantically similar notes:
- Filter by matching attendees
- Filter by meeting/one-on-one type
- Filter by current quarter (plus previous quarter if within 2 weeks)
- Rank by semantic similarity score (threshold: 0.35)
- Filesystem Fallback - If no semantic matches, use heuristic scoring:
- Title similarity (40 points)
- Attendee overlap (30 points)
- Date recency (20 points)
- Meeting type priority (15 points for 1:1s)
- Keyword overlap (10 points)
- Minimum threshold: 15 points
- Extract Action Items - Find uncompleted tasks in format:
- [ ] @Owner — task description- Highlight items for today's attendees with ⭐
- Quote Context - Extract relevant snippets (first 500 chars) from matched notes
Relevance Thresholds
Configure matching sensitivity with environment variables:
# Semantic search minimum score (0.0-1.0)
export MIN_QDRANT_SCORE=0.35
# Filesystem fallback minimum score (0-100)
export MIN_FALLBACK_SCORE=15
# Title similarity when no attendees (0.0-1.0)
export TITLE_SIM_THRESHOLD_NO_ATTENDEES=0.4
# Maximum notes per event
export MAX_NOTES_PER_EVENT=3 Daily Briefing
The create_daily_briefing.py script generates a consolidated daily briefing file at
Dashboard/Daily Briefing.md with all of today's meetings and relevant context.
How It Works
- Read Calendar JSON - Accepts calendar data via stdin (designed for n8n integration)
- Retrieve Context - Searches for relevant notes for each event using the same matching logic as briefs
- Generate Briefing - Either:
- Deterministic Mode (default): Quote-only output with direct excerpts
- AI Mode: Uses Gemini 2.5 Pro to synthesize insights (requires Vertex AI auth)
- Write to Vault - Overwrites
Dashboard/Daily Briefing.mdwith timestamped content
Output Structure
# Daily Briefing - 2025-12-09
## Staff Meeting (09:00:00 - 10:00:00)
### [[12-02-25 - Staff Meeting]]
**Action Items (quote-only):**
> - [ ] @Erik — Review Q4 budget allocation ⭐
**Quoted Context:**
> Discussed AWS outage mitigation strategy.
> Team agreed to implement redundant failover.
## 1:1 with Alice (14:00:00 - 15:00:00)
### [[11-25-25 - Alice / Erik]]
**Action Items (quote-only):**
> - [ ] @Alice — Prepare architecture proposal
**Quoted Context:**
> Alice is working on the new microservices design.
---
*Generated at 2025-12-09 06:00:15* Mode Configuration
# Use deterministic quote-only mode (default, no AI required)
export BRIEFING_DETERMINISTIC=1
# Use AI synthesis mode (requires Vertex AI auth)
export BRIEFING_DETERMINISTIC=0
# Disable Qdrant (filesystem-only matching)
export BRIEFING_DISABLE_QDRANT=1
# Enable debug output
export BRIEFING_DEBUG=1 Customizing Briefs
You can customize the briefing output by modifying the prompt template or adjusting scoring weights
in create_daily_briefing.py.
Prompt Template Location
The AI briefing prompt is defined in the create_briefing_prompt() function around
line 514. Key sections:
- Grounding Rules - Instructions for quote-only output
- Calendar Events - Today's schedule with attendees
- Relevant Excerpts - Context snippets with scores
Scoring Weight Adjustments
Modify the calculate_relevance_score() function around line 273 to adjust weights:
# Title similarity (default: 40 points)
score += title_similarity * 40
# Attendee overlap (default: 30 points)
score += attendee_score * 30
# Date recency (default: 20 points)
score += recency_score * 20
# Meeting type (default: 15 for 1:1s, 10 for team)
if 'one-on-one' in category:
score += 15 Adding Custom Filters
To filter events by category, location, or other metadata, modify the get_relevant_meeting_notes()
function around line 375. Add Qdrant filters or filesystem conditions as needed.
Running Manually
While designed for automation, you can run the calendar scripts manually for testing or one-off briefs.
Fetch Today's Events
cd ~/Documents/MyVault/scripts/calendar-events
./fetch_today_events.py > /tmp/today_events.json
cat /tmp/today_events.json Generate Daily Briefing
cd ~/Documents/MyVault/scripts
./fetch_today_events.py | ./create_daily_briefing.py This will:
- Fetch today's calendar events
- Pipe the JSON to the briefing script
- Generate
Dashboard/Daily Briefing.md - Print "Daily briefing generation completed successfully."
Fetch Specific Date Range
# Fetch this week's events
./fetch_today_events.py --start 2025-12-09 --end 2025-12-13
# Fetch tomorrow's events
./fetch_today_events.py --date 2025-12-10 Verify It Works
Step 1: Authenticate with Google
cd ~/Documents/MyVault/scripts/calendar-events
./fetch_today_events.py Expected output:
- Browser opens for OAuth consent
- After authorization, token saved to
~/.google/token.json - JSON output with today's events printed to terminal
Step 2: Test Event Fetching
./fetch_today_events.py --debug Expected debug output:
[debug] user=you@company.com calendar=primary
[debug] timeMin=2025-12-09T00:00:00Z timeMax=2025-12-09T23:59:59Z Followed by JSON event data.
Step 3: Generate Daily Briefing
cd ~/Documents/MyVault/scripts
export BRIEFING_DETERMINISTIC=1
export BRIEFING_DEBUG=1
./fetch_today_events.py | ./create_daily_briefing.py Expected output:
Processing 3 calendar events...
Found relevant notes for 2 events
Generating briefing content with AI...
Daily briefing created: /path/to/vault/Dashboard/Daily Briefing.md
Daily briefing generation completed successfully. Step 4: Verify Briefing File
cat Dashboard/Daily\ Briefing.md You should see a markdown file with:
- Today's date in the title
- Section for each calendar event
- Relevant past meeting notes linked
- Action items extracted
- Context quotes from matched notes
- Timestamp at bottom
Troubleshooting
OAuth authentication failed
Symptom: "Credentials file not found" or "Invalid credentials"
Solution: Ensure ~/.google/credentials.json exists and contains valid OAuth2 client credentials.
Re-download from Google Cloud Console if needed.
No events found
Symptom: "total_events": 0 even though you have meetings
Possible causes:
- Wrong calendar ID (default is "primary")
- All events are working location markers (filtered out)
- You declined all events and they're in the future
- Check with
--debugflag to see query parameters
Context missing from briefing
Symptom: "No relevant quotes found" for events that should have context
Possible causes:
- Qdrant not running - Start Qdrant or enable
BRIEFING_DISABLE_QDRANT=1 - Notes not embedded - Run
embed_to_qdrant.pyon your Meetings folder - Threshold too high - Lower
MIN_QDRANT_SCOREorMIN_FALLBACK_SCORE - Attendee mismatch - Verify frontmatter
attendeesfield matches calendar emails - Wrong quarter - Notes outside current/previous quarter are excluded
Permission denied errors
Symptom: "Insufficient permissions" when accessing calendar
Solution: Delete ~/.google/token.json and re-authenticate. Ensure the OAuth consent
screen includes the calendar.readonly scope.
Vertex AI authentication errors (AI mode)
Symptom: "Could not automatically determine credentials" when generating briefing
Solution: Set required environment variables:
export GOOGLE_CLOUD_PROJECT=your-project-id
export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=service-account@project.iam.gserviceaccount.com
export CLOUDSDK_ACTIVE_CONFIG_NAME=default
Or use deterministic mode: export BRIEFING_DETERMINISTIC=1
Next Steps
Now that you have calendar integration working, continue to Automation to set up n8n workflows that run these scripts automatically every morning and generate briefings without manual intervention.