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

  1. Go to Google Cloud Console
  2. Create a new project or select an existing one
  3. Enable the Google Calendar API:
    • Navigate to "APIs & Services" → "Library"
    • Search for "Google Calendar API"
    • Click "Enable"

Step 2: Create OAuth2 Credentials

  1. Go to "APIs & Services" → "Credentials"
  2. Click "Create Credentials" → "OAuth client ID"
  3. 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
  4. Application type: "Desktop app"
  5. Name: "Calendar Integration"
  6. Click "Create"

Step 3: Download Credentials

  1. Click the download icon next to your newly created OAuth client
  2. Save the JSON file as ~/.google/credentials.json
  3. 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:

  1. Open your default browser
  2. Prompt you to sign in to your Google account
  3. Ask permission to access your calendar (read-only)
  4. 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

  1. Authenticate - Uses OAuth2 token from ~/.google/token.json
  2. Query Calendar - Fetches events for specified date range (defaults to today)
  3. Filter Events - Excludes:
    • Working location markers (eventType: "workingLocation")
    • Events you're not invited to
    • Future events you've declined
  4. Extract Attendees - Collects emails of attendees who accepted or tentatively accepted
  5. 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

  1. Email Parsing - Extracts name components from email addresses:
    • alice.smith@company.com → ["alice", "smith"]
    • bob-jones@company.com → ["bob", "jones"]
  2. Frontmatter Search - Looks for matches in People note frontmatter:
    • attendees field in processed notes
    • links field with [[@ First Last]] format
  3. Filename Pattern - Checks for one-on-one patterns:
    • MM-DD-YY - Alice / Erik.md
    • MM-DD-YY - Bob 1:1.md
  4. 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

  1. Identify Event - Parse event title and attendees from calendar
  2. 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)
  3. 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
  4. Extract Action Items - Find uncompleted tasks in format:
    • - [ ] @Owner — task description
    • Highlight items for today's attendees with ⭐
  5. 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

  1. Read Calendar JSON - Accepts calendar data via stdin (designed for n8n integration)
  2. Retrieve Context - Searches for relevant notes for each event using the same matching logic as briefs
  3. 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)
  4. Write to Vault - Overwrites Dashboard/Daily Briefing.md with 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:

  1. Fetch today's calendar events
  2. Pipe the JSON to the briefing script
  3. Generate Dashboard/Daily Briefing.md
  4. 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 --debug flag 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.py on your Meetings folder
  • Threshold too high - Lower MIN_QDRANT_SCORE or MIN_FALLBACK_SCORE
  • Attendee mismatch - Verify frontmatter attendees field 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.