---
title: "Building a Calendar Management Agent: Scheduling, Rescheduling, and Conflict Resolution"
description: "Build an AI-powered calendar management agent that checks availability across time zones, resolves scheduling conflicts, and handles rescheduling workflows using the Google Calendar API."
canonical: https://callsphere.ai/blog/building-calendar-management-agent-scheduling-conflicts
category: "Learn Agentic AI"
tags: ["Calendar Automation", "AI Agents", "Google Calendar API", "Scheduling", "Workflow Automation", "Python"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-06T01:02:45.586Z
---

# Building a Calendar Management Agent: Scheduling, Rescheduling, and Conflict Resolution

> Build an AI-powered calendar management agent that checks availability across time zones, resolves scheduling conflicts, and handles rescheduling workflows using the Google Calendar API.

## The Complexity Behind Simple Scheduling

Scheduling a meeting between three people across two time zones sounds trivial until you account for existing commitments, buffer times between meetings, lunch blocks, focus time preferences, and the fact that one participant is in Tokyo while another is in New York. Calendar management agents handle this complexity by querying availability, proposing optimal slots, and resolving conflicts automatically.

In this guide, we build a calendar management agent that integrates with Google Calendar, checks availability across multiple attendees, handles timezone conversions, and uses an LLM to negotiate scheduling conflicts.

## Authenticating with Google Calendar

The Google Calendar API uses OAuth2. For a service agent that manages calendars on behalf of users, a service account with domain-wide delegation is the cleanest approach:

```mermaid
flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus
classify"]
    PLAN["Plan and tool
selection"]
    AGENT["Agent loop
LLM plus tools"]
    GUARD{"Guardrails
and policy"}
    EXEC["Execute and
verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus
next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
```

```python
from google.oauth2 import service_account
from googleapiclient.discovery import build
from datetime import datetime, timedelta
import pytz

SCOPES = ["https://www.googleapis.com/auth/calendar"]

def get_calendar_service(user_email: str):
    """Get a Calendar API service delegated to a specific user."""
    credentials = service_account.Credentials.from_service_account_file(
        "service-account.json",
        scopes=SCOPES,
    )
    delegated = credentials.with_subject(user_email)
    return build("calendar", "v3", credentials=delegated)
```

With domain-wide delegation, the agent can read and write calendars for any user in the organization without individual OAuth flows.

## Checking Availability with FreeBusy

The FreeBusy API is the correct way to check availability. It returns busy blocks for multiple calendars in a single call, which is far more efficient than fetching all events:

```python
from dataclasses import dataclass

@dataclass
class TimeSlot:
    start: datetime
    end: datetime

def get_busy_times(
    service, emails: list[str], start: datetime, end: datetime
) -> dict[str, list[TimeSlot]]:
    """Query free/busy information for multiple users."""
    body = {
        "timeMin": start.isoformat(),
        "timeMax": end.isoformat(),
        "items": [{"id": email} for email in emails],
    }
    result = service.freebusy().query(body=body).execute()

    busy = {}
    for email in emails:
        calendar_busy = result["calendars"].get(email, {}).get("busy", [])
        busy[email] = [
            TimeSlot(
                start=datetime.fromisoformat(b["start"]),
                end=datetime.fromisoformat(b["end"]),
            )
            for b in calendar_busy
        ]
    return busy
```

## Finding Common Available Slots

With busy times for all attendees, the agent computes overlapping free windows. This interval-based approach merges all busy blocks and finds gaps:

```python
def find_available_slots(
    busy_times: dict[str, list[TimeSlot]],
    search_start: datetime,
    search_end: datetime,
    duration_minutes: int = 30,
    working_hours: tuple[int, int] = (9, 17),
) -> list[TimeSlot]:
    """Find common available slots across all attendees."""
    # Merge all busy times into a single sorted list
    all_busy = []
    for blocks in busy_times.values():
        all_busy.extend(blocks)
    all_busy.sort(key=lambda s: s.start)

    # Merge overlapping busy blocks
    merged = []
    for block in all_busy:
        if merged and block.start = min_duration:
            # Check working hours constraint
            if cursor.hour >= working_hours[0] and block.start.hour = min_duration:
        available.append(TimeSlot(cursor, search_end))

    return available
```

## Handling Timezone Conversions

Time zones are the most common source of scheduling bugs. The agent normalizes everything to UTC internally and converts to local time only for display:

```python
def normalize_to_utc(dt: datetime, timezone_str: str) -> datetime:
    """Convert a local datetime to UTC."""
    local_tz = pytz.timezone(timezone_str)
    if dt.tzinfo is None:
        dt = local_tz.localize(dt)
    return dt.astimezone(pytz.utc)

def display_in_timezone(dt: datetime, timezone_str: str) -> str:
    """Format a UTC datetime for display in a local timezone."""
    local_tz = pytz.timezone(timezone_str)
    local_dt = dt.astimezone(local_tz)
    return local_dt.strftime("%A, %B %d at %I:%M %p %Z")

# Example: attendees in different zones
attendees = {
    "alice@company.com": "America/New_York",
    "bob@company.com": "Asia/Tokyo",
    "carol@company.com": "Europe/London",
}
```

## Conflict Resolution with LLM Reasoning

When no common slot exists, the agent uses an LLM to propose the best compromise. It considers factors like who has the most flexible schedule, meeting priority, and time zone fairness:

```python
from openai import OpenAI

client = OpenAI()

def resolve_conflict(
    attendees: dict[str, str],
    busy_times: dict[str, list[TimeSlot]],
    meeting_purpose: str,
) -> str:
    """Use LLM to suggest a conflict resolution strategy."""
    busy_summary = ""
    for email, blocks in busy_times.items():
        tz = attendees[email]
        times = [
            f"  {display_in_timezone(b.start, tz)} - {display_in_timezone(b.end, tz)}"
            for b in blocks
        ]
        busy_summary += f"{email} ({tz}):\n" + "\n".join(times) + "\n\n"

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": (
                    "You are a scheduling assistant. When no common free slot exists, "
                    "propose the best compromise. Consider: meeting urgency, time zone "
                    "fairness (avoid repeatedly burdening the same timezone), and which "
                    "attendees are optional vs required."
                ),
            },
            {
                "role": "user",
                "content": (
                    f"Meeting purpose: {meeting_purpose}\n\n"
                    f"Attendee busy times:\n{busy_summary}\n"
                    "Suggest the best scheduling approach."
                ),
            },
        ],
    )
    return response.choices[0].message.content
```

## Creating and Rescheduling Events

Once a slot is confirmed, the agent creates or updates the calendar event:

```python
def create_event(
    service, summary: str, start: datetime, end: datetime,
    attendee_emails: list[str], description: str = "",
) -> str:
    """Create a calendar event with attendees."""
    event = {
        "summary": summary,
        "description": description,
        "start": {"dateTime": start.isoformat(), "timeZone": "UTC"},
        "end": {"dateTime": end.isoformat(), "timeZone": "UTC"},
        "attendees": [{"email": e} for e in attendee_emails],
        "reminders": {"useDefault": True},
    }
    result = service.events().insert(
        calendarId="primary", body=event, sendUpdates="all"
    ).execute()
    return result["id"]
```

## FAQ

### How do I handle recurring meetings that need rescheduling?

Use the `recurringEventId` field to identify event series. To reschedule a single occurrence, modify that instance only. To reschedule the entire series, update the parent event. The FreeBusy API automatically accounts for recurring events when checking availability.

### What about buffer time between meetings?

Add a configurable buffer (typically 10-15 minutes) by extending each busy block's end time before computing available slots. This prevents back-to-back meetings and gives attendees transition time.

### How do I respect "Do Not Disturb" or focus time blocks?

Query each user's calendar for events marked as "outOfOffice" or with specific keywords like "Focus Time." Treat these as busy blocks during availability computation. Google Calendar's working hours settings can also be fetched via the Settings API and applied as constraints.

---

#CalendarAutomation #AIAgents #GoogleCalendarAPI #Scheduling #WorkflowAutomation #Python #AgenticAI #LearnAI #AIEngineering

---

Source: https://callsphere.ai/blog/building-calendar-management-agent-scheduling-conflicts
