Skip to content

Repository files navigation

Resilient Multi-Agent Scheduling Engine

A fault-tolerant calendar coordination system built on CrewAI — uses a sweep-line interval algorithm and custom Interval Tree to find optimal meeting windows across 5+ global timezones, with exponential-backoff resilience and zero-dependency mock execution.

Python Framework License Tests LLM


What This Is

Scheduling a meeting across 5+ global time zones is a constraint-satisfaction problem that most tools fail at: when no standard 9–5 windows overlap, they return nothing. This project solves it with an autonomous 3-agent pipeline.

Three specialized CrewAI agents work sequentially:

  1. Profile Manager — maps human-readable locations ("London, UK") to IANA timezone identifiers
  2. Availability Checker — fetches calendar data concurrently via asyncio, with exponential-backoff retries and graceful degradation for offline participants
  3. Optimization Coordinator — runs a sweep-line interval scan backed by a custom BST-based Interval Tree to find the minimum-disruption meeting window, with a penalty-scored compromise mode when no clean overlap exists

The system runs fully offline with a --mode mock flag — no API key required. A --mode real path integrates live Google Calendar OAuth and pluggable LLM backends (OpenAI, Gemini, Claude).


Demo:

image

image

Architecture

sequenceDiagram
    autonumber
    actor CLI as User (main.py)
    participant PM as Profile Manager Agent
    participant AC as Availability Checker Agent
    participant HC as Hybrid Calendar Client
    participant OC as Optimization Coordinator Agent
    participant DB as calendars_mock.json

    CLI->>PM: Input: names + locations
    PM->>PM: GetParticipantTimezoneTool (IANA lookup)
    PM->>AC: Resolved timezone map (JSON)

    rect rgb(200, 220, 240)
        Note over AC, HC: Concurrency Layer — asyncio.gather
        AC->>HC: Parallel availability queries
        alt credentials.json exists
            HC->>HC: OAuth Desktop Flow
            HC->>HC: Google Calendar API (freebusy)
        else No credentials
            HC->>DB: Read calendars_mock.json
        end
        HC-->>AC: Availability payload (per participant)
    end

    AC->>OC: Compiled timezones + busy slots (JSON)
    OC->>OC: CalculateMeetingWindowTool
    Note over OC: Sweep-Line + IntervalTree overlap search
    OC-->>CLI: Localized schedule report + compromise rationale

Key design decision — MockLLM: Rather than mocking at the HTTP layer, a custom BaseLLM subclass intercepts CrewAI's prompt strings, identifies the active agent by role, executes tools programmatically, and returns formatted ReAct-style thoughts that advance the Crew state machine. This lets the full sequential pipeline run end-to-end without any API key.


Engineering Highlights

Sweep-Line Interval Search (O(M) vs O(N))

The naive approach divides the 24-hour day into 96 grid slots of 15 minutes and evaluates each one. This runs in O(N) where N = 96 per participant, and rounds all event boundaries to 15-minute increments.

This implementation collects only the critical boundary points across all participants — the start/end of every busy block, every working-hour boundary, and every sleep boundary — then evaluates only those candidate start times. For a team with M total calendar events, the search evaluates O(M) candidates at minute-level precision rather than 96 fixed slices.

# Collect only meaningful boundaries — not a 96-slot grid
critical_boundaries = set()
for slot in busy_slots:
    critical_boundaries.add(busy_start_utc)
    critical_boundaries.add(busy_end_utc)

# Evaluate each boundary as a candidate meeting start
for slot_start in sorted(critical_boundaries):
    ...

Custom Interval Tree (BST-backed, O(log K) average case overlap queries)

Rather than scanning all busy blocks linearly for each candidate start time, each participant's busy slots are loaded into a hand-rolled binary search tree IntervalTree (in interval_tree.py). The tree maintains a max subtree high-endpoint at each node to prune branches early during overlap queries.

overlap_search([slot_start, slot_end]):
  - If left.max ≤ slot_start: prune entire left subtree
  - If root.low ≥ slot_end: no overlap possible in right subtree
  - Result: O(log K + R) where K = events, R = results returned

This is particularly important in the compromise-scoring path, where hundreds of candidate slots are each evaluated against every participant's event tree.

Exponential Backoff with Jitter

Calendar API fetches use an async retry wrapper with randomized jitter to prevent thundering-herd behavior on shared infrastructure:

delay = initial_delay × (factor ^ attempt) + random(0, 1.0)

Parameters: initial_delay=1.0s, factor=2.0, max_retries=3. A SIMULATE_TRANSIENT_FAILURE environment flag triggers this path for testing.

Graceful Degradation

If a participant's calendar fetch fails after all retries, the scheduler excludes them from the overlap calculation, flags them in the output, and continues planning for the remaining group. The final report marks failed participants explicitly rather than silently dropping them.

Penalty-Based Compromise Mode

When no zero-penalty slot exists (i.e. no window falls within working hours for all participants), the system selects the minimum-disruption slot using a weighted penalty model:

Condition Penalty
Within working hours, no conflicts 0
Outside working hours (shoulder) 15
Sleep hours (10 PM – 7 AM local) 100
High-priority calendar conflict 1000
Low-priority calendar conflict 50

The output includes the disruption score and a per-participant status breakdown, so callers understand which trade-offs were made.


Project Structure

├── main.py               # CLI entry point; MockLLM implementation; mode switching
├── agents.py             # CrewAI agent definitions (Profile Manager, Availability Checker, Coordinator)
├── tasks.py              # CrewAI task configurations and context passing
├── tools.py              # All custom tools: timezone lookup, async calendar fetch, sweep-line solver
├── interval_tree.py      # Custom BST-backed IntervalTree data structure
├── calendars_mock.json   # Offline calendar dataset (5 participants, 5 timezones)
├── requirements.txt      # Dependencies
├── .github/workflows/    # CI configuration
└── tests/
    └── test_scheduler.py # Unit tests: sweep-line math, retry logic, IntervalTree behavior

Quickstart

Requirements: Python 3.11+, Git

# 1. Clone and set up environment
git clone https://github.com/imohitseth/Resilient-Multi-Agent-Orchestration.git
cd Resilient-Multi-Agent-Orchestration
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

# 2. Install dependencies
pip install -r requirements.txt

# 3. Run the test suite
python -m unittest tests/test_scheduler.py

# 4. Run in mock mode (no API key required)
export PYTHONIOENCODING=utf-8    # Windows: $env:PYTHONIOENCODING="utf-8"
python main.py --mode mock --date 2026-06-24 --duration 45

Expected output: a full localized schedule report for 5 participants (Alice/NYC, Bob/London, Charlie/Tokyo, David/Sydney, Eve/Bangalore), showing the optimal UTC window and each participant's local equivalent.


Live Google Calendar Mode

To query real calendars instead of the mock dataset:

  1. In Google Cloud Console, enable the Google Calendar API and configure an OAuth Consent Screen (add your email as a test user).
  2. Create Desktop Application OAuth Credentials, download the JSON, and rename it credentials.json in the project root.
  3. Set an LLM key in .env:
    OPENAI_API_KEY=sk-...
    # or GEMINI_API_KEY / ANTHROPIC_API_KEY
    
  4. Run:
    python main.py --mode real --date 2026-06-24 --duration 45
    A browser tab will open on first run for Google OAuth. Credentials are cached to token.json.

The system auto-detects which participant matches the authenticated Google account by comparing calendar timezone metadata, then falls back to mock data for all others.


Environment Variables

Variable Description
OPENAI_API_KEY OpenAI key for real-mode LLM
GEMINI_API_KEY Gemini key for real-mode LLM
ANTHROPIC_API_KEY Claude key for real-mode LLM
OTEL_SDK_DISABLED Set true to suppress OpenTelemetry network hooks
CREWAI_TELEMETRY_OPT_OUT Set true to disable CrewAI analytics
SIMULATE_TRANSIENT_FAILURE Set true to trigger backoff retry path for testing
PRIMARY_USER Override auto-detected Google Calendar user match

Engineering Challenges Solved

Pydantic validation conflict with CrewAI: CrewAI's Agent class wraps LLMs in dynamic Pydantic validators at init time. Subclassing LangChain's LLM base caused type-identity failures inside those validators. The fix was subclassing crewai.llms.base_llm.BaseLLM directly, which preserves the class identity CrewAI's validators check for.

DST-aware timezone conversion: Hardcoding UTC offsets (e.g. EST = UTC-5) produces wrong results for dates in summer when DST is active. All conversions use pytz.timezone.localize() to attach timezone context to naive datetimes before converting, ensuring correctness on any target date regardless of DST state.

Context passing between CrewAI sequential tasks: Each task's output is a raw string. The next agent's prompt injects that string as context — but the Optimization Coordinator needs structured JSON from the Availability Checker. Custom extract_participant_data() and extract_compilation_json() parsers in main.py recover structured data from the prompt string when the pipeline runs in mock mode, with fallbacks to the mock JSON file.


Roadmap

  • Service Account auth: Migrate from Desktop OAuth to Google Cloud Service Account JWT flow for daemon-mode organization-wide calendar access without user interaction
  • Full Interval Tree balancing: Add AVL/Red-Black rebalancing to maintain O(log K) worst-case query performance as event count grows
  • REST API layer: Wrap the scheduling engine in a FastAPI endpoint so it can be called from Slack bots, calendar integrations, or web frontends
  • Multi-day search: Extend the sweep across multiple days when no acceptable slot exists on the requested date
  • Participant preference weights: Allow per-user priority weights so a senior participant's sleep hours carry higher penalty than a junior's

About

Async multi-agent meeting scheduler using CrewAI, sweep-line interval math, and a custom Interval Tree — runs offline with zero API keys

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages