C-suite executives spend an average of 4.1 hours per week on scheduling coordination. That's not a small number β it's a full half-day every week spent on back-and-forth emails to find a 30-minute slot.
The "solution" most people use is Calendly. But Calendly doesn't solve the problem β it just moves it. You still have to find the professional, send them your link, wait for them to pick a time, confirm it, add it to your calendar. And if something conflicts? Start over.
This tutorial shows a different approach: an AI Executive Assistant that initiates bookings on your behalf. You say what you need in plain English. The assistant does the rest.
How This Is Different From Calendly
Calendly is reactive β it gives people a link to book you.
This assistant is proactive β it books for you.
Calendly flow:
You β find professional β email them β share link β wait β confirm β calendar
Executive Assistant flow:
You β say what you need β done
The key difference is who initiates the booking. With Orita's scheduling API, your agent can check a professional's real-time availability and create a confirmed booking β without the professional having to do anything except show up.
Architecture
Your Natural Language Command
β
βΌ
OpenAI GPT-4o-mini
(understands intent,
chooses functions)
β
βΌ
Function Calls
β
βββββββ΄βββββββββββββββββββββββββββ
β β
βΌ βΌ
search_professionals find_best_slot
(find the right person) (Orita API β availability)
β
βΌ
book_appointment
(Orita API β confirmed)
β
βΌ
Confirmation back to you
Three components working together:
- OpenAI understands what you asked for and decides which functions to call
- Orita executes the scheduling (availability check + booking)
- The agent loop connects them and maintains conversation context
The Code
Defining functions for OpenAI
OpenAI's function calling works by giving the model a list of available functions with their signatures. The model decides when and how to call them.
Here are the five functions our assistant has access to:
FUNCTION_DEFINITIONS = [
{
"name": "search_professionals",
"description": "Search for professionals to meet with.",
"parameters": {
"properties": {
"professional_type": { "type": "string" },
"specialty": { "type": "string" }
},
"required": ["professional_type"]
}
},
{
"name": "find_best_slot",
"description": "Find the optimal available slot with a professional using Orita.",
"parameters": {
"properties": {
"provider_id": { "type": "string" },
"event_type_id": { "type": "string" },
"date_range": {
"type": "string",
"enum": ["today", "tomorrow", "this_week", "next_week"]
},
"preference": {
"type": "string",
"enum": ["morning", "afternoon", "evening", "earliest"]
}
},
"required": ["provider_id", "event_type_id"]
}
},
{
"name": "book_appointment",
"description": "Book a confirmed appointment via Orita. Sends calendar invite automatically.",
"parameters": { ... }
},
{
"name": "list_my_bookings",
"description": "List all upcoming bookings."
},
{
"name": "cancel_booking",
"description": "Cancel an existing booking by ID."
}
]
The description field is not cosmetic β GPT-4o reads it to decide which function to call. Write it like documentation.
The agent loop
The core loop runs until the model produces a final text response (no more function calls):
def run_agent_turn(messages: list, user_input: str) -> tuple[str, list]:
messages.append({"role": "user", "content": user_input})
while True:
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": SYSTEM_PROMPT}] + messages,
tools=[{"type": "function", "function": fn} for fn in FUNCTION_DEFINITIONS],
tool_choice="auto",
)
msg = response.choices[0].message
messages.append(msg_dict)
# No tool calls β done, return the text response
if not msg.tool_calls:
return msg.content, messages
# Execute all requested function calls
for tc in msg.tool_calls:
fn = FUNCTION_DISPATCH[tc.function.name]
result = fn(**json.loads(tc.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
# Loop continues β model reads tool results, reasons again
This loop is the key insight: the model reads the tool results, reasons about them, and either calls more tools or produces a final answer. A single user command might trigger 3-4 function calls in sequence before the model is satisfied.
Finding the best slot with Orita
The find_best_slot function translates human preferences ("next week, mornings") into actual calendar availability:
def find_best_slot(
provider_id: str,
event_type_id: str,
date_range: str = "next_week",
preference: str = "morning",
) -> dict:
# Map date_range to a start date
if date_range == "next_week":
days_until_monday = (7 - date.today().weekday()) % 7
start_date = date.today() + timedelta(days=days_until_monday or 7)
# Map preference to time window
pref_range = EXECUTIVE_PROFILE["preferences"][preference] # e.g. "08:00-12:00"
pref_start, pref_end = pref_range.split("-")
# Query Orita for each day until we find a matching slot
for offset in range(7):
check_date = start_date + timedelta(days=offset)
slots = orita.slots(
event_type_id=event_type_id,
date=check_date.isoformat(),
provider_id=provider_id,
)
# Filter slots that fall within the preferred time window
matching = [s for s in slots if pref_start <= s["value"] <= pref_end]
if matching:
return {"best_slot": {"date": check_date.isoformat(), "time": matching[0]["value"]}, ...}
This is the Orita advantage: orita.slots() returns real-time availability in a structured format your code can filter and compare β not a UI widget your user has to click through.
Booking with case notes
When the assistant books an appointment, it passes context into Orita's notes field:
def book_appointment(provider_id, event_type_id, date_str, time_str, professional_name, meeting_purpose):
booking = orita.book(
provider_id=provider_id,
event_type_id=event_type_id,
date=date_str,
time=time_str,
name=EXECUTIVE_PROFILE["name"],
email=EXECUTIVE_PROFILE["email"],
notes=f"Purpose: {meeting_purpose}",
)
return {
"success": True,
"booking_id": booking["id"],
"confirmed": {"with": professional_name, "date": date_str, "time": time_str},
}
The professional receives a calendar invite that already explains why the meeting was booked. No "what is this meeting for?" back-and-forth.
Demo: 3 Commands, 3 Bookings
Here's the assistant handling three different professionals in a single session:
Command 1: "Book me a meeting with my financial advisor next week, mornings preferred."
Assistant: Booked. β
β’ With: Dr. Patricia Hayes (Certified Financial Planner)
β’ When: 2026-08-03 at 09:00
β’ Ref: BK-2026-08-03-0900
Calendar invite sent to jordan.mitchell@example.com.
The model called search_professionals("financial_advisor"), then find_best_slot(provider_id, event_type_id, "next_week", "morning"), then book_appointment(...). Three function calls, one response.
Command 2: "Schedule a check-up with my doctor for tomorrow."
Assistant: Done. β
β’ With: Dr. Michael Chen (Primary Care Physician)
β’ When: 2026-07-30 at 09:00
β’ Ref: BK-2026-07-30-0900
Confirmation at jordan.mitchell@example.com.
Notice the model inferred "doctor" maps to search_professionals("doctor") without needing to be told.
Command 3: "Find me the earliest slot with a lawyer this week β it's urgent."
Assistant: Booked the earliest available. β
β’ With: Sarah Kim, Esq. (Corporate Attorney)
β’ When: 2026-07-29 at 10:00
β’ Ref: BK-2026-07-29-1000
Confirmation sent to jordan.mitchell@example.com.
The word "urgent" made the model pass preference="earliest" to find_best_slot. It didn't need explicit instruction β it read intent from context.
Total execution time for all three: < 15 seconds.
The Orita Advantage: Solve-Scheduling
Traditional scheduling APIs are passive β they return data. Orita's solve-scheduling endpoint is designed for agentic use: pass it constraints (professional, date range, time preferences) and it returns the optimal slot.
For an executive assistant, this matters enormously. The agent doesn't need to:
- Scrape a calendar UI
- Parse availability HTML
- Handle timezone conversions manually
- Deal with double-booking race conditions
orita.slots() returns structured, real-time availability. orita.book() creates an atomic, confirmed booking. That's the infrastructure an AI agent needs.
Conversation Memory
Because we pass the full messages array to each OpenAI call, the assistant maintains context across the session:
# The messages list grows with each turn
messages = []
# Turn 1: book financial advisor
reply1, messages = run_agent_turn(messages, "Book me with my financial advisor")
# Turn 2: the model knows about the financial advisor booking
reply2, messages = run_agent_turn(messages, "Reschedule that to the afternoon instead")
You can reference previous bookings by context ("reschedule that", "cancel the one with the doctor") and the model understands what "that" refers to because it has the full conversation history.
Extend This
Connect to your real calendar
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
def sync_to_google_calendar(booking: dict):
service = build("calendar", "v3", credentials=creds)
event = {
"summary": f"Meeting with {booking['professional']}",
"start": {"dateTime": f"{booking['date']}T{booking['time']}:00"},
"end": {"dateTime": f"{booking['date']}T{booking['end_time']}:00"},
}
service.events().insert(calendarId="primary", body=event).execute()
Call this inside book_appointment after the Orita booking is confirmed.
Priority scoring
If your executive has 5 pending appointments to schedule, which professional gets the first available slot?
PRIORITY_WEIGHTS = {
"doctor": 10, # Health first
"lawyer": 9, # Legal > financial
"financial_advisor": 7,
"accountant": 5,
"coach": 3,
}
def prioritize_bookings(pending: list[dict]) -> list[dict]:
return sorted(pending, key=lambda x: PRIORITY_WEIGHTS.get(x["type"], 0), reverse=True)
Email integration
Connect to Gmail or Outlook to auto-detect scheduling requests in the executive's inbox:
def process_email(email_body: str) -> str:
"""Feed an email to the assistant and book if it contains a scheduling request."""
if "schedule" in email_body.lower() or "meeting" in email_body.lower():
reply, _ = run_agent_turn([], email_body)
return reply
Clone & Run in 5 Steps
Prerequisites: Python 3.11+, OpenAI API key, Orita account
# 1. Clone the examples repo
git clone https://github.com/Alkilo-do/orita-python
cd orita-python
# 2. Install dependencies
pip install openai orita-sdk
# 3. Set your API keys
export OPENAI_API_KEY=sk-your-openai-key
export ORITA_API_KEY=orita_your-key
# 4. Run the demo (3 pre-scripted commands)
python examples/executive_assistant.py
# 5. Run in interactive mode
python examples/executive_assistant.py --interactive
The demo runs without requiring an OpenAI key β it uses a smart mock that simulates the function calling flow. With an OpenAI key, you get the real multi-step reasoning.
Next Steps
This example is a foundation. In production:
- Add authentication β the assistant should only have access to one executive's calendar and contacts
- Persist bookings β replace the in-memory store with a database
- Add webhooks β Orita fires events on booking changes; use them to update your calendar and notify the executive
- Embed in Slack/Teams β expose the agent as a slash command:
/book doctor tomorrow morning - Connect email β auto-detect scheduling requests in the inbox and handle them proactively
The function calling pattern makes this modular: add a function, add a description, the model learns to use it.
β Get your Orita API key and start building
Full source code: github.com/Alkilo-do/orita-python/examples/executive_assistant.py
Questions? Reach us at developers@orita.online
