Build Motion
KINDAreplaces $19/mosaves $228/yrback to the verdict
Tasks with duration, deadline and priority, availability read from your real calendar, a solver that places them into free time respecting working hours and deadlines, time blocks written back as calendar events, and a rescheduling pass that explains what moved and why when a meeting appears.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the solver and the service.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - accountfree with an existing account
Why Availability from a test calendar tells you nothing, and writing blocks into a real calendar is the whole product. Use one you actually live by.
Get it Set up the connection with your provider's credentials or CalDAV. Create a dedicated calendar for scheduled blocks so they are visually separable and easy to clear.
Verify
Busy time reads correctly and a test event writes to the blocks calendar - installfree
Why Holds tasks, plans and the history of what moved.
Get it Install locally or run it in Docker. open ↗
Verify
psql connects and reports 16 or higher - have readyfree
Why The solver is only interesting against real work, and honest durations are what reveal whether the plan is achievable. Optimistic estimates produce a plan that fits and a week that does not.
Get it Write down what you actually intend to do for two weeks, with a duration you would defend and a real deadline.
Verify
At least twenty tasks with durations and deadlines are entered - accountfree
Why Product phase only. Most people have a second calendar containing meetings that block time, and ignoring it produces a plan that collides with reality.
Get it Connect it read-only. It constrains placement without receiving blocks.
Verify
Its events appear as busy time
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- tasks(id, title, duration_minutes, deadline_at, priority, min_block_minutes, max_block_minutes, state, project_id) - dependencies(task_id, depends_on_task_id) · a task cannot be scheduled before what it needs - availability(id, weekday, start_local, end_local, kind) · kind is work, deep or personal, in a stated time zone - busy(id, source, starts_at_utc, ends_at_utc, external_id) · pulled from real calendars, never invented - blocks(id, task_id, starts_at_utc, ends_at_utc, external_event_id, generation) · what was written back - schedule_runs(id, at, generation, tasks_considered, tasks_placed, tasks_unplaceable, reason_json) - A generation number on every block is what lets a rebuild replace the previous plan cleanly instead of accumulating duplicates.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://user:pass@localhost:5432/schedule | Your PostgreSQL connection string. |
BLOCKS_CALENDAR_ID | required | scheduled-work | The calendar that receives blocks. A dedicated one, so clearing it is safe. |
EVENT_TAG | required | autoplan | The property value marking events this application created. Nothing without it is ever modified. |
WORKING_TIMEZONE | required | Europe/London | The zone working hours are expressed in. Not the server's zone. |
MIN_BLOCK_MINUTES | required | 30 | Default minimum block. Shorter blocks fit more easily and are less useful for real work. |
AVAILABILITY_STALE_MINUTES | required | 60 | How old calendar data may be before the solver refuses to write a plan. |
PLAN_HORIZON_DAYS | required | 14 | How far ahead to schedule. Longer plans are more fragile because more will change. |
The build, in order
Real availability
The application knows exactly when you are free, from your actual calendar.
Behind one interface so the provider can change. Store instants with the source and external identifier.
Files
src/calendar.tsPer weekday, local wall clock, with the zone stated explicitly rather than inherited from the server.
Files
src/availability.tsConvert working hours to instants per date. Test across a clock change.
Files
src/availability.test.ts
done when · tick each as it passeswatch out- Working hours are local wall clock and busy time is instants, so subtracting one from the other without converting produces free intervals that shift by an hour twice a year. Convert working hours to instants for the specific dates being scheduled, using the zone, rather than treating them as fixed offsets.
A solver that explains itself
Tasks are placed into free time, and every task that was not placed says why.
Deadline first, then priority, with a deterministic tie-break so runs are reproducible.
Files
src/solve.tsRespect minimum and maximum block size. A task needing two hours of unbroken focus cannot be four half hours.
Files
src/solve.tsUnplaceable is an output, not an omission. This is the difference between a plan and a wish.
Files
src/solve.ts
done when · tick each as it passeswatch out- A solver that silently omits what it cannot fit produces a plan that looks achievable and is not, and you discover the missing work at the deadline. Every unplaced task must appear with a reason: not enough free time before the deadline, blocked by a dependency, or a minimum block size larger than any available gap.
Writing blocks back
The plan appears in your calendar, and rebuilding it does not leave debris.
An identifier in a property you own. Nothing untagged is ever touched.
Files
src/write.tsDelete the previous generation's tagged events, then write the new one. Count before and after.
Files
src/write.tsA block whose event is gone is a signal the user rejected it, not an error to correct.
Files
src/sync.ts
done when · tick each as it passeswatch out- Identifying your own events by title or by time will eventually delete a real meeting, and that is an unrecoverable mistake in someone's calendar. Tag every event you create with an identifier you own, and refuse to delete anything without that tag, even during cleanup.
Rescheduling when the day changes
A meeting appearing at short notice reshuffles the plan, and you can see what moved.
On a schedule or on a change notification. A stale plan is worse than no plan.
Files
src/watch.tsKeep placements that still fit. Only what the change displaced may move.
Files
src/solve.tsMoved, at risk, and newly unplaceable. This is what the user actually reads after a rerun.
Files
src/diff.ts
done when · tick each as it passeswatch out- A solver rerun that reshuffles everything because one meeting appeared makes the plan feel random and untrustworthy, even when each individual placement is optimal. Weight stability explicitly: keep blocks that still fit, and move only what the change forced.
Being honest about the plan
The application tells you when the plan is not achievable rather than presenting a full calendar.
Total task duration against total free time for the horizon. One number, shown prominently.
Files
src/capacity.tsScheduled against actually done. Report the ratio as information about your estimates.
Files
src/completion.tsAn estimate that was optimistic is a fact about estimating, not a failing.
Files
src/report.ts
done when · tick each as it passeswatch out- A scheduler that always produces a full plan implies the work is achievable, and the honest failure mode is telling someone that it is not. If committed work exceeds available time, say so at the top rather than placing tasks past their deadlines and calling it a schedule.
Multiple calendars and running it continuouslyproduct builder
It reads every calendar that constrains you, and it keeps running without supervision.
Read-only ones count as constraints. Write only to the one you own.
Files
src/calendar.tsA freshness threshold per calendar. Refusing is safer than scheduling over an invisible meeting.
Files
src/freshness.tsScheduled runs, restart survival, and an alert on connection failure.
Files
src/service.ts
done when · tick each as it passeswatch out- A silently failed calendar connection means the solver schedules over meetings it can no longer see, and the plan looks normal while being wrong. Track last successful sync per calendar and refuse to write a plan built on availability older than a threshold.
That is the whole plan for Motion. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Multi-provider calendar sync at commercial reliability.
- Recurring work rules and accumulated scheduling patterns.
- Mobile applications.
- Team scheduling across several people.
- multi-calendar sync
- recurring work rules
- mobile apps
- team scheduling
- polished rescheduling
- support
- Energy-aware placement, putting demanding work in the hours you actually do it well
- Task splitting suggestions when a task is too large for any available gap
- A morning summary showing today's plan and what is at risk
Need the files? The project pack on the verdict page hands your agent the whole brief · more tasks & calendar.