Unmasking Motion AI Assistant: Under the Hood — AI Scheduling

Benny Friedman
6 min readOct 30, 2024

--

This is an entry from the Not Magic, Just Math newsletter, published 10/29/2024

This week, I thought I’d take a break from our deep dive into ML Typologies and try something new. I want to introduce a fresh “field note” series called Under the Hood, where we take an “AI software” and try to deduce how the system works — and how I would build it from scratch. To kick things off, let’s look at a tool I love and use daily: Motion.

🕰️ Meet Motion

Motion brands itself as your “AI assistant.” According to their website:

“Motion takes all of your projects and tasks, prioritizes and time-blocks them on your calendar, and dynamically optimizes your schedule dozens of times a day, all done automatically. Your plan will always be perfect and up-to-date.”

The primary “AI” component is its auto-scheduling and prioritization system. Here’s how it works:

  1. Set your working hours.
  2. Connect your events calendar (work calendar, for example).
  3. Define your current tasks, specifying:
  • Estimated time to complete.
  • Due date.

Once you’ve done this, Motion fits your tasks into open time slots in your calendar between meetings, all within your specified working hours. Neat, right?

Interested?

Check out the fully featured Motion app here: https://get.usemotion.com/34nmt4f5cg5x

🕵️‍♂️ Under the Hood: How Does Motion Work?

To figure out how Motion works, let’s start by categorizing it in terms of an ML typology.

🔍 Typology Check:

I’d describe the problem as follows:

“Given defined tasks with time, decomposition constraints, and due dates, place them on a timeline (schedule) that maximizes the number of tasks completed on time, without violating working window constraints or interfering with pre-existing events.”

Sounds like an optimization problem! Keywords like “maximize” and “constraint” are telltale signs.

🛠️ Solving the Problem:

Now that we know it’s an optimization problem, how might we approach it? In our optimization deep dive, we explored two methods:

I often use RL when the system can’t be fully described with known equations. But in this case, we can represent the constraints mathematically, making Mathematical Optimization our tool of choice.

If you’ve made it this far you might like other field notes from Not Magic, Just Math!

I write a new field note each week as a part of my weekly free newsletter “Not Magic, Just Math” our goal is to create a community of people interested in the practicalities of Data Science, ML and AI. We share insights, lessons learned, and tricks of the trade — both from my experience and those of other practitioners!

Join here: https://nmjm.beehiiv.com/subscribe

✏️ Representing Motion as a Mathematical Optimization:

To solve this as a Mathematical Optimization problem, we have a few options:

  • Linear Programming (LP)
  • Mixed Integer Programming (MIP)

For this problem, I’d use Mixed Integer Programming because we need boolean (yes/no) variables to represent task scheduling.

Let’s break it down simply:

  • We assume a 1-week schedule and represent it using 15-minute increments.
  • The system schedules tasks in available slots that align with working hours and pre-existing events.

📅 Setting Up the Data:

  1. A boolean list of work hours: True for working periods (e.g., 9–5, M–F), False otherwise.
  2. A boolean list of pre-existing events: True for available slots, except where events already exist.
  3. An initial availability list that’s True when within work hours and not blocked by other events.

🔨 Variables for Task Scheduling:

  • Task lists: One list per task, with boolean values for each time slot.
  • Tracking variables for whether a task is successfully scheduled.

📏 Defining Constraints:

We introduce constraints to ensure:

  1. Task Duration: Each task occupies consecutive time slots according to its duration.
  2. Task Scheduling & Completion: A task is considered scheduled when its required duration is met.
  3. No Overlap: Only one task can be scheduled per time slot.
  4. Within Working Hours: Tasks are only scheduled within defined work hours.
  5. No Conflict with Pre-Existing Events: Tasks cannot overlap with events on your calendar.

🎯 Objective Function:

The objective is to maximize the number of successfully scheduled tasks.

🖥 In Code:

To validate my approach, I developed a simple Motion clone using OR-Tools (Google’s mathematical optimization package). Here’s how I brought this to life:

1) Set up our initial information

HORIZON_WEEKS = 1
START_DATE = '2024-10-01'
WORKING_DAYS = "M-F" # Workdays: Monday to Friday
WORKING_HOURS = (9, 17) # Working hours: 9 AM to 5 PM
## Create our task definitions
tasks = {
'Complete Budget Draft': {'time_to_complete': 30, 'due_date': Timestamp('2024-10-02 11:00:00')},
...
}
# Create our event definitions
events = {
'Team Alignment Meeting': {'date_time': Timestamp('2024-10-01 09:00:00'), 'length': 60},
'Sales Review Session': {'date_time': Timestamp('2024-10-01 15:00:00'), 'length': 90},
...
}

2) Set up our schedule dataset

#Calculate the end date based on the scheduling horizon in weeks
end_date = pd.to_datetime(START_DATE) + pd.Timedelta(weeks=HORIZON_WEEKS)
# Create a date range with 15-minute increments to represent all possible time slots in the schedule
date_range = pd.date_range(start=START_DATE, end=end_date, freq='15min')
# Create a DataFrame to store the schedule, using the generated date range as the index
df_schedule = pd.DataFrame(index=date_range)
# Add a column to track the day of the week for each time slot
df_schedule['dow'] = df_schedule.index.dayofweek # Monday=0, Sunday=6
# Create a column to determine if each 15-minute slot falls within working hours and on a working day
df_schedule['working_hour'] = df_schedule.index.to_series().apply(
lambda x: 1 if (x.weekday() < 5 and WORKING_HOURS[0] <= x.hour < WORKING_HOURS[1]) else 0
)
# Initialize a column in the DataFrame to track time slots that are occupied by existing events
df_schedule['scheduled'] = 0
# Mark time slots occupied by pre-existing events as "scheduled" in the DataFrame
for event, details in events.items():
start_time = details['date_time']
duration_minutes = details['length']
end_time = start_time + pd.Timedelta(minutes=duration_minutes)
# Mark the event slots as unavailable by setting `scheduled` to 1
df_schedule.loc[start_time:end_time, 'scheduled'] = 1
# Create a boolean column `available` to identify time slots that are within working hours and not scheduled
df_schedule['available'] = (df_schedule['working_hour'] == 1) & (df_schedule['scheduled'] == 0)
# Create a numerical index to reference the time slots for easy access later
df_schedule['time_index'] = range(len(df_schedule))

This creates a dataset that looks like:

3) Write our optimization code including:

  • variables
  • constraints
  • objective
# Initialize the CP-SAT model for scheduling optimization
model = cp_model.CpModel()

...

# Loop through all tasks to set up variables and constraints for each
for task in tasks:
details = tasks[task]
due_time = details['due_date']
duration = details['time_to_complete'] // 15 # Duration in 15-minute increments
task_increment_vars = increment_vars[task_i]
# Find the maximum index up to which the task can be scheduled based on its due date
max_due_index = df_schedule[df_schedule.index <= due_time]['time_index'].max()
# Boolean variables to track the task's schedule across all time slots
task_timesteps = [model.NewBoolVar(f'scheduled_step_{task}_{ts}') for ts in range(0, max_index)]
# Integer variable to store the start time of the task
start_timestep = model.NewIntVar(name=f'scheduled_start_step_{task}', ub=max_index, lb=0)
# Variable to track the count of scheduled slots for the task
scheduled_itterator = model.NewIntVar(name=f"scheduled_iter_{task}", lb=0, ub=duration + 1)
# Boolean variable indicating if the task is fully scheduled
scheduled = model.NewBoolVar(f"{task}_scheduled")
...


# Constraints for ensuring that each slot is not double-booked and follows availability rules
for i in range(0, max_index):
availability = available[i]
indexed_increments = []
for t in increment_vars:
indexed_increments.append(t[i])
# Ensure only one task can occupy each slot
model.Add(master_timestep_vars[i] == sum(indexed_increments))
# Ensure tasks are only scheduled within available slots
if availability == False:
model.Add(master_timestep_vars[i] == 0)
# Objective function: maximize the number of fully scheduled tasks
total_scheduled = model.NewIntVar(name="Total_Scheduled", lb=0, ub=1000)
model.add(total_scheduled == sum(scheduled_vars))
model.Maximize(total_scheduled)
# Solve the model and check for an optimal or feasible solution
solver = cp_model.CpSolver()
status = solver.Solve(model)

📝 Notes on the Code:

Constraint optimization can be quite an art! One of my favorite “inventions” I developed in this code is using a “master” timestep variable for overlap, working hours, and pre-existing events. Instead of multiple constraints, we link time slots to boolean variables and set constraints on these “master” timestep variables:

  • 1 if available.
  • 0 if outside work hours or pre-occupied.

With these simple constraints, we ensure no overlap and adherence to work hours and event constraints!

💻 Try it Yourself!

Check out my GitHub for a full implementation of this Motion clone [GitHub link]. It’s a great way to experiment with the concept! Here are the results from the example settings, events, and tasks.

Highlighted sections are work hours, the gray line indicates previously scheduled events and the colored horizontal lines are scheduled tasks. You’ll notice that the scheduled tasks avoid conflicting events and fall within working hours.

🚀 Conclusion:

So, as you can see, we can indeed develop the capabilities of tools like Motion using Mathematical Optimization! 🥳 If you enjoyed this peek “Under the Hood,” let me know by replying to this email or commenting on the field note.

Oh, and do check out Motion for a polished experience — they’ve got a great UI, and you can connect it to your Google or Outlook calendar for seamless scheduling!

https://get.usemotion.com/34nmt4f5cg5x

Thanks for coming along on this new type of field note! 😊

--

--

Benny Friedman

I am a spatial data scientist fascinated by how we measure our built environment.